/* * Copyright (C) 2002 Andrew Tridgell * Copyright (C) 2009-2010 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 #include #ifdef HAVE_SYS_MMAN_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef _WIN32 #include #include #endif static FILE *logfile; static int init_log(void) { extern char *cache_logfile; if (logfile) { return 1; } if (!cache_logfile) { return 0; } logfile = fopen(cache_logfile, "a"); if (logfile) { return 1; } else { return 0; } } 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) { if (write(fd_out, buf, n) != n) { fatal("Failed to copy fd"); } } 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, ret; char *tmp_name; #ifndef _WIN32 mode_t mask; #endif struct stat st; int errnum; cc_log("Copying %s to %s (%s)", src, dest, 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 */ tmp_name = format("%s.%s.XXXXXX", dest, tmp_string()); 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) { ret = gzwrite(gz_out, buf, n); } else { ret = write(fd_out, buf, n); } if (ret != 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; } } if (n == 0 && !gzeof(gz_in)) { 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); 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); } 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) { 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 */ int test_if_compressed(const char *filename) { FILE *f; f = fopen(filename, "rb"); if (!f) { return 0; } /* test if file starts with 1F8B, which is zlib's * magic number */ if ((fgetc(f) != 0x1f) || (fgetc(f) != 0x8b)) { fclose(f); return 0; } fclose(f); return 1; } /* 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) { 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; ret = malloc(size); if (!ret) { fatal("x_malloc: 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) { return 0; } 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(); 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) { return x_strdup(pwd); } else { return cwd; } } /* * Check whether s1 and s2 have the same executable name. */ int compare_executable_name(const char *s1, const char *s2) { #ifdef _WIN32 int 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. */ int 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. */ int 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 } /* * Map file into memory. Return a pointer to the mapped area if successful or * -1 if any error occurred. The file size is also returned, */ void * x_fmmap(const char *fname, off_t *size, const char *errstr) { struct stat st; void *data = (void *) -1; int fd = -1; #ifdef _WIN32 HANDLE section; HANDLE file; file = CreateFile(fname, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (file == INVALID_HANDLE_VALUE) { cc_log("Failed to open %s %s", errstr, fname); goto error; } fd = _open_osfhandle((intptr_t) file, O_RDONLY | O_BINARY); if (fd == -1) { cc_log("Failed to open %s %s", errstr, fname); CloseHandle(file); goto error; } if (fstat(fd, &st) == -1) { cc_log("Failed to fstat %s %s", errstr, fname); CloseHandle(file); goto error; } section = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL); CloseHandle(file); if (!section) { cc_log("Failed to mmap %s %s", errstr, fname); goto error; } data = MapViewOfFile(section, FILE_MAP_READ, 0, 0, 0); CloseHandle(section); #else fd = open(fname, O_RDONLY | O_BINARY); if (fd == -1) { cc_log("Failed to open %s %s", errstr, fname); goto error; } if (fstat(fd, &st) == -1) { cc_log("Failed to fstat %s %s", errstr, fname); close(fd); goto error; } data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (data == (void *) -1) { cc_log("Failed to mmap %s %s", errstr, fname); } #endif *size = st.st_size; error: return data; } /* * Unmap file from memory. */ int x_munmap(void *addr, size_t length) { #ifdef _WIN32 (void) length; return UnmapViewOfFile(addr) ? 0 : -1; #else return munmap(addr, length); #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); #endif return rename(oldpath, newpath); } #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 /* Return the content of a text file, or NULL on error. Caller frees. */ char * read_file(const char *path) { int fd, ret; size_t pos = 0, allocated = 1024; char *result = malloc(allocated); fd = open(path, O_RDONLY); if (fd == -1) { free(result); return NULL; } ret = 0; do { pos += ret; if (pos > allocated / 2) { allocated *= 2; result = realloc(result, allocated); } } while ((ret = read(fd, result + pos, allocated - pos - 1)) > 0); close(fd); if (ret == -1) { free(result); return NULL; } result[pos] = '\0'; return result; }