void make_pipe(const wchar_t *test_path) { wcstring vars_path = test_path ? wcstring(test_path) : default_vars_path(); vars_path.append(L".notifier"); const std::string narrow_path = wcs2string(vars_path); int fd = wopen_cloexec(vars_path, O_RDWR | O_NONBLOCK, 0600); if (fd < 0 && errno == ENOENT) { /* File doesn't exist, try creating it */ if (mkfifo(narrow_path.c_str(), 0600) >= 0) { fd = wopen_cloexec(vars_path, O_RDWR | O_NONBLOCK, 0600); } } if (fd < 0) { // Maybe open failed, maybe mkfifo failed int err = errno; report_error(err, L"Unable to make or open a FIFO for universal variables with path '%ls'", vars_path.c_str()); } else { pipe_fd = fd; } }
// Do a private, read-only map of the entirety of a history file with the given name. Returns true if successful. Returns the mapped memory region by reference. static bool map_file(const wcstring &name, const char **out_map_start, size_t *out_map_len) { bool result = false; wcstring filename = history_filename(name, L""); if (! filename.empty()) { int fd; if((fd = wopen_cloexec(filename, O_RDONLY)) > 0) { off_t len = lseek( fd, 0, SEEK_END ); if(len != (off_t)-1) { size_t mmap_length = (size_t)len; if(lseek(fd, 0, SEEK_SET) == 0) { char *mmap_start; if ((mmap_start = (char *)mmap(0, mmap_length, PROT_READ, MAP_PRIVATE, fd, 0)) != MAP_FAILED) { result = true; *out_map_start = mmap_start; *out_map_len = mmap_length; } } } close( fd ); } } return result; }
FILE *wfopen(const wcstring &path, const char *mode) { int permissions = 0, options = 0; size_t idx = 0; switch (mode[idx++]) { case 'r': { permissions = O_RDONLY; break; } case 'w': { permissions = O_WRONLY; options = O_CREAT | O_TRUNC; break; } case 'a': { permissions = O_WRONLY; options = O_CREAT | O_APPEND; break; } default: { errno = EINVAL; return NULL; } } // Skip binary. if (mode[idx] == 'b') idx++; // Consider append option. if (mode[idx] == '+') permissions = O_RDWR; int fd = wopen_cloexec(path, permissions | options, 0666); if (fd < 0) return NULL; FILE *result = fdopen(fd, mode); if (result == NULL) close(fd); return result; }
bool env_universal_t::open_temporary_file(const wcstring &directory, wcstring *out_path, int *out_fd) { /* Create and open a temporary file for writing within the given directory */ /* Try to create a temporary file, up to 10 times. We don't use mkstemps because we want to open it CLO_EXEC. This should almost always succeed on the first try. */ assert(! string_suffixes_string(L"/", directory)); bool success = false; const wcstring tmp_name_template = directory + L"/fishd.tmp.XXXXXX"; wcstring tmp_name; for (size_t attempt = 0; attempt < 10 && ! success; attempt++) { int result_fd = -1; char *narrow_str = wcs2str(tmp_name_template.c_str()); #if HAVE_MKOSTEMP result_fd = mkostemp(narrow_str, O_CLOEXEC); if (result_fd >= 0) { tmp_name = str2wcstring(narrow_str); } #else if (mktemp(narrow_str)) { /* It was successfully templated; try opening it atomically */ tmp_name = str2wcstring(narrow_str); result_fd = wopen_cloexec(tmp_name, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, 0644); } #endif if (result_fd >= 0) { /* Success */ *out_fd = result_fd; *out_path = str2wcstring(narrow_str); success = true; } free(narrow_str); } if (! success) { int err = errno; report_error(err, L"Unable to open file '%ls'", tmp_name.c_str()); } return success; }
bool env_universal_t::load_from_path(const wcstring &path, callback_data_list_t *callbacks) { ASSERT_IS_LOCKED(lock); /* Check to see if the file is unchanged. We do this again in load_from_fd, but this avoids opening the file unnecessarily. */ if (last_read_file != kInvalidFileID && file_id_for_path(path) == last_read_file) { UNIVERSAL_LOG("Sync elided based on fast stat()"); return true; } bool result = false; int fd = wopen_cloexec(path, O_RDONLY); if (fd >= 0) { UNIVERSAL_LOG("Reading from file"); this->load_from_fd(fd, callbacks); close(fd); result = true; } return result; }
/// The source builtin, sometimes called `.`. Evaluates the contents of a file in the current /// context. int builtin_source(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); help_only_cmd_opts_t opts; int optind; int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; if (opts.print_help) { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } int fd; struct stat buf; const wchar_t *fn, *fn_intern; if (argc == optind || wcscmp(argv[optind], L"-") == 0) { // Either a bare `source` which means to implicitly read from stdin or an explicit `-`. if (argc == optind && !streams.stdin_is_directly_redirected) { // Don't implicitly read from the terminal. return STATUS_CMD_ERROR; } fn = L"-"; fn_intern = fn; fd = dup(streams.stdin_fd); } else { if ((fd = wopen_cloexec(argv[optind], O_RDONLY)) == -1) { streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), cmd, argv[optind]); builtin_wperror(cmd, streams); return STATUS_CMD_ERROR; } if (fstat(fd, &buf) == -1) { close(fd); streams.err.append_format(_(L"%ls: Error encountered while sourcing file '%ls':\n"), cmd, argv[optind]); builtin_wperror(L"source", streams); return STATUS_CMD_ERROR; } if (!S_ISREG(buf.st_mode)) { close(fd); streams.err.append_format(_(L"%ls: '%ls' is not a file\n"), cmd, argv[optind]); return STATUS_CMD_ERROR; } fn_intern = intern(argv[optind]); } const source_block_t *sb = parser.push_block<source_block_t>(fn_intern); reader_push_current_filename(fn_intern); // This is slightly subtle. If this is a bare `source` with no args then `argv + optind` already // points to the end of argv. Otherwise we want to skip the file name to get to the args if any. env_set_argv(argv + optind + (argc == optind ? 0 : 1)); retval = reader_read(fd, streams.io_chain ? *streams.io_chain : io_chain_t()); parser.pop_block(sb); if (retval != STATUS_CMD_OK) { streams.err.append_format(_(L"%ls: Error while reading file '%ls'\n"), cmd, fn_intern == intern_static(L"-") ? L"<stdin>" : fn_intern); } else { retval = proc_get_last_status(); } // Do not close fd after calling reader_read. reader_read automatically closes it before calling // eval. reader_pop_current_filename(); return retval; }
bool env_universal_t::open_and_acquire_lock(const wcstring &path, int *out_fd) { /* Attempt to open the file for reading at the given path, atomically acquiring a lock. On BSD, we can use O_EXLOCK. On Linux, we open the file, take a lock, and then compare fstat() to stat(); if they match, it means that the file was not replaced before we acquired the lock. We pass O_RDONLY with O_CREAT; this creates a potentially empty file. We do this so that we have something to lock on. */ int result_fd = -1; bool needs_lock = true; int flags = O_RDONLY | O_CREAT; #ifdef O_EXLOCK flags |= O_EXLOCK; needs_lock = false; #endif for (;;) { int fd = wopen_cloexec(path, flags, 0644); if (fd < 0) { int err = errno; if (err == EINTR) { /* Signal; try again */ continue; } #ifdef O_EXLOCK else if (err == EOPNOTSUPP) { /* Filesystem probably does not support locking. Clear the flag and try again. Note that we try taking the lock via flock anyways. */ flags &= ~O_EXLOCK; needs_lock = true; continue; } #endif else { report_error(err, L"Unable to open universal variable file '%ls'", path.c_str()); break; } } /* If we get here, we must have a valid fd */ assert(fd >= 0); /* Try taking the lock, if necessary. If we failed, we may be on lockless NFS, etc.; in that case we pretend we succeeded. See the comment in save_to_path for the rationale. */ if (needs_lock) { while (flock(fd, LOCK_EX) < 0) { /* error */ if (errno != EINTR) { int err = errno; report_error(err, L"Unable to lock universal variable file '%ls'", path.c_str()); break; } } } /* Hopefully we got the lock. However, it's possible the file changed out from under us while we were waiting for the lock. Make sure that didn't happen. */ if (file_id_for_fd(fd) != file_id_for_path(path)) { /* Oops, it changed! Try again */ close(fd); continue; } /* Finally, we have an fd that's valid and hopefully locked. We're done */ assert(fd >= 0); result_fd = fd; break; } *out_fd = result_fd; return result_fd >= 0; }