Commit 1e2e0f7e authored by Nick Mathewson's avatar Nick Mathewson 🎨
Browse files

Extract functions from compat.c and util.h into a new fs library

parent 3246c114
......@@ -175,6 +175,8 @@ uptime-*.json
/src/lib/libtor-err-testing.a
/src/lib/libtor-fdio.a
/src/lib/libtor-fdio-testing.a
/src/lib/libtor-fs.a
/src/lib/libtor-fs-testing.a
/src/lib/libtor-intmath.a
/src/lib/libtor-intmath-testing.a
/src/lib/libtor-lock.a
......
......@@ -40,6 +40,7 @@ endif
# "Common" libraries used to link tor's utility code.
TOR_UTIL_LIBS = \
src/common/libor.a \
src/lib/libtor-fs.a \
src/lib/libtor-sandbox.a \
src/lib/libtor-net.a \
src/lib/libtor-log.a \
......@@ -57,6 +58,7 @@ TOR_UTIL_LIBS = \
# and tests)
TOR_UTIL_TESTING_LIBS = \
src/common/libor-testing.a \
src/lib/libtor-fs-testing.a \
src/lib/libtor-sandbox-testing.a \
src/lib/libtor-net-testing.a \
src/lib/libtor-log-testing.a \
......
......@@ -131,267 +131,6 @@ SecureZeroMemory(PVOID ptr, SIZE_T cnt)
#include "lib/net/address.h"
#include "lib/sandbox/sandbox.h"
/** As open(path, flags, mode), but return an fd with the close-on-exec mode
* set. */
int
tor_open_cloexec(const char *path, int flags, unsigned mode)
{
int fd;
const char *p = sandbox_intern_string(path);
#ifdef O_CLOEXEC
fd = open(p, flags|O_CLOEXEC, mode);
if (fd >= 0)
return fd;
/* If we got an error, see if it is EINVAL. EINVAL might indicate that,
* even though we were built on a system with O_CLOEXEC support, we
* are running on one without. */
if (errno != EINVAL)
return -1;
#endif /* defined(O_CLOEXEC) */
log_debug(LD_FS, "Opening %s with flags %x", p, flags);
fd = open(p, flags, mode);
#ifdef FD_CLOEXEC
if (fd >= 0) {
if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno));
close(fd);
return -1;
}
}
#endif /* defined(FD_CLOEXEC) */
return fd;
}
/** As fopen(path,mode), but ensures that the O_CLOEXEC bit is set on the
* underlying file handle. */
FILE *
tor_fopen_cloexec(const char *path, const char *mode)
{
FILE *result = fopen(path, mode);
#ifdef FD_CLOEXEC
if (result != NULL) {
if (fcntl(fileno(result), F_SETFD, FD_CLOEXEC) == -1) {
log_warn(LD_FS,"Couldn't set FD_CLOEXEC: %s", strerror(errno));
fclose(result);
return NULL;
}
}
#endif /* defined(FD_CLOEXEC) */
return result;
}
/** As rename(), but work correctly with the sandbox. */
int
tor_rename(const char *path_old, const char *path_new)
{
log_debug(LD_FS, "Renaming %s to %s", path_old, path_new);
return rename(sandbox_intern_string(path_old),
sandbox_intern_string(path_new));
}
#if defined(HAVE_MMAP) || defined(RUNNING_DOXYGEN)
/** Try to create a memory mapping for <b>filename</b> and return it. On
* failure, return NULL. Sets errno properly, using ERANGE to mean
* "empty file". Must only be called on trusted Tor-owned files, as changing
* the underlying file's size causes unspecified behavior. */
tor_mmap_t *
tor_mmap_file(const char *filename)
{
int fd; /* router file */
char *string;
int result;
tor_mmap_t *res;
size_t size, filesize;
struct stat st;
tor_assert(filename);
fd = tor_open_cloexec(filename, O_RDONLY, 0);
if (fd<0) {
int save_errno = errno;
int severity = (errno == ENOENT) ? LOG_INFO : LOG_WARN;
log_fn(severity, LD_FS,"Could not open \"%s\" for mmap(): %s",filename,
strerror(errno));
errno = save_errno;
return NULL;
}
/* Get the size of the file */
result = fstat(fd, &st);
if (result != 0) {
int save_errno = errno;
log_warn(LD_FS,
"Couldn't fstat opened descriptor for \"%s\" during mmap: %s",
filename, strerror(errno));
close(fd);
errno = save_errno;
return NULL;
}
size = filesize = (size_t)(st.st_size);
if (st.st_size > SSIZE_T_CEILING || (off_t)size < st.st_size) {
log_warn(LD_FS, "File \"%s\" is too large. Ignoring.",filename);
errno = EFBIG;
close(fd);
return NULL;
}
if (!size) {
/* Zero-length file. If we call mmap on it, it will succeed but
* return NULL, and bad things will happen. So just fail. */
log_info(LD_FS,"File \"%s\" is empty. Ignoring.",filename);
errno = ERANGE;
close(fd);
return NULL;
}
string = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (string == MAP_FAILED) {
int save_errno = errno;
log_warn(LD_FS,"Could not mmap file \"%s\": %s", filename,
strerror(errno));
errno = save_errno;
return NULL;
}
res = tor_malloc_zero(sizeof(tor_mmap_t));
res->data = string;
res->size = filesize;
res->mapping_size = size;
return res;
}
/** Release storage held for a memory mapping; returns 0 on success,
* or -1 on failure (and logs a warning). */
int
tor_munmap_file(tor_mmap_t *handle)
{
int res;
if (handle == NULL)
return 0;
res = munmap((char*)handle->data, handle->mapping_size);
if (res == 0) {
/* munmap() succeeded */
tor_free(handle);
} else {
log_warn(LD_FS, "Failed to munmap() in tor_munmap_file(): %s",
strerror(errno));
res = -1;
}
return res;
}
#elif defined(_WIN32)
tor_mmap_t *
tor_mmap_file(const char *filename)
{
TCHAR tfilename[MAX_PATH]= {0};
tor_mmap_t *res = tor_malloc_zero(sizeof(tor_mmap_t));
int empty = 0;
HANDLE file_handle = INVALID_HANDLE_VALUE;
DWORD size_low, size_high;
uint64_t real_size;
res->mmap_handle = NULL;
#ifdef UNICODE
mbstowcs(tfilename,filename,MAX_PATH);
#else
strlcpy(tfilename,filename,MAX_PATH);
#endif
file_handle = CreateFile(tfilename,
GENERIC_READ, FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (file_handle == INVALID_HANDLE_VALUE)
goto win_err;
size_low = GetFileSize(file_handle, &size_high);
if (size_low == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) {
log_warn(LD_FS,"Error getting size of \"%s\".",filename);
goto win_err;
}
if (size_low == 0 && size_high == 0) {
log_info(LD_FS,"File \"%s\" is empty. Ignoring.",filename);
empty = 1;
goto err;
}
real_size = (((uint64_t)size_high)<<32) | size_low;
if (real_size > SIZE_MAX) {
log_warn(LD_FS,"File \"%s\" is too big to map; not trying.",filename);
goto err;
}
res->size = real_size;
res->mmap_handle = CreateFileMapping(file_handle,
NULL,
PAGE_READONLY,
size_high,
size_low,
NULL);
if (res->mmap_handle == NULL)
goto win_err;
res->data = (char*) MapViewOfFile(res->mmap_handle,
FILE_MAP_READ,
0, 0, 0);
if (!res->data)
goto win_err;
CloseHandle(file_handle);
return res;
win_err: {
DWORD e = GetLastError();
int severity = (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND) ?
LOG_INFO : LOG_WARN;
char *msg = format_win32_error(e);
log_fn(severity, LD_FS, "Couldn't mmap file \"%s\": %s", filename, msg);
tor_free(msg);
if (e == ERROR_FILE_NOT_FOUND || e == ERROR_PATH_NOT_FOUND)
errno = ENOENT;
else
errno = EINVAL;
}
err:
if (empty)
errno = ERANGE;
if (file_handle != INVALID_HANDLE_VALUE)
CloseHandle(file_handle);
tor_munmap_file(res);
return NULL;
}
/* Unmap the file, and return 0 for success or -1 for failure */
int
tor_munmap_file(tor_mmap_t *handle)
{
if (handle == NULL)
return 0;
if (handle->data) {
/* This is an ugly cast, but without it, "data" in struct tor_mmap_t would
have to be redefined as non-const. */
BOOL ok = UnmapViewOfFile( (LPVOID) handle->data);
if (!ok) {
log_warn(LD_FS, "Failed to UnmapViewOfFile() in tor_munmap_file(): %d",
(int)GetLastError());
}
}
if (handle->mmap_handle != NULL)
CloseHandle(handle->mmap_handle);
tor_free(handle);
return 0;
}
#else
#error "cannot implement tor_mmap_file"
#endif /* defined(HAVE_MMAP) || ... || ... */
/** Given <b>hlen</b> bytes at <b>haystack</b> and <b>nlen</b> bytes at
* <b>needle</b>, return a pointer to the first occurrence of the needle
* within the haystack, or NULL if there is no such occurrence.
......@@ -553,45 +292,6 @@ set_uint64(void *cp, uint64_t v)
memcpy(cp,&v,8);
}
/**
* Rename the file <b>from</b> to the file <b>to</b>. On Unix, this is
* the same as rename(2). On windows, this removes <b>to</b> first if
* it already exists.
* Returns 0 on success. Returns -1 and sets errno on failure.
*/
int
replace_file(const char *from, const char *to)
{
#ifndef _WIN32
return tor_rename(from, to);
#else
switch (file_status(to))
{
case FN_NOENT:
break;
case FN_FILE:
case FN_EMPTY:
if (unlink(to)) return -1;
break;
case FN_ERROR:
return -1;
case FN_DIR:
errno = EISDIR;
return -1;
}
return tor_rename(from,to);
#endif /* !defined(_WIN32) */
}
/** Change <b>fname</b>'s modification time to now. */
int
touch_file(const char *fname)
{
if (utime(fname, NULL)!=0)
return -1;
return 0;
}
/** Represents a lockfile on which we hold the lock. */
struct tor_lockfile_t {
/** Name of the file */
......@@ -928,109 +628,6 @@ log_credential_status(void)
}
#endif /* !defined(_WIN32) */
#ifndef _WIN32
/** Cached struct from the last getpwname() call we did successfully. */
static struct passwd *passwd_cached = NULL;
/** Helper: copy a struct passwd object.
*
* We only copy the fields pw_uid, pw_gid, pw_name, pw_dir. Tor doesn't use
* any others, and I don't want to run into incompatibilities.
*/
static struct passwd *
tor_passwd_dup(const struct passwd *pw)
{
struct passwd *new_pw = tor_malloc_zero(sizeof(struct passwd));
if (pw->pw_name)
new_pw->pw_name = tor_strdup(pw->pw_name);
if (pw->pw_dir)
new_pw->pw_dir = tor_strdup(pw->pw_dir);
new_pw->pw_uid = pw->pw_uid;
new_pw->pw_gid = pw->pw_gid;
return new_pw;
}
#define tor_passwd_free(pw) \
FREE_AND_NULL(struct passwd, tor_passwd_free_, (pw))
/** Helper: free one of our cached 'struct passwd' values. */
static void
tor_passwd_free_(struct passwd *pw)
{
if (!pw)
return;
tor_free(pw->pw_name);
tor_free(pw->pw_dir);
tor_free(pw);
}
/** Wrapper around getpwnam() that caches result. Used so that we don't need
* to give the sandbox access to /etc/passwd.
*
* The following fields alone will definitely be copied in the output: pw_uid,
* pw_gid, pw_name, pw_dir. Other fields are not present in cached values.
*
* When called with a NULL argument, this function clears storage associated
* with static variables it uses.
**/
const struct passwd *
tor_getpwnam(const char *username)
{
struct passwd *pw;
if (username == NULL) {
tor_passwd_free(passwd_cached);
passwd_cached = NULL;
return NULL;
}
if ((pw = getpwnam(username))) {
tor_passwd_free(passwd_cached);
passwd_cached = tor_passwd_dup(pw);
log_info(LD_GENERAL, "Caching new entry %s for %s",
passwd_cached->pw_name, username);
return pw;
}
/* Lookup failed */
if (! passwd_cached || ! passwd_cached->pw_name)
return NULL;
if (! strcmp(username, passwd_cached->pw_name))
return passwd_cached; // LCOV_EXCL_LINE - would need to make getpwnam flaky
return NULL;
}
/** Wrapper around getpwnam() that can use cached result from
* tor_getpwnam(). Used so that we don't need to give the sandbox access to
* /etc/passwd.
*
* The following fields alone will definitely be copied in the output: pw_uid,
* pw_gid, pw_name, pw_dir. Other fields are not present in cached values.
*/
const struct passwd *
tor_getpwuid(uid_t uid)
{
struct passwd *pw;
if ((pw = getpwuid(uid))) {
return pw;
}
/* Lookup failed */
if (! passwd_cached)
return NULL;
if (uid == passwd_cached->pw_uid)
return passwd_cached; // LCOV_EXCL_LINE - would need to make getpwnam flaky
return NULL;
}
#endif /* !defined(_WIN32) */
/** Return true iff we were compiled with capability support, and capabilities
* seem to work. **/
int
......@@ -1322,159 +919,6 @@ tor_disable_debugger_attach(void)
return r;
}
#ifdef HAVE_PWD_H
/** Allocate and return a string containing the home directory for the
* user <b>username</b>. Only works on posix-like systems. */
char *
get_user_homedir(const char *username)
{
const struct passwd *pw;
tor_assert(username);
if (!(pw = tor_getpwnam(username))) {
log_err(LD_CONFIG,"User \"%s\" not found.", username);
return NULL;
}
return tor_strdup(pw->pw_dir);
}
#endif /* defined(HAVE_PWD_H) */
/** Modify <b>fname</b> to contain the name of its parent directory. Doesn't
* actually examine the filesystem; does a purely syntactic modification.
*
* The parent of the root director is considered to be iteself.
*
* Path separators are the forward slash (/) everywhere and additionally
* the backslash (\) on Win32.
*
* Cuts off any number of trailing path separators but otherwise ignores
* them for purposes of finding the parent directory.
*
* Returns 0 if a parent directory was successfully found, -1 otherwise (fname
* did not have any path separators or only had them at the end).
* */
int
get_parent_directory(char *fname)
{
char *cp;
int at_end = 1;
tor_assert(fname);
#ifdef _WIN32
/* If we start with, say, c:, then don't consider that the start of the path
*/
if (fname[0] && fname[1] == ':') {
fname += 2;
}
#endif /* defined(_WIN32) */
/* Now we want to remove all path-separators at the end of the string,
* and to remove the end of the string starting with the path separator
* before the last non-path-separator. In perl, this would be
* s#[/]*$##; s#/[^/]*$##;
* on a unixy platform.
*/
cp = fname + strlen(fname);
at_end = 1;
while (--cp >= fname) {
int is_sep = (*cp == '/'
#ifdef _WIN32
|| *cp == '\\'
#endif
);
if (is_sep) {
if (cp == fname) {
/* This is the first separator in the file name; don't remove it! */
cp[1] = '\0';
return 0;
}
*cp = '\0';
if (! at_end)
return 0;
} else {
at_end = 0;
}
}
return -1;
}
#ifndef _WIN32
/** Return a newly allocated string containing the output of getcwd(). Return
* NULL on failure. (We can't just use getcwd() into a PATH_MAX buffer, since
* Hurd hasn't got a PATH_MAX.)
*/
static char *
alloc_getcwd(void)
{
#ifdef HAVE_GET_CURRENT_DIR_NAME
/* Glibc makes this nice and simple for us. */
char *cwd = get_current_dir_name();
char *result = NULL;
if (cwd) {
/* We make a copy here, in case tor_malloc() is not malloc(). */
result = tor_strdup(cwd);
raw_free(cwd); // alias for free to avoid tripping check-spaces.
}
return result;
#else /* !(defined(HAVE_GET_CURRENT_DIR_NAME)) */
size_t size = 1024;
char *buf = NULL;
char *ptr = NULL;
while (ptr == NULL) {
buf = tor_realloc(buf, size);
ptr = getcwd(buf, size);
if (ptr == NULL && errno != ERANGE) {
tor_free(buf);
return NULL;
}
size *= 2;
}
return buf;
#endif /* defined(HAVE_GET_CURRENT_DIR_NAME) */
}
#endif /* !defined(_WIN32) */
/** Expand possibly relative path <b>fname</b> to an absolute path.
* Return a newly allocated string, possibly equal to <b>fname</b>. */
char *
make_path_absolute(char *fname)
{
#ifdef _WIN32
char *absfname_malloced = _fullpath(NULL, fname, 1);
/* We don't want to assume that tor_free can free a string allocated
* with malloc. On failure, return fname (it's better than nothing). */
char *absfname = tor_strdup(absfname_malloced ? absfname_malloced : fname);
if (absfname_malloced) raw_free(absfname_malloced);
return absfname;
#else /* !(defined(_WIN32)) */
char *absfname = NULL, *path = NULL;
tor_assert(fname);
if (fname[0] == '/') {
absfname = tor_strdup(fname);
} else {
path = alloc_getcwd();
if (path) {
tor_asprintf(&absfname, "%s/%s", path, fname);
tor_free(path);
} else {
/* LCOV_EXCL_START Can't make getcwd fail. */
/* If getcwd failed, the best we can do here is keep using the
* relative path. (Perhaps / isn't readable by this UID/GID.) */
log_warn(LD_GENERAL, "Unable to find current working directory: %s",
strerror(errno));
absfname = tor_strdup(fname);
/* LCOV_EXCL_STOP */
}
}
return absfname;
#endif /* defined(_WIN32) */
}
#ifndef HAVE__NSGETENVIRON
#ifndef HAVE_EXTERN_ENVIRON_DECLARED
/* Some platforms declare environ under some circumstances, others don't. */
......
......@@ -55,31 +55,13 @@
#include "lib/net/ipv4.h"
#include "lib/net/ipv6.h"
#include "lib/net/resolve.h"
#include "lib/fs/files.h"
#include "lib/fs/mmap.h"
#include "lib/fs/userdb.h"
#include <stdio.h>
#include <errno.h>
/* ===== Compiler compatibility */
/** Represents an mmaped file. Allocated via tor_mmap_file; freed with
* tor_munmap_file. */
typedef struct tor_mmap_t {
const char *data; /**< Mapping of the file's contents. */
size_t size; /**< Size of the file. */
/* None of the fields below should be accessed from outside compat.c */
#ifdef HAVE_MMAP