/** * Get the full program path. * * @return a newly allocated string (through halloc()) that points to the * path of the program being run, NULL if we can't compute a suitable path. */ char * file_program_path(const char *argv0) { filestat_t buf; char *file = deconstify_char(argv0); char filepath[MAX_PATH_LEN + 1]; if (is_running_on_mingw() && !is_strsuffix(argv0, (size_t) -1, ".exe")) { concat_strings(filepath, sizeof filepath, argv0, ".exe", NULL_PTR); } else { clamp_strcpy(filepath, sizeof filepath, argv0); } if (-1 == stat(filepath, &buf)) { int saved_errno = errno; file = file_locate_from_path(argv0); if (NULL == file) { errno = saved_errno; s_warning("%s(): could not stat() \"%s\": %m", G_STRFUNC, filepath); return NULL; } } if (file != NULL && file != argv0) return file; /* Allocated by file_locate_from_path() */ return h_strdup(filepath); }
/** * Search executable within the user's PATH. * * @return full path if found, NULL otherwise. * The returned string is allocated with halloc(). */ char * file_locate_from_path(const char *argv0) { static bool already_done; char *path; char *tok; char filepath[MAX_PATH_LEN + 1]; char *result = NULL; char *ext = ""; if (is_running_on_mingw() && !is_strsuffix(argv0, (size_t) -1, ".exe")) { ext = ".exe"; } if (filepath_basename(argv0) != argv0) { if (!already_done) { s_warning("can't locate \"%s\" in PATH: name contains '%c' already", argv0, strchr(argv0, G_DIR_SEPARATOR) != NULL ? G_DIR_SEPARATOR : '/'); } goto done; } path = getenv("PATH"); if (NULL == path) { if (!already_done) { s_warning("can't locate \"%s\" in PATH: " "no such environment variable", argv0); } goto done; } path = h_strdup(path); tok = strtok(path, G_SEARCHPATH_SEPARATOR_S); while (NULL != tok) { const char *dir = tok; filestat_t buf; if ('\0' == *dir) dir = "."; concat_strings(filepath, sizeof filepath, dir, G_DIR_SEPARATOR_S, argv0, ext, NULL); if (-1 != stat(filepath, &buf)) { if (S_ISREG(buf.st_mode) && -1 != access(filepath, X_OK)) { result = h_strdup(filepath); break; } } tok = strtok(NULL, G_SEARCHPATH_SEPARATOR_S); } hfree(path); done: already_done = TRUE; /* No warning on subsequent invocation */ return result; }
/** * Set iterator to close all known socket descriptors. */ static void fd_socket_close(const void *data, void *udata) { (void) udata; if (is_running_on_mingw()) { socket_fd_t fd = pointer_to_int(data); (void) s_close(fd); } }
/** * Notifies that a socket descriptor has been closed. * * This is only required on Windows, since we need to keep track of opened * socket descriptors, in order to close them before exec(). Failure to * do so would leave listen sockets around, and because we use SO_REUSEADDR * to bind our listening sockets, we would have two processes listening on * the same socket -- a recipe for blackouts on Windows! */ void fd_notify_socket_closed(socket_fd_t fd) { if (!is_running_on_mingw()) { s_carp_once("%s(): not needed on UNIX", G_STRFUNC); return; } else { if G_LIKELY(fd_sockets != NULL) hset_remove(fd_sockets, int_to_pointer(fd)); } }
static bool filename_is_reserved(const char *filename) { const char *endptr; if ('\0' == filename[0]) return TRUE; /** * FIXME: Doesn't this apply to CYGWIN, too? */ if (!is_running_on_mingw()) return FALSE; /** * The following may be a superset because PRN1 is (probably) not reserved. */ if (!( (endptr = is_strcaseprefix(filename, "aux")) || (endptr = is_strcaseprefix(filename, "com")) || (endptr = is_strcaseprefix(filename, "con")) || (endptr = is_strcaseprefix(filename, "lpt")) || (endptr = is_strcaseprefix(filename, "nul")) || (endptr = is_strcaseprefix(filename, "prn")) )) return FALSE; switch (*endptr) { case '\0': return TRUE; case '.': /* con.txt is reserved con.blah.txt isn't */ return NULL == strchr(&endptr[1], '.'); case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* lpt0, com0 are not reserved */ endptr++; switch (*endptr) { case '\0': return TRUE; case '.': /* com1.txt is reserved com1.blah.txt isn't */ return NULL == strchr(&endptr[1], '.'); } break; } /* com1blah.txt is not reserved */ return FALSE; }
/** * Check whether path is an absolute path. */ bool is_absolute_path(const char *path) { g_assert(path != NULL); if (is_dir_separator(path[0])) return TRUE; /* On Windows also check for something like C:\ and x:/ */ return is_running_on_mingw() && is_ascii_alpha(path[0]) && ':' == path[1] && is_dir_separator(path[2]); }
/** * Execute special operation. * * @param old_name An absolute pathname, the old file name. * @param new_name An absolute pathname, the new file name. * * @return TRUE if operation was successful, FALSE otherwise, with errno set. */ static bool file_object_special_op(enum file_object_op op, const char * const old_name, const char * const new_name) { const int accmodes[] = { O_RDONLY, O_WRONLY, O_RDWR }; unsigned i; GSList *objects = NULL; bool ok = TRUE; int saved_errno = 0; errno = EINVAL; /* In case one of the soft assertions fails */ g_return_val_if_fail(old_name, FALSE); g_return_val_if_fail(is_absolute_path(old_name), FALSE); if (op != FO_OP_UNLINK) { g_return_val_if_fail(new_name, FALSE); g_return_val_if_fail(is_absolute_path(new_name), FALSE); } /* * Identify all the file objects currently active for the old name. */ for (i = 0; i < G_N_ELEMENTS(accmodes); i++) { struct file_object *fo; fo = file_object_find(old_name, accmodes[i]); if (fo != NULL) { if (NULL == g_slist_find(objects, fo)) { objects = g_slist_prepend(objects, fo); } } } /* * On Windows, close all the files prior renaming / unlinking. * * On UNIX, only close all the files on unlink and moving. There is * no need to do anything for a rename() operation. */ if (op != FO_OP_RENAME || is_running_on_mingw()) { GSList *sl; GM_SLIST_FOREACH(objects, sl) { struct file_object *fo = sl->data; fd_forget_and_close(&fo->fd); } }
static inline ssize_t unix_write(int fd, const void *buf, size_t size) { /* * On Windows, we have to call s_write() for sockets, not write(). * API fragmentation (winsocks versus other handles) at its best. * --RAM, 2011-01-05 */ if (is_running_on_mingw()) { ssize_t ret = s_write(fd, buf, size); if (ret >= 0 || ENOTSOCK != errno) return ret; /* FALL THROUGH -- fd is a plain file, not a socket */ } return write(fd, buf, size); }
static inline ssize_t unix_read(int fd, void *buf, size_t size) { /* * On Windows, we have to call s_read() for sockets, not read(), * or it does not work since winsock descriptors are distinct * from other file objects and the Windows kernel is too stupid * to do the dirty work for us. * --RAM, 2011-01-05 */ if (is_running_on_mingw()) { ssize_t ret = s_read(fd, buf, size); if (ret >= 0 || ENOTSOCK != errno) return ret; /* FALL THROUGH -- fd is a plain file, not a socket */ } return read(fd, buf, size); }
static inline int is_okay_for_select(int fd) { return is_valid_fd(fd) && (is_running_on_mingw() || UNSIGNED(fd) < FD_SETSIZE); }
/** * Collect information from file system. */ static void entropy_collect_filesystem(SHA1Context *ctx) { const char *path[RANDOM_SHUFFLE_MAX]; size_t i; i = 0; path[i++] = gethomedir(); path[i++] = "."; path[i++] = ".."; path[i++] = "/"; g_assert(i <= G_N_ELEMENTS(path)); entropy_array_stat_collect(ctx, path, i); i = 0; if (is_running_on_mingw()) { path[i++] = "C:/"; path[i++] = mingw_get_admin_tools_path(); path[i++] = mingw_get_common_appdata_path(); path[i++] = mingw_get_common_docs_path(); path[i++] = mingw_get_cookies_path(); path[i++] = mingw_get_fonts_path(); path[i++] = mingw_get_history_path(); g_assert(i <= G_N_ELEMENTS(path)); entropy_array_stat_collect(ctx, path, i); i = 0; path[i++] = mingw_get_home_path(); path[i++] = mingw_get_internet_cache_path(); path[i++] = mingw_get_mypictures_path(); path[i++] = mingw_get_personal_path(); path[i++] = mingw_get_program_files_path(); path[i++] = mingw_get_startup_path(); path[i++] = mingw_get_system_path(); path[i++] = mingw_get_windows_path(); g_assert(i <= G_N_ELEMENTS(path)); entropy_array_stat_collect(ctx, path, i); } else { path[i++] = "/bin"; path[i++] = "/boot"; path[i++] = "/dev"; path[i++] = "/etc"; path[i++] = "/home"; path[i++] = "/lib"; path[i++] = "/mnt"; path[i++] = "/opt"; g_assert(i <= G_N_ELEMENTS(path)); entropy_array_stat_collect(ctx, path, i); i = 0; path[i++] = "/proc"; path[i++] = "/root"; path[i++] = "/sbin"; path[i++] = "/sys"; path[i++] = "/tmp"; path[i++] = "/usr"; path[i++] = "/var"; g_assert(i <= G_N_ELEMENTS(path)); entropy_array_stat_collect(ctx, path, i); } }
/** * Initialize the bfd context. * * @return TRUE if OK. */ static bool bfd_util_open(bfd_ctx_t *bc, const char *path) { static mutex_t bfd_library_mtx = MUTEX_INIT; bfd *b; void *symbols = NULL; unsigned size = 0; long count; int fd = -1; const char *libpath = path; /* * On Debian systems, there is a debugging version of libraries held * under /usr/lib/debug. We'll get better symbol resolution by * opening these instead of the usually stripped runtime versions * that will only contain externally visible symbols. */ if (!is_running_on_mingw() && is_absolute_path(path)) { static char debugpath[MAX_PATH_LEN]; const char *base = filepath_basename(path); concat_strings(debugpath, sizeof debugpath, "/usr/lib/debug/", base, NULL_PTR); fd = open(debugpath, O_RDONLY); if (-1 == fd) { concat_strings(debugpath, sizeof debugpath, "/usr/lib/debug", path, NULL_PTR); fd = open(debugpath, O_RDONLY); } if (-1 != fd) libpath = debugpath; } if (-1 == fd) fd = open(libpath, O_RDONLY); if (-1 == fd) { s_miniwarn("%s: can't open %s: %m", G_STRFUNC, libpath); return FALSE; } /* * Protect calls to BFD opening: they don't appear to be fully * thread-safe and we could enter here concurrently. */ mutex_lock_fast(&bfd_library_mtx); b = bfd_fdopenr(libpath, NULL, fd); if (NULL == b) { mutex_unlock_fast(&bfd_library_mtx); close(fd); return FALSE; } if (!bfd_util_check_format(b, bfd_object, libpath)) { s_miniwarn("%s: %s is not an object", G_STRFUNC, libpath); goto failed; } if (0 == (bfd_get_file_flags(b) & HAS_SYMS)) { s_miniwarn("%s: %s has no symbols", G_STRFUNC, libpath); goto failed; } count = bfd_read_minisymbols(b, FALSE, &symbols, &size); if (count <= 0) { bc->dynamic = TRUE; count = bfd_read_minisymbols(b, TRUE, &symbols, &size); } if (count >= 0) goto done; s_miniwarn("%s: unable to load symbols from %s ", G_STRFUNC, libpath); symbols = NULL; /* FALL THROUGH */ /* * We keep the context on errors to avoid logging them over and over * each time we attempt to access the same file. The BFD and system * resources are released though. */ failed: bfd_close(b); b = NULL; count = 0; /* FALL THROUGH */ done: mutex_unlock_fast(&bfd_library_mtx); bc->magic = BFD_CTX_MAGIC; bc->handle = b; bc->symbols = symbols; /* Allocated by the bfd library */ bc->count = count; bc->symsize = size; mutex_init(&bc->lock); return TRUE; }
/** * Open configuration file, renaming it as ".orig" when `renaming' is TRUE. * If configuration file cannot be found, try opening the ".orig" variant * if already present and `renaming' is TRUE. * If not found, try with successive alternatives, if supplied. * * @attention * NB: the supplied `fv' argument is a vector of `fvcnt' elements. Items * with a NULL `dir' field are ignored, but fv[0].dir cannot be NULL. * * @param what is what is being opened, for logging purposes. * @param fv is a vector of files to try to open, in sequence * @param fvcnt is the size of the vector * @param renaming indicates whether the opened file should be renamed .orig. * @param chosen is filled with the index of the chosen path in the vector, * unless NULL is given. * * @return opened FILE, or NULL if we were unable to open any. `chosen' is * only filled if the file is opened. */ static FILE * open_read( const char *what, const file_path_t *fv, int fvcnt, bool renaming, int *chosen) { FILE *in; char *path; char *path_orig; const char *instead = empty_str; int idx = 0; g_assert(fv != NULL); g_assert(fvcnt >= 1); g_assert(fv->dir != NULL); path = make_pathname(fv->dir, fv->name); if (!is_absolute_path(path)) { HFREE_NULL(path); return NULL; } path_orig = h_strdup_printf("%s.%s", path, orig_ext); in = fopen(path, "r"); if (in) { if (is_running_on_mingw()) { /* Windows can't rename an open file */ fclose(in); in = NULL; } if (renaming && -1 == rename(path, path_orig)) { s_warning("[%s] could not rename \"%s\" as \"%s\": %m", what, path, path_orig); } if (NULL == in) { in = fopen(path_orig, "r"); } goto out; } else { if (ENOENT == errno) { if (common_dbg > 0) { g_debug("[%s] cannot load non-existent \"%s\"", what, path); } } else { instead = instead_str; /* Regular file was present */ s_warning("[%s] failed to retrieve from \"%s\": %m", what, path); } if (fvcnt > 1 && common_dbg > 0) g_debug("[%s] trying to load from alternate locations...", what); } /* * Maybe we crashed after having retrieved the file in a previous run * but before being able to write it again correctly? Try to open the * ".orig" file instead. */ g_assert(in == NULL); if (renaming) in = fopen(path_orig, "r"); /* The ".orig", in case of a crash */ if (in != NULL) { instead = instead_str; HFREE_NULL(path); path = path_orig; path_orig = NULL; } /* * Try with alternatives, if supplied. */ if (in == NULL && fvcnt > 1) { const file_path_t *xfv; int xfvcnt; instead = instead_str; for (xfv = fv + 1, xfvcnt = fvcnt - 1; xfvcnt; xfv++, xfvcnt--) { HFREE_NULL(path); if (NULL == xfv->dir) /* In alternatives, dir may be NULL */ continue; path = make_pathname(xfv->dir, xfv->name); idx++; if (NULL != path && NULL != (in = fopen(path, "r"))) break; if (path != NULL && common_dbg > 0) { g_debug("[%s] cannot load non-existent \"%s\" either", what, path); } } } if (common_dbg > 0) { if (in) { g_debug("[%s] retrieving from \"%s\"%s", what, path, instead); } else if (instead == instead_str) { g_debug("[%s] unable to retrieve: tried %d alternate location%s", what, fvcnt, fvcnt == 1 ? "" : "s"); } else { g_debug("[%s] unable to retrieve: no alternate locations known", what); } } out: HFREE_NULL(path); HFREE_NULL(path_orig); if (in != NULL && chosen != NULL) *chosen = idx; return in; }
/** * Creates a valid and sanitized filename from the supplied string. For most * Unix-like platforms anything goes but for security reasons, shell meta * characters are replaced by harmless characters. * * @param filename the suggested filename. * @param no_spaces if TRUE, spaces are replaced with underscores. * @param no_evil if TRUE, "evil" characters are replaced with underscores. * * @returns a newly allocated string using halloc() or ``filename'' * if it was a valid filename already. */ char * filename_sanitize(const char *filename, bool no_spaces, bool no_evil) { const char *p; const char *s; char *q; g_assert(filename); /* Almost all evil characters are forbidden on Windows, anyway */ no_evil |= is_running_on_mingw(); /* Leading spaces are just confusing */ p = skip_ascii_spaces(filename); /* Make sure the filename isn't too long */ if (strlen(p) >= FILENAME_MAXBYTES) { q = halloc(FILENAME_MAXBYTES); filename_shrink(p, q, FILENAME_MAXBYTES); s = q; } else { s = p; q = NULL; } /* * Replace shell meta characters and likely problematic characters. * * Although parentheses are not evil per se, they make it a pain to * copy-n-paste filenames without going through the shell's auto- * completion (which normally does the necessary escaping). * * To keep things "readable", we replace parentheses with brackets. * Although brackets are meaningful for the shells, they are only * interpreted in the presence of "*" or "?", two characters that we * strip already. * --RAM, 2013-11-03 */ { size_t i; uchar c; for (i = 0; '\0' != (c = s[i]); i++) { if ( c < 32 || is_ascii_cntrl(c) || G_DIR_SEPARATOR == c || '/' == c || (0 == i && ('.' == c || '-' == c)) || (no_spaces && is_ascii_space(c)) || (no_evil && filename_is_evil_char(c)) ) { if (!q) q = h_strdup(s); q[i] = '_'; /* replace undesired char with underscore */ } else if ('(' == c) { if (!q) q = h_strdup(s); q[i] = '['; } else if (')' == c) { if (!q) q = h_strdup(s); q[i] = ']'; } } /** * Windows does not like filenames ending with a space or period. */ while (i-- > 0 && (is_ascii_space(s[i]) || '.' == s[i])) { if (!q) q = h_strdup(s); q[i] = '\0'; /* truncate string */ } } if (filename_is_reserved(q ? q : s)) { HFREE_NULL(q); q = h_strdup("noname"); } if (NULL == q && s != filename) q = h_strdup(s); /* Trimmed leading white space, must copy */ return q ? q : deconstify_gchar(s); }
/** * Search executable within the user's PATH. * * @return full path if found, NULL otherwise. * The returned string is allocated with halloc(). */ char * file_locate_from_path(const char *argv0) { static bool already_done; char *path; char *tok; char filepath[MAX_PATH_LEN + 1]; char *result = NULL; char *ext = ""; if (is_running_on_mingw() && !is_strsuffix(argv0, (size_t) -1, ".exe")) { ext = ".exe"; } if (filepath_basename(argv0) != argv0) { if (!already_done) { s_warning("can't locate \"%s\" in PATH: name contains '%c' already", argv0, strchr(argv0, G_DIR_SEPARATOR) != NULL ? G_DIR_SEPARATOR : '/'); } result = h_strdup(argv0); goto done; } path = getenv("PATH"); if (NULL == path) { if (!already_done) { s_warning("can't locate \"%s\" in PATH: " "no such environment variable", argv0); } goto done; } /* * On Windows, we need to implicitly add "." to the path if not already * present -- this is done by appending a separator and a dot, not by * checking whether "." is already part of the path. * * The reason is that "." is implied, and also because one may omit the * ".exe" extension when launching a program. This means checks done * in crash_init() for instance to see whether the file listed in * argv[0] exists and which do not account for a missing ".exe" will * attempt to locate the program in the PATH to get a full name and will * fail if we do not add ".". * * On UNIX this cannot happen because there is no hidden extension and * the "." is never made part of the PATH implictly. * * --RAM, 2015-12-06 */ if (is_running_on_mingw()) path = h_strdup_printf("%s%c.", path, *G_SEARCHPATH_SEPARATOR_S); else path = h_strdup(path); path = h_strdup(path); /* FIXME: strtok() is not thread-safe --RAM, 2015-12-06 */ tok = strtok(path, G_SEARCHPATH_SEPARATOR_S); while (NULL != tok) { const char *dir = tok; filestat_t buf; if ('\0' == *dir) dir = "."; concat_strings(filepath, sizeof filepath, dir, G_DIR_SEPARATOR_S, argv0, ext, NULL_PTR); if (-1 != stat(filepath, &buf)) { if (S_ISREG(buf.st_mode) && -1 != access(filepath, X_OK)) { result = h_strdup(filepath); break; } } tok = strtok(NULL, G_SEARCHPATH_SEPARATOR_S); } hfree(path); done: already_done = TRUE; /* No warning on subsequent invocation */ return result; }
/** * Closes all file descriptors greater or equal to ``first_fd'', skipping * preserved ones if ``preserve'' is TRUE. */ static void fd_close_from_internal(const int first_fd, bool preserve) { int fd; g_return_if_fail(first_fd >= 0); if (!preserve && try_close_from(first_fd)) return; fd = getdtablesize() - 1; while (fd >= first_fd) { if (preserve && hset_contains(fd_preserved, int_to_pointer(fd))) goto next; #ifdef HAVE_GTKOSXAPPLICATION /* OS X doesn't allow fds being closed not opened by us. During * GUI initialisation a new kqueue fd is created for UI events. This * is visible to us as a fifo which we are not allowed to close. * Set close on exec on all fifo's so we won't leak any of our other * fifo's * -- JA 2011-11-28 */ if (is_a_fifo(fd)) fd_set_close_on_exec(fd); else #endif /* OS X frowns upon random fds being closed --RAM 2011-11-13 */ if (fd_is_opened(fd)) { if (close(fd)) { #if defined(F_MAXFD) fd = fcntl(0, F_MAXFD); continue; #endif /* F_MAXFD */ } } next: fd--; } /* * When called with a first_fd of 3, and we are on Windows, also make * sure we close all the known sockets we have. This lets the process * safely auto-restart, avoiding multiple listening sockets on the same * port. * --RAM, 2015-04-05 */ if ( is_running_on_mingw() && !preserve && 3 == first_fd && NULL != fd_sockets ) { hset_t *fds = fd_sockets; /* * We're about to exec() another process, and we may be crashing, * hence do not bother using hset_foreach_remove() to ensure minimal * processing. We also reset the fd_sockets pointer to NULL to * make sure s_close() will do nothing when fd_notify_socket_closed() * is called. */ fd_sockets = NULL; /* We don't expect race conditions here */ hset_foreach(fds, fd_socket_close, NULL); /* Don't bother freeing / clearing set, we're about to exec() */ } }