diff options
Diffstat (limited to 'util.c')
-rw-r--r-- | util.c | 1210 |
1 files changed, 1210 insertions, 0 deletions
@@ -0,0 +1,1210 @@ +/* + * Copyright (C) 2002 Andrew Tridgell + * Copyright (C) 2009-2011 Joel Rosdahl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "ccache.h" + +#include <zlib.h> + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#include <sys/locking.h> +#endif + +static FILE *logfile; + +static bool +init_log(void) +{ + extern char *cache_logfile; + + if (logfile) { + return true; + } + if (!cache_logfile) { + return false; + } + logfile = fopen(cache_logfile, "a"); + if (logfile) { + return true; + } else { + return false; + } +} + +static void +log_prefix(void) +{ +#ifdef HAVE_GETTIMEOFDAY + char timestamp[100]; + struct timeval tv; + struct tm *tm; + + gettimeofday(&tv, NULL); +#ifdef __MINGW64_VERSION_MAJOR + tm = _localtime32(&tv.tv_sec); +#else + tm = localtime(&tv.tv_sec); +#endif + strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S", tm); + fprintf(logfile, "[%s.%06d %-5d] ", timestamp, (int)tv.tv_usec, + (int)getpid()); +#else + fprintf(logfile, "[%-5d] ", (int)getpid()); +#endif +} + +/* + * Write a message to the CCACHE_LOGFILE location (adding a newline). + */ +void +cc_log(const char *format, ...) +{ + va_list ap; + + if (!init_log()) { + return; + } + + log_prefix(); + va_start(ap, format); + vfprintf(logfile, format, ap); + va_end(ap); + fprintf(logfile, "\n"); + fflush(logfile); +} + +/* + * Log an executed command to the CCACHE_LOGFILE location. + */ +void +cc_log_argv(const char *prefix, char **argv) +{ + if (!init_log()) { + return; + } + + log_prefix(); + fputs(prefix, logfile); + print_command(logfile, argv); + fflush(logfile); +} + +/* something went badly wrong! */ +void +fatal(const char *format, ...) +{ + va_list ap; + char msg[1000]; + + va_start(ap, format); + vsnprintf(msg, sizeof(msg), format, ap); + va_end(ap); + + cc_log("FATAL: %s", msg); + fprintf(stderr, "ccache: FATAL: %s\n", msg); + + exit(1); +} + +/* + * Copy all data from fd_in to fd_out, decompressing data from fd_in if needed. + */ +void +copy_fd(int fd_in, int fd_out) +{ + char buf[10240]; + int n; + gzFile gz_in; + + gz_in = gzdopen(dup(fd_in), "rb"); + + if (!gz_in) { + fatal("Failed to copy fd"); + } + + while ((n = gzread(gz_in, buf, sizeof(buf))) > 0) { + ssize_t count, written = 0; + do { + count = write(fd_out, buf + written, n - written); + if (count == -1 && errno != EINTR) { + fatal("Failed to copy fd"); + } + written += count; + } while (written < n); + } + + gzclose(gz_in); +} + +#ifndef HAVE_MKSTEMP +/* cheap and nasty mkstemp replacement */ +int +mkstemp(char *template) +{ + mktemp(template); + return open(template, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600); +} +#endif + +/* + * Copy src to dest, decompressing src if needed. compress_dest decides whether + * dest will be compressed. + */ +int +copy_file(const char *src, const char *dest, int compress_dest) +{ + int fd_in = -1, fd_out = -1; + gzFile gz_in = NULL, gz_out = NULL; + char buf[10240]; + int n, written; + char *tmp_name; +#ifndef _WIN32 + mode_t mask; +#endif + struct stat st; + int errnum; + + tmp_name = format("%s.%s.XXXXXX", dest, tmp_string()); + cc_log("Copying %s to %s via %s (%s)", + src, dest, tmp_name, compress_dest ? "compressed": "uncompressed"); + + /* open source file */ + fd_in = open(src, O_RDONLY | O_BINARY); + if (fd_in == -1) { + cc_log("open error: %s", strerror(errno)); + return -1; + } + + gz_in = gzdopen(fd_in, "rb"); + if (!gz_in) { + cc_log("gzdopen(src) error: %s", strerror(errno)); + close(fd_in); + return -1; + } + + /* open destination file */ + fd_out = mkstemp(tmp_name); + if (fd_out == -1) { + cc_log("mkstemp error: %s", strerror(errno)); + goto error; + } + + if (compress_dest) { + /* + * A gzip file occupies at least 20 bytes, so it will always + * occupy an entire filesystem block, even for empty files. + * Turn off compression for empty files to save some space. + */ + if (fstat(fd_in, &st) != 0) { + cc_log("fstat error: %s", strerror(errno)); + goto error; + } + if (file_size(&st) == 0) { + compress_dest = 0; + } + } + + if (compress_dest) { + gz_out = gzdopen(dup(fd_out), "wb"); + if (!gz_out) { + cc_log("gzdopen(dest) error: %s", strerror(errno)); + goto error; + } + } + + while ((n = gzread(gz_in, buf, sizeof(buf))) > 0) { + if (compress_dest) { + written = gzwrite(gz_out, buf, n); + } else { + ssize_t count; + written = 0; + do { + count = write(fd_out, buf + written, n - written); + if (count == -1 && errno != EINTR) { + break; + } + written += count; + } while (written < n); + } + if (written != n) { + if (compress_dest) { + cc_log("gzwrite error: %s (errno: %s)", + gzerror(gz_in, &errnum), + strerror(errno)); + } else { + cc_log("write error: %s", strerror(errno)); + } + goto error; + } + } + + /* + * gzeof won't tell if there's an error in the trailing CRC, so we must check + * gzerror before considering everything OK. + */ + gzerror(gz_in, &errnum); + if (!gzeof(gz_in) || (errnum != Z_OK && errnum != Z_STREAM_END)) { + cc_log("gzread error: %s (errno: %s)", + gzerror(gz_in, &errnum), strerror(errno)); + gzclose(gz_in); + if (gz_out) { + gzclose(gz_out); + } + close(fd_out); + tmp_unlink(tmp_name); + free(tmp_name); + return -1; + } + + gzclose(gz_in); + gz_in = NULL; + if (gz_out) { + gzclose(gz_out); + gz_out = NULL; + } + +#ifndef _WIN32 + /* get perms right on the tmp file */ + mask = umask(0); + fchmod(fd_out, 0666 & ~mask); + umask(mask); +#endif + + /* the close can fail on NFS if out of space */ + if (close(fd_out) == -1) { + cc_log("close error: %s", strerror(errno)); + goto error; + } + + if (x_rename(tmp_name, dest) == -1) { + cc_log("rename error: %s", strerror(errno)); + goto error; + } + + free(tmp_name); + + return 0; + +error: + if (gz_in) { + gzclose(gz_in); + } + if (gz_out) { + gzclose(gz_out); + } + if (fd_out != -1) { + close(fd_out); + } + tmp_unlink(tmp_name); + free(tmp_name); + return -1; +} + +/* Run copy_file() and, if successful, delete the source file. */ +int +move_file(const char *src, const char *dest, int compress_dest) +{ + int ret; + + ret = copy_file(src, dest, compress_dest); + if (ret != -1) { + x_unlink(src); + } + return ret; +} + +/* + * Like move_file(), but assumes that src is uncompressed and that src and dest + * are on the same file system. + */ +int +move_uncompressed_file(const char *src, const char *dest, int compress_dest) +{ + if (compress_dest) { + return move_file(src, dest, compress_dest); + } else { + return x_rename(src, dest); + } +} + +/* test if a file is zlib compressed */ +bool +file_is_compressed(const char *filename) +{ + FILE *f; + + f = fopen(filename, "rb"); + if (!f) { + return false; + } + + /* test if file starts with 1F8B, which is zlib's + * magic number */ + if ((fgetc(f) != 0x1f) || (fgetc(f) != 0x8b)) { + fclose(f); + return false; + } + + fclose(f); + return true; +} + +/* make sure a directory exists */ +int +create_dir(const char *dir) +{ + struct stat st; + if (stat(dir, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + return 0; + } + errno = ENOTDIR; + return 1; + } + if (mkdir(dir, 0777) != 0 && errno != EEXIST) { + return 1; + } + return 0; +} + +/* + * Return a static string with the current hostname. + */ +const char * +get_hostname(void) +{ + static char hostname[200] = ""; + + if (!hostname[0]) { + strcpy(hostname, "unknown"); +#if HAVE_GETHOSTNAME + gethostname(hostname, sizeof(hostname)-1); +#endif + hostname[sizeof(hostname)-1] = 0; + } + + return hostname; +} + +/* + * Return a string to be used to distinguish temporary files. Also tries to + * cope with NFS by adding the local hostname. + */ +const char * +tmp_string(void) +{ + static char *ret; + + if (!ret) { + ret = format("%s.%u", get_hostname(), (unsigned)getpid()); + } + + return ret; +} + +/* Return the hash result as a hex string. Caller frees. */ +char * +format_hash_as_string(const unsigned char *hash, unsigned size) +{ + char *ret; + int i; + + ret = x_malloc(53); + for (i = 0; i < 16; i++) { + sprintf(&ret[i*2], "%02x", (unsigned) hash[i]); + } + sprintf(&ret[i*2], "-%u", size); + + return ret; +} + +char const CACHEDIR_TAG[] = + "Signature: 8a477f597d28d172789f06886806bc55\n" + "# This file is a cache directory tag created by ccache.\n" + "# For information about cache directory tags, see:\n" + "# http://www.brynosaurus.com/cachedir/\n"; + +int +create_cachedirtag(const char *dir) +{ + struct stat st; + FILE *f; + char *filename = format("%s/CACHEDIR.TAG", dir); + if (stat(filename, &st) == 0) { + if (S_ISREG(st.st_mode)) { + goto success; + } + errno = EEXIST; + goto error; + } + f = fopen(filename, "w"); + if (!f) goto error; + if (fwrite(CACHEDIR_TAG, sizeof(CACHEDIR_TAG)-1, 1, f) != 1) { + fclose(f); + goto error; + } + if (fclose(f)) goto error; +success: + free(filename); + return 0; +error: + free(filename); + return 1; +} + +/* Construct a string according to a format. Caller frees. */ +char * +format(const char *format, ...) +{ + va_list ap; + char *ptr = NULL; + + va_start(ap, format); + if (vasprintf(&ptr, format, ap) == -1) { + fatal("Out of memory in format"); + } + va_end(ap); + + if (!*ptr) fatal("Internal error in format"); + return ptr; +} + +/* + this is like strdup() but dies if the malloc fails +*/ +char * +x_strdup(const char *s) +{ + char *ret; + ret = strdup(s); + if (!ret) { + fatal("Out of memory in x_strdup"); + } + return ret; +} + +/* + this is like strndup() but dies if the malloc fails +*/ +char * +x_strndup(const char *s, size_t n) +{ + char *ret; +#ifndef HAVE_STRNDUP + size_t m; + + if (!s) + return NULL; + m = 0; + while (m < n && s[m]) { + m++; + } + ret = malloc(m + 1); + if (ret) { + memcpy(ret, s, m); + ret[m] = '\0'; + } +#else + ret = strndup(s, n); +#endif + if (!ret) { + fatal("x_strndup: Could not allocate %lu bytes", (unsigned long)n); + } + return ret; +} + +/* + this is like malloc() but dies if the malloc fails +*/ +void * +x_malloc(size_t size) +{ + void *ret; + if (size == 0) { + /* + * malloc() may return NULL if size is zero, so always do this to make sure + * that the code handles it regardless of platform. + */ + return NULL; + } + ret = malloc(size); + if (!ret) { + fatal("x_malloc: Could not allocate %lu bytes", (unsigned long)size); + } + return ret; +} + +/* This is like calloc() but dies if the allocation fails. */ +void * +x_calloc(size_t nmemb, size_t size) +{ + void *ret; + if (nmemb * size == 0) { + /* + * calloc() may return NULL if nmemb or size is 0, so always do this to + * make sure that the code handles it regardless of platform. + */ + return NULL; + } + ret = calloc(nmemb, size); + if (!ret) { + fatal("x_calloc: Could not allocate %lu bytes", (unsigned long)size); + } + return ret; +} + +/* + this is like realloc() but dies if the malloc fails +*/ +void * +x_realloc(void *ptr, size_t size) +{ + void *p2; + if (!ptr) return x_malloc(size); + p2 = realloc(ptr, size); + if (!p2) { + fatal("x_realloc: Could not allocate %lu bytes", (unsigned long)size); + } + return p2; +} + + +/* + * This is like x_asprintf() but frees *ptr if *ptr != NULL. + */ +void +x_asprintf2(char **ptr, const char *format, ...) +{ + char *saved = *ptr; + va_list ap; + + *ptr = NULL; + va_start(ap, format); + if (vasprintf(ptr, format, ap) == -1) { + fatal("Out of memory in x_asprintf2"); + } + va_end(ap); + + if (!ptr) fatal("Out of memory in x_asprintf2"); + if (saved) { + free(saved); + } +} + +/* + * Recursive directory traversal. fn() is called on all entries in the tree. + */ +void +traverse(const char *dir, void (*fn)(const char *, struct stat *)) +{ + DIR *d; + struct dirent *de; + + d = opendir(dir); + if (!d) return; + + while ((de = readdir(d))) { + char *fname; + struct stat st; + + if (str_eq(de->d_name, ".")) continue; + if (str_eq(de->d_name, "..")) continue; + + if (strlen(de->d_name) == 0) continue; + + fname = format("%s/%s", dir, de->d_name); + if (lstat(fname, &st)) { + if (errno != ENOENT) { + perror(fname); + } + free(fname); + continue; + } + + if (S_ISDIR(st.st_mode)) { + traverse(fname, fn); + } + + fn(fname, &st); + free(fname); + } + + closedir(d); +} + + +/* return the base name of a file - caller frees */ +char * +basename(const char *s) +{ + char *p; + p = strrchr(s, '/'); + if (p) s = p + 1; +#ifdef _WIN32 + p = strrchr(s, '\\'); + if (p) s = p + 1; +#endif + + return x_strdup(s); +} + +/* return the dir name of a file - caller frees */ +char * +dirname(char *s) +{ + char *p; + char *p2 = NULL; + s = x_strdup(s); + p = strrchr(s, '/'); +#ifdef _WIN32 + p2 = strrchr(s, '\\'); +#endif + if (p < p2) + p = p2; + if (p) { + *p = 0; + return s; + } else { + free(s); + return x_strdup("."); + } +} + +/* + * Return the file extension (including the dot) of a path as a pointer into + * path. If path has no file extension, the empty string and the end of path is + * returned. + */ +const char * +get_extension(const char *path) +{ + size_t len = strlen(path); + const char *p; + + for (p = &path[len - 1]; p >= path; --p) { + if (*p == '.') { + return p; + } + if (*p == '/') { + break; + } + } + return &path[len]; +} + +/* + * Return a string containing the given path without the filename extension. + * Caller frees. + */ +char * +remove_extension(const char *path) +{ + return x_strndup(path, strlen(path) - strlen(get_extension(path))); +} + +/* return size on disk of a file */ +size_t +file_size(struct stat *st) +{ +#ifdef _WIN32 + return (st->st_size + 1023) & ~1023; +#else + size_t size = st->st_blocks * 512; + if ((size_t)st->st_size > size) { + /* probably a broken stat() call ... */ + size = (st->st_size + 1023) & ~1023; + } + return size; +#endif +} + +/* a safe open/create for read-write */ +int +safe_open(const char *fname) +{ + int fd = open(fname, O_RDWR|O_BINARY); + if (fd == -1 && errno == ENOENT) { + fd = open(fname, O_RDWR|O_CREAT|O_EXCL|O_BINARY, 0666); + if (fd == -1 && errno == EEXIST) { + fd = open(fname, O_RDWR|O_BINARY); + } + } + return fd; +} + +/* Format a size (in KiB) as a human-readable string. Caller frees. */ +char * +format_size(size_t v) +{ + char *s; + if (v >= 1024*1024) { + s = format("%.1f Gbytes", v/((double)(1024*1024))); + } else if (v >= 1024) { + s = format("%.1f Mbytes", v/((double)(1024))); + } else { + s = format("%.0f Kbytes", (double)v); + } + return s; +} + +/* return a value in multiples of 1024 give a string that can end + in K, M or G +*/ +size_t +value_units(const char *s) +{ + char m; + double v = atof(s); + m = s[strlen(s)-1]; + switch (m) { + case 'G': + case 'g': + default: + v *= 1024*1024; + break; + case 'M': + case 'm': + v *= 1024; + break; + case 'K': + case 'k': + v *= 1; + break; + } + return (size_t)v; +} + +#ifndef _WIN32 +static long +path_max(const char *path) +{ +#ifdef PATH_MAX + (void)path; + return PATH_MAX; +#elif defined(MAXPATHLEN) + (void)path; + return MAXPATHLEN; +#elif defined(_PC_PATH_MAX) + long maxlen = pathconf(path, _PC_PATH_MAX); + if (maxlen >= 4096) { + return maxlen; + } else { + return 4096; + } +#endif +} + +/* + a sane realpath() function, trying to cope with stupid path limits and + a broken API +*/ +char * +x_realpath(const char *path) +{ + long maxlen = path_max(path); + char *ret, *p; + + ret = x_malloc(maxlen); + +#if HAVE_REALPATH + p = realpath(path, ret); +#else + /* yes, there are such systems. This replacement relies on + the fact that when we call x_realpath we only care about symlinks */ + { + int len = readlink(path, ret, maxlen-1); + if (len == -1) { + free(ret); + return NULL; + } + ret[len] = 0; + p = ret; + } +#endif + if (p) { + p = x_strdup(p); + free(ret); + return p; + } + free(ret); + return NULL; +} +#endif /* !_WIN32 */ + +/* a getcwd that will returns an allocated buffer */ +char * +gnu_getcwd(void) +{ + unsigned size = 128; + + while (1) { + char *buffer = (char *)x_malloc(size); + if (getcwd(buffer, size) == buffer) { + return buffer; + } + free(buffer); + if (errno != ERANGE) { + cc_log("getcwd error: %d (%s)", errno, strerror(errno)); + return NULL; + } + size *= 2; + } +} + +/* create an empty file */ +int +create_empty_file(const char *fname) +{ + int fd; + + fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_BINARY, 0666); + if (fd == -1) { + return -1; + } + close(fd); + return 0; +} + +/* + * Return current user's home directory, or NULL if it can't be determined. + */ +const char * +get_home_directory(void) +{ + const char *p = getenv("HOME"); + if (p) { + return p; + } +#ifdef HAVE_GETPWUID + { + struct passwd *pwd = getpwuid(getuid()); + if (pwd) { + return pwd->pw_dir; + } + } +#endif + return NULL; +} + +/* + * Get the current directory by reading $PWD. If $PWD isn't sane, gnu_getcwd() + * is used. Caller frees. + */ +char * +get_cwd(void) +{ + char *pwd; + char *cwd; + struct stat st_pwd; + struct stat st_cwd; + + cwd = gnu_getcwd(); + if (!cwd) { + return NULL; + } + pwd = getenv("PWD"); + if (!pwd) { + return cwd; + } + if (stat(pwd, &st_pwd) != 0) { + return cwd; + } + if (stat(cwd, &st_cwd) != 0) { + return cwd; + } + if (st_pwd.st_dev == st_cwd.st_dev && st_pwd.st_ino == st_cwd.st_ino) { + free(cwd); + return x_strdup(pwd); + } else { + return cwd; + } +} + +/* + * Check whether s1 and s2 have the same executable name. + */ +bool +same_executable_name(const char *s1, const char *s2) +{ +#ifdef _WIN32 + bool eq = strcasecmp(s1, s2) == 0; + if (!eq) { + char *tmp = format("%s.exe", s2); + eq = strcasecmp(s1, tmp) == 0; + free(tmp); + } + return eq; +#else + return str_eq(s1, s2); +#endif +} + +/* + * Compute the length of the longest directory path that is common to two + * strings. + */ +size_t +common_dir_prefix_length(const char *s1, const char *s2) +{ + const char *p1 = s1; + const char *p2 = s2; + + while (*p1 && *p2 && *p1 == *p2) { + ++p1; + ++p2; + } + while (p1 > s1 && ((*p1 && *p1 != '/' ) || (*p2 && *p2 != '/'))) { + p1--; + p2--; + } + return p1 - s1; +} + +/* + * Compute a relative path from from to to. Caller frees. + */ +char * +get_relative_path(const char *from, const char *to) +{ + size_t common_prefix_len; + int i; + const char *p; + char *result; + + if (!*to || *to != '/') { + return x_strdup(to); + } + + result = x_strdup(""); + common_prefix_len = common_dir_prefix_length(from, to); + for (p = from + common_prefix_len; *p; p++) { + if (*p == '/') { + x_asprintf2(&result, "../%s", result); + } + } + if (strlen(to) > common_prefix_len) { + p = to + common_prefix_len + 1; + while (*p == '/') { + p++; + } + x_asprintf2(&result, "%s%s", result, p); + } + i = strlen(result) - 1; + while (i >= 0 && result[i] == '/') { + result[i] = '\0'; + i--; + } + if (str_eq(result, "")) { + free(result); + result = x_strdup("."); + } + return result; +} + +/* + * Return whether path is absolute. + */ +bool +is_absolute_path(const char *path) +{ +#ifdef _WIN32 + return path[0] && path[1] == ':'; +#else + return path[0] == '/'; +#endif +} + +/* + * Return whether the argument is a full path. + */ +bool +is_full_path(const char *path) +{ + if (strchr(path, '/')) + return 1; +#ifdef _WIN32 + if (strchr(path, '\\')) + return 1; +#endif + return 0; +} + +/* + * Update the modification time of a file in the cache to save it from LRU + * cleanup. + */ +void +update_mtime(const char *path) +{ +#ifdef HAVE_UTIMES + utimes(path, NULL); +#else + utime(path, NULL); +#endif +} + +/* + * Rename oldpath to newpath (deleting newpath). + */ +int +x_rename(const char *oldpath, const char *newpath) +{ +#ifdef _WIN32 + /* Windows' rename() refuses to overwrite an existing file. */ + unlink(newpath); /* not x_unlink, as x_unlink calls x_rename */ +#endif + return rename(oldpath, newpath); +} + +/* + * Remove path, NFS hazardous. Use only for temporary files that will not exist + * on other systems. That is, the path should include tmp_string(). + */ +int +tmp_unlink(const char *path) +{ + cc_log("Unlink %s (as-tmp)", path); + return unlink(path); +} + +/* + * Remove path, NFS safe. + */ +int +x_unlink(const char *path) +{ + /* + * If path is on an NFS share, unlink isn't atomic, so we rename to a temp + * file. We don't care if the temp file is trashed, so it's always safe to + * unlink it first. + */ + const char* tmp_name = format("%s.%s.rmXXXXXX", path, tmp_string()); + cc_log("Unlink %s via %s", path, tmp_name); + if (x_rename(path, tmp_name) == -1) { + return -1; + } + if (unlink(tmp_name) == -1) { + return -1; + } + return 0; +} + +#ifndef _WIN32 +/* Like readlink() but returns the string or NULL on failure. Caller frees. */ +char * +x_readlink(const char *path) +{ + long maxlen = path_max(path); + ssize_t len; + char *buf; +#ifdef PATH_MAX + maxlen = PATH_MAX; +#elif defined(MAXPATHLEN) + maxlen = MAXPATHLEN; +#elif defined(_PC_PATH_MAX) + maxlen = pathconf(path, _PC_PATH_MAX); +#endif + if (maxlen < 4096) maxlen = 4096; + + buf = x_malloc(maxlen); + len = readlink(path, buf, maxlen-1); + if (len == -1) { + free(buf); + return NULL; + } + buf[len] = 0; + return buf; +} +#endif + +/* + * Reads the content of a file. Size hint 0 means no hint. Returns true on + * success, otherwise false. + */ +bool +read_file(const char *path, size_t size_hint, char **data, size_t *size) +{ + int fd, ret; + size_t pos = 0, allocated; + + if (size_hint == 0) { + struct stat st; + if (stat(path, &st) == 0) { + size_hint = st.st_size; + } + } + size_hint = (size_hint < 1024) ? 1024 : size_hint; + + fd = open(path, O_RDONLY); + if (fd == -1) { + return false; + } + allocated = size_hint; + *data = x_malloc(allocated); + ret = 0; + while (true) { + if (pos > allocated / 2) { + allocated *= 2; + *data = x_realloc(*data, allocated); + } + ret = read(fd, *data + pos, allocated - pos); + if (ret == 0 || (ret == -1 && errno != EINTR)) { + break; + } + if (ret > 0) { + pos += ret; + } + } + close(fd); + if (ret == -1) { + cc_log("Failed reading %s", path); + free(*data); + *data = NULL; + return false; + } + + *size = pos; + return true; +} + +/* + * Return the content (with NUL termination) of a text file, or NULL on error. + * Caller frees. + */ +char * +read_text_file(const char *path) +{ + size_t size; + char *data; + + if (read_file(path, 0, &data, &size)) { + data = x_realloc(data, size + 1); + data[size] = '\0'; + return data; + } else { + return NULL; + } +} |