/*
* Copyright (c) 2014, 2015 Roy Keene
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#define FUSE_USE_VERSION 26
#include <sys/resource.h>
#include <sys/fsuid.h>
#include <sys/types.h>
#include <sys/time.h>
#include <pthread.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <fuse.h>
#include <pwd.h>
#include <tcl.h>
/*
* Default cache directory
*/
#ifndef APPFS_CACHEDIR
#define APPFS_CACHEDIR "/var/cache/appfs"
#endif
/* Debugging macros */
#ifdef DEBUG
int appfs_debug_fd = STDERR_FILENO;
#define APPFS_DEBUG(x...) { \
char buf[8192]; \
int bufoff = 0; \
if (appfs_debug_fd == -1) { \
appfs_debug_fd = open("/tmp/appfsd.log", O_WRONLY | O_APPEND | O_CREAT, 0600); \
}; \
bufoff = snprintf(buf, sizeof(buf), "[debug] [t=%llx] %s:%i:%s: ", (unsigned long long) pthread_self(), __FILE__, __LINE__, __func__); \
if (bufoff < sizeof(buf)) { \
bufoff += snprintf(buf + bufoff, sizeof(buf) - bufoff, x); \
}; \
if (bufoff < sizeof(buf)) { \
bufoff += snprintf(buf + bufoff, sizeof(buf) - bufoff, "\n");\
} \
if (bufoff > sizeof(buf)) { \
bufoff = sizeof(buf); \
}; \
write(appfs_debug_fd, buf, bufoff); \
}
#else
#define APPFS_DEBUG(x...) /**/
#endif
/*
* SHA1 Tcl Package initializer, from sha1.o
*/
int Sha1_Init(Tcl_Interp *interp);
/*
* Thread Specific Data (TSD) for Tcl Interpreter for the current thread
*/
static pthread_key_t interpKey;
/*
* Global variables, needed for all threads but only initialized before any
* FUSE threads are created
*/
const char *appfs_cachedir;
time_t appfs_boottime;
int appfs_fuse_started = 0;
int appfs_threaded_tcl;
/*
* Global variables for AppFS caching
*/
pthread_mutex_t appfs_path_info_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
int appfs_path_info_cache_size = 8209;
struct appfs_pathinfo *appfs_path_info_cache = NULL;
#ifndef TCL_THREADS
/*
* Handle unthreaded Tcl
*/
pthread_mutex_t appfs_tcl_big_global_lock = PTHREAD_MUTEX_INITIALIZER;
#define appfs_call_libtcl_enter pthread_mutex_lock(&appfs_tcl_big_global_lock);
#define appfs_call_libtcl_exit pthread_mutex_unlock(&appfs_tcl_big_global_lock);
#else
#define appfs_call_libtcl_enter /**/
#define appfs_call_libtcl_exit /**/
#endif
#define appfs_call_libtcl(x...) appfs_call_libtcl_enter x appfs_call_libtcl_exit
/*
* Global variables for AppFS Tcl Interpreter restarting
*/
int interp_reset_key = 0;
/*
* AppFS Path Type: Describes the type of path a given file is
*/
typedef enum {
APPFS_PATHTYPE_INVALID,
APPFS_PATHTYPE_DOES_NOT_EXIST,
APPFS_PATHTYPE_FILE,
APPFS_PATHTYPE_DIRECTORY,
APPFS_PATHTYPE_SYMLINK,
APPFS_PATHTYPE_SOCKET,
APPFS_PATHTYPE_FIFO,
} appfs_pathtype_t;
/*
* AppFS Path Information:
* Completely describes a specific path, how it should be returned to
* to the kernel
*/
struct appfs_pathinfo {
appfs_pathtype_t type;
time_t time;
char hostname[256];
int packaged;
unsigned long long inode;
union {
struct {
int childcount;
} dir;
struct {
int executable;
int suidRoot;
int worldaccessible;
off_t size;
} file;
struct {
off_t size;
char source[256];
} symlink;
} typeinfo;
/* Attributes used only for caching entries */
char *_cache_path;
uid_t _cache_uid;
};
/*
* Create a new Tcl interpreter and completely initialize it
*/
static Tcl_Interp *appfs_create_TclInterp(char **error_string) {
Tcl_Interp *interp;
int tcl_ret;
const char *tcl_setvar_ret;
APPFS_DEBUG("Creating new Tcl interpreter for TID = 0x%llx", (unsigned long long) pthread_self());
appfs_call_libtcl(
interp = Tcl_CreateInterp();
)
if (interp == NULL) {
fprintf(stderr, "Unable to create Tcl Interpreter. Aborting.\n");
if (error_string) {
*error_string = strdup("Unable to create Tcl interpreter.");
}
return(NULL);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
appfs_call_libtcl(
tcl_ret = Tcl_Init(interp);
)
if (tcl_ret != TCL_OK) {
fprintf(stderr, "Unable to initialize Tcl. Aborting.\n");
appfs_call_libtcl(
fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
)
if (error_string) {
appfs_call_libtcl(
*error_string = strdup(Tcl_GetStringResult(interp));
)
}
appfs_call_libtcl(Tcl_Release(interp);)
APPFS_DEBUG("Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
appfs_call_libtcl(
tcl_ret = Tcl_Eval(interp, "package ifneeded sha1 1.0 [list load {} sha1]");
)
if (tcl_ret != TCL_OK) {
fprintf(stderr, "Unable to initialize Tcl SHA1. Aborting.\n");
appfs_call_libtcl(
fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
)
if (error_string) {
appfs_call_libtcl(
*error_string = strdup(Tcl_GetStringResult(interp));
)
}
appfs_call_libtcl(Tcl_Release(interp);)
APPFS_DEBUG("Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
appfs_call_libtcl(
tcl_ret = Tcl_Eval(interp, "package ifneeded appfsd 1.0 [list load {} appfsd]");
)
if (tcl_ret != TCL_OK) {
fprintf(stderr, "Unable to initialize Tcl AppFS Package. Aborting.\n");
appfs_call_libtcl(
fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
)
if (error_string) {
appfs_call_libtcl(
*error_string = strdup(Tcl_GetStringResult(interp));
)
}
appfs_call_libtcl(Tcl_Release(interp);)
APPFS_DEBUG("Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
/*
* Load "pki.tcl" in the same way as appfsd.tcl (see below)
*/
appfs_call_libtcl_enter
tcl_ret = Tcl_Eval(interp, ""
#include "pki.tcl.h"
"");
appfs_call_libtcl_exit
if (tcl_ret != TCL_OK) {
fprintf(stderr, "Unable to initialize Tcl PKI. Aborting.\n");
appfs_call_libtcl(
fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
)
if (error_string) {
appfs_call_libtcl(
*error_string = strdup(Tcl_GetStringResult(interp));
)
}
appfs_call_libtcl(Tcl_Release(interp);)
APPFS_DEBUG("Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
/*
* Load the "appfsd.tcl" script, which is "compiled" into a C header
* so that it does not need to exist on the filesystem and can be
* directly evaluated.
*/
appfs_call_libtcl_enter
tcl_ret = Tcl_Eval(interp, ""
#include "appfsd.tcl.h"
"");
appfs_call_libtcl_exit
if (tcl_ret != TCL_OK) {
fprintf(stderr, "Unable to initialize Tcl AppFS script. Aborting.\n");
appfs_call_libtcl(
fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
)
if (error_string) {
appfs_call_libtcl(
*error_string = strdup(Tcl_GetStringResult(interp));
)
}
appfs_call_libtcl(Tcl_Release(interp);)
APPFS_DEBUG("Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
/*
* Set global variables from C to Tcl
*/
appfs_call_libtcl(
tcl_setvar_ret = Tcl_SetVar(interp, "::appfs::cachedir", appfs_cachedir, TCL_GLOBAL_ONLY);
)
if (tcl_setvar_ret == NULL) {
fprintf(stderr, "Unable to set cache directory. This should never fail.\n");
if (error_string) {
appfs_call_libtcl(
*error_string = strdup(Tcl_GetStringResult(interp));
)
}
appfs_call_libtcl(Tcl_Release(interp);)
APPFS_DEBUG("Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
/*
* Initialize the "appfsd.tcl" environment, which must be done after
* global variables are set.
*/
appfs_call_libtcl(
tcl_ret = Tcl_Eval(interp, "::appfs::init");
)
if (tcl_ret != TCL_OK) {
fprintf(stderr, "Unable to initialize Tcl AppFS script (::appfs::init). Aborting.\n");
appfs_call_libtcl(
fprintf(stderr, "Tcl Error is: %s\n", Tcl_GetStringResult(interp));
)
if (error_string) {
appfs_call_libtcl(
*error_string = strdup(Tcl_GetStringResult(interp));
)
}
appfs_call_libtcl(Tcl_Release(interp);)
APPFS_DEBUG("Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
/*
* Hide some Tcl commands that we do not care to use and which may
* slow down run-time operations.
*/
appfs_call_libtcl(
Tcl_HideCommand(interp, "auto_load_index", "auto_load_index");
Tcl_HideCommand(interp, "unknown", "unknown");
Tcl_HideCommand(interp, "exit", "exit");
)
/*
* Release the hold we have on the interpreter so that it may be
* deleted if needed
*/
appfs_call_libtcl(Tcl_Release(interp);)
/*
* Return the completely initialized interpreter
*/
return(interp);
}
/*
* Return the thread-specific Tcl interpreter, creating it if needed
*/
static Tcl_Interp *appfs_TclInterp(void) {
Tcl_Interp *interp;
int pthread_ret;
static __thread int thread_interp_reset_key = 0;
int global_interp_reset_key;
global_interp_reset_key = __sync_fetch_and_add(&interp_reset_key, 0);
interp = pthread_getspecific(interpKey);
if (interp != NULL && thread_interp_reset_key != global_interp_reset_key) {
APPFS_DEBUG("Terminating old interpreter and restarting due to reset request.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
interp = NULL;
pthread_ret = pthread_setspecific(interpKey, interp);
}
if (global_interp_reset_key == -1) {
APPFS_DEBUG("Returning NULL since we are in the process of terminating all threads.");
return(NULL);
}
thread_interp_reset_key = global_interp_reset_key;
if (interp == NULL) {
interp = appfs_create_TclInterp(NULL);
if (interp == NULL) {
APPFS_DEBUG("Create interp failed, returning in failure.");
return(NULL);
}
pthread_ret = pthread_setspecific(interpKey, interp);
if (pthread_ret != 0) {
APPFS_DEBUG("pthread_setspecific() failed. Terminating Tcl interpreter.");
appfs_call_libtcl(Tcl_DeleteInterp(interp);)
return(NULL);
}
}
return(interp);
}
/*
* Evaluate a Tcl script constructed by concatenating a bunch of C strings
* together.
*/
static int appfs_Tcl_Eval(Tcl_Interp *interp, int objc, const char *cmd, ...) {
Tcl_Obj **objv;
const char *arg;
va_list argp;
int retval;
int i;
if (interp == NULL) {
APPFS_DEBUG("Invalid interpreter passed in, returning in failure.");
return(TCL_ERROR);
}
objv = (void *) ckalloc(sizeof(*objv) * objc);
appfs_call_libtcl(
objv[0] = Tcl_NewStringObj(cmd, -1);
Tcl_IncrRefCount(objv[0]);
va_start(argp, cmd);
for (i = 1; i < objc; i++) {
arg = va_arg(argp, const char *);
objv[i] = Tcl_NewStringObj(arg, -1);
Tcl_IncrRefCount(objv[i]);
}
va_end(argp);
)
appfs_call_libtcl(
retval = Tcl_EvalObjv(interp, objc, objv, 0);
)
appfs_call_libtcl(
for (i = 0; i < objc; i++) {
Tcl_DecrRefCount(objv[i]);
}
)
ckfree((void *) objv);
if (retval != TCL_OK) {
appfs_call_libtcl(
APPFS_DEBUG("Tcl command failed, ::errorInfo contains: %s\n", Tcl_GetVar(interp, "::errorInfo", 0));
)
}
return(retval);
}
/*
* Request all Tcl interpreters restart
*/
static void appfs_tcl_ResetInterps(void) {
APPFS_DEBUG("Requesting reset of all interpreters.");
__sync_add_and_fetch(&interp_reset_key, 1);
return;
}
/*
* Determine the UID for the user making the current FUSE filesystem request.
* This will be used to lookup the user's home directory so we can search for
* locally modified files.
*/
static uid_t appfs_get_fsuid(void) {
struct fuse_context *ctx;
if (!appfs_fuse_started) {
return(getuid());
}
ctx = fuse_get_context();
if (ctx == NULL) {
/* Unable to lookup user for some reason */
/* Return an unprivileged user ID */
APPFS_DEBUG("Unable to lookup user for some reason, returninng user ID of 1");
return(1);
}
return(ctx->uid);
}
/*
* Determine the GID for the user making the current FUSE filesystem request.
*/
static gid_t appfs_get_fsgid(void) {
struct fuse_context *ctx;
if (!appfs_fuse_started) {
return(getgid());
}
ctx = fuse_get_context();
if (ctx == NULL) {
/* Unable to lookup user for some reason */
/* Return an unprivileged user ID */
APPFS_DEBUG("Unable to lookup group for some reason, returninng group ID of 1");
return(1);
}
return(ctx->gid);
}
static void appfs_simulate_user_fs_enter(void) {
setfsuid(appfs_get_fsuid());
setfsgid(appfs_get_fsgid());
}
static void appfs_simulate_user_fs_leave(void) {
setfsuid(0);
setfsgid(0);
}
/*
* Look up the home directory for a given UID
* Returns a C string containing the user's home directory or NULL if
* the user's home directory does not exist or is not correctly
* configured
*/
static char *appfs_get_homedir(uid_t fsuid) {
struct passwd entry, *result;
struct stat stbuf;
char buf[1024], *retval;
int gpu_ret, stat_ret;
gpu_ret = getpwuid_r(fsuid, &entry, buf, sizeof(buf), &result);
if (gpu_ret != 0) {
APPFS_DEBUG("getpwuid_r(%llu, ...) returned in failure", (unsigned long long) fsuid);
return(NULL);
}
if (result == NULL) {
APPFS_DEBUG("getpwuid_r(%llu, ...) returned NULL result", (unsigned long long) fsuid);
return(NULL);
}
if (result->pw_dir == NULL) {
APPFS_DEBUG("getpwuid_r(%llu, ...) returned NULL home directory", (unsigned long long) fsuid);
return(NULL);
}
stat_ret = stat(result->pw_dir, &stbuf);
if (stat_ret != 0) {
APPFS_DEBUG("stat(%s) returned in failure", result->pw_dir);
return(NULL);
}
if (stbuf.st_uid != fsuid) {
APPFS_DEBUG("UID mis-match on user %llu's home directory (%s). It's owned by %llu.",
(unsigned long long) fsuid,
result->pw_dir,
(unsigned long long) stbuf.st_uid
);
return(NULL);
}
retval = strdup(result->pw_dir);
return(retval);
}
/*
* Generate an inode for a given path. The inode should be computed in such
* a way that it is unlikely to be duplicated and remains the same for a given
* file
*
* Current implementation is an FNV-1a 32-bit
*/
#if UINT_MAX < 4294967295
#error Integer size is too small
#endif
static unsigned long long appfs_get_path_inode(const char *path, int uid) {
unsigned int retval;
const unsigned char *p;
retval = 2166136261U; /* FNV-1a 32-bit offset_basis */
for (p = (unsigned char *) path; *p; p++) {
retval ^= (int) *p;
#if 0
retval *= 16777619; /* FNV-1a 32-bit prime */
#else
/* GCC Optimized replacement */
retval += (retval << 1) + (retval << 4) + (retval << 7) + (retval << 8) + (retval << 24);
#endif
}
if (uid >= 0) {
retval += uid;
retval++;
}
APPFS_DEBUG("Looked up inode number for path=%s,uid=%i: %u", path, uid, retval);
return(retval);
}
/*
* Cache Get Path Info lookups for speed
*/
static int appfs_get_path_info_cache_get(const char *path, uid_t uid, struct appfs_pathinfo *pathinfo) {
unsigned int hash_idx;
int pthread_ret;
int retval;
retval = 1;
pthread_ret = pthread_mutex_lock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to lock path_info cache mutex !");
return(-1);
}
APPFS_DEBUG("Looking up cache entry for path=%s,uid=%lli...", path, (long long) uid);
if (appfs_path_info_cache != NULL) {
hash_idx = (appfs_get_path_inode(path, uid)) % appfs_path_info_cache_size;
if (appfs_path_info_cache[hash_idx]._cache_path != NULL) {
if (strcmp(appfs_path_info_cache[hash_idx]._cache_path, path) == 0 && appfs_path_info_cache[hash_idx]._cache_uid == uid) {
retval = 0;
memcpy(pathinfo, &appfs_path_info_cache[hash_idx], sizeof(*pathinfo));
pathinfo->_cache_path = NULL;
}
}
}
pthread_ret = pthread_mutex_unlock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to unlock path_info cache mutex !");
return(-1);
}
if (retval == 0) {
APPFS_DEBUG("Cache hit on path=%s,uid=%lli", path, (long long) uid);
} else {
APPFS_DEBUG("Cache miss on path=%s,uid=%lli", path, (long long) uid);
}
return(retval);
}
static void appfs_get_path_info_cache_add(const char *path, uid_t uid, struct appfs_pathinfo *pathinfo) {
unsigned int hash_idx;
int pthread_ret;
pthread_ret = pthread_mutex_lock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to lock path_info cache mutex !");
return;
}
if (appfs_path_info_cache == NULL) {
appfs_path_info_cache = calloc(appfs_path_info_cache_size, sizeof(*appfs_path_info_cache));
}
hash_idx = (appfs_get_path_inode(path, uid)) % appfs_path_info_cache_size;
if (appfs_path_info_cache[hash_idx]._cache_path != NULL) {
free(appfs_path_info_cache[hash_idx]._cache_path);
}
memcpy(&appfs_path_info_cache[hash_idx], pathinfo, sizeof(*pathinfo));
appfs_path_info_cache[hash_idx]._cache_path = strdup(path);
appfs_path_info_cache[hash_idx]._cache_uid = uid;
pthread_ret = pthread_mutex_unlock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to unlock path_info cache mutex !");
return;
}
return;
}
static void appfs_get_path_info_cache_rm(const char *path, uid_t uid) {
unsigned int hash_idx;
int pthread_ret;
pthread_ret = pthread_mutex_lock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to lock path_info cache mutex !");
return;
}
if (appfs_path_info_cache != NULL) {
hash_idx = (appfs_get_path_inode(path, uid)) % appfs_path_info_cache_size;
if (appfs_path_info_cache[hash_idx]._cache_path != NULL) {
free(appfs_path_info_cache[hash_idx]._cache_path);
appfs_path_info_cache[hash_idx]._cache_path = NULL;
}
}
pthread_ret = pthread_mutex_unlock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to unlock path_info cache mutex !");
return;
}
return;
}
static void appfs_get_path_info_cache_flush(uid_t uid, int new_size) {
unsigned int idx;
int pthread_ret;
APPFS_DEBUG("Flushing AppFS cache (uid = %lli, new_size = %i)", (long long) uid, new_size);
pthread_ret = pthread_mutex_lock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to lock path_info cache mutex !");
return;
}
if (appfs_path_info_cache != NULL) {
for (idx = 0; idx < appfs_path_info_cache_size; idx++) {
if (appfs_path_info_cache[idx]._cache_path != NULL) {
if (uid != ((uid_t) -1)) {
if (appfs_path_info_cache[idx]._cache_uid != uid) {
continue;
}
}
free(appfs_path_info_cache[idx]._cache_path);
appfs_path_info_cache[idx]._cache_path = NULL;
}
}
}
if (uid == ((uid_t) -1)) {
free(appfs_path_info_cache);
appfs_path_info_cache = NULL;
if (new_size != -1) {
appfs_path_info_cache_size = new_size;
}
}
pthread_ret = pthread_mutex_unlock(&appfs_path_info_cache_mutex);
if (pthread_ret != 0) {
APPFS_DEBUG("Unable to unlock path_info cache mutex !");
return;
}
return;
}
/* Get information about a path, and optionally list children */
static int appfs_get_path_info(const char *path, struct appfs_pathinfo *pathinfo) {
Tcl_Interp *interp;
Tcl_Obj *attrs_dict, *attr_value;
const char *attr_value_str, *attr_value_str_i;
Tcl_WideInt attr_value_wide;
int attr_value_int;
static __thread Tcl_Obj *attr_key_type = NULL, *attr_key_perms = NULL, *attr_key_size = NULL, *attr_key_time = NULL, *attr_key_source = NULL, *attr_key_childcount = NULL, *attr_key_packaged = NULL;
int cache_ret;
int tcl_ret;
int retval;
uid_t fsuid;
retval = 0;
fsuid = appfs_get_fsuid();
cache_ret = appfs_get_path_info_cache_get(path, fsuid, pathinfo);
if (cache_ret == 0) {
if (pathinfo->type == APPFS_PATHTYPE_DOES_NOT_EXIST) {
APPFS_DEBUG("Returning from cache: does not exist \"%s\"", path);
return(-ENOENT);
}
if (pathinfo->type == APPFS_PATHTYPE_INVALID) {
APPFS_DEBUG("Returning from cache: invalid object \"%s\"", path);
return(-EIO);
}
return(0);
}
interp = appfs_TclInterp();
if (interp == NULL) {
APPFS_DEBUG("error: Unable to get an interpreter");
return(-EIO);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getattr", path);
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("::appfs::getattr(%s) failed.", path);
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
pathinfo->type = APPFS_PATHTYPE_DOES_NOT_EXIST;
appfs_get_path_info_cache_add(path, fsuid, pathinfo);
appfs_call_libtcl(Tcl_Release(interp);)
return(-ENOENT);
}
if (attr_key_type == NULL) {
appfs_call_libtcl(
attr_key_type = Tcl_NewStringObj("type", -1);
attr_key_perms = Tcl_NewStringObj("perms", -1);
attr_key_size = Tcl_NewStringObj("size", -1);
attr_key_time = Tcl_NewStringObj("time", -1);
attr_key_source = Tcl_NewStringObj("source", -1);
attr_key_childcount = Tcl_NewStringObj("childcount", -1);
attr_key_packaged = Tcl_NewStringObj("packaged", -1);
Tcl_IncrRefCount(attr_key_type);
Tcl_IncrRefCount(attr_key_perms);
Tcl_IncrRefCount(attr_key_size);
Tcl_IncrRefCount(attr_key_time);
Tcl_IncrRefCount(attr_key_source);
Tcl_IncrRefCount(attr_key_childcount);
Tcl_IncrRefCount(attr_key_packaged);
)
}
appfs_call_libtcl(
attrs_dict = Tcl_GetObjResult(interp);
tcl_ret = Tcl_DictObjGet(interp, attrs_dict, attr_key_type, &attr_value);
)
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("[dict get \"type\"] failed");
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
appfs_call_libtcl(Tcl_Release(interp);)
return(-EIO);
}
if (attr_value == NULL) {
APPFS_DEBUG("error: Unable to get type for \"%s\" from Tcl", path);
appfs_call_libtcl(Tcl_Release(interp);)
return(-EIO);
}
pathinfo->packaged = 0;
appfs_call_libtcl(
attr_value_str = Tcl_GetString(attr_value);
switch (attr_value_str[0]) {
case 'd': /* directory */
pathinfo->type = APPFS_PATHTYPE_DIRECTORY;
pathinfo->typeinfo.dir.childcount = 0;
Tcl_DictObjGet(interp, attrs_dict, attr_key_childcount, &attr_value);
if (attr_value != NULL) {
tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide);
if (tcl_ret == TCL_OK) {
pathinfo->typeinfo.dir.childcount = attr_value_wide;
}
}
break;
case 'f': /* file */
pathinfo->type = APPFS_PATHTYPE_FILE;
pathinfo->typeinfo.file.size = 0;
pathinfo->typeinfo.file.executable = 0;
pathinfo->typeinfo.file.suidRoot = 0;
pathinfo->typeinfo.file.worldaccessible = 0;
Tcl_DictObjGet(interp, attrs_dict, attr_key_size, &attr_value);
if (attr_value != NULL) {
tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide);
if (tcl_ret == TCL_OK) {
pathinfo->typeinfo.file.size = attr_value_wide;
}
}
Tcl_DictObjGet(interp, attrs_dict, attr_key_perms, &attr_value);
if (attr_value != NULL) {
attr_value_str = Tcl_GetString(attr_value);
for (attr_value_str_i = &attr_value_str[0]; *attr_value_str_i != '\0'; attr_value_str_i++) {
switch (*attr_value_str_i) {
case 'x':
pathinfo->typeinfo.file.executable = 1;
break;
case 'U':
pathinfo->typeinfo.file.suidRoot = 1;
break;
case '-':
pathinfo->typeinfo.file.worldaccessible = 1;
break;
}
}
}
break;
case 's': /* symlink */
pathinfo->type = APPFS_PATHTYPE_SYMLINK;
pathinfo->typeinfo.symlink.size = 0;
pathinfo->typeinfo.symlink.source[0] = '\0';
Tcl_DictObjGet(interp, attrs_dict, attr_key_source, &attr_value);
if (attr_value != NULL) {
attr_value_str = Tcl_GetStringFromObj(attr_value, &attr_value_int);
if ((attr_value_int + 1) <= sizeof(pathinfo->typeinfo.symlink.source)) {
pathinfo->typeinfo.symlink.size = attr_value_int;
pathinfo->typeinfo.symlink.source[attr_value_int] = '\0';
memcpy(pathinfo->typeinfo.symlink.source, attr_value_str, attr_value_int);
}
}
break;
case 'F': /* pipe/fifo */
pathinfo->type = APPFS_PATHTYPE_FIFO;
break;
case 'S': /* UNIX domain socket */
pathinfo->type = APPFS_PATHTYPE_SOCKET;
break;
default:
retval = -EIO;
}
Tcl_DictObjGet(interp, attrs_dict, attr_key_packaged, &attr_value);
if (attr_value != NULL) {
pathinfo->packaged = 1;
}
Tcl_DictObjGet(interp, attrs_dict, attr_key_time, &attr_value);
if (attr_value != NULL) {
tcl_ret = Tcl_GetWideIntFromObj(NULL, attr_value, &attr_value_wide);
if (tcl_ret == TCL_OK) {
pathinfo->time = attr_value_wide;
}
} else {
pathinfo->time = appfs_boottime;
}
Tcl_Release(interp);
)
if (pathinfo->packaged) {
pathinfo->inode = appfs_get_path_inode(path, -1);
} else {
pathinfo->inode = appfs_get_path_inode(path, fsuid);
}
APPFS_DEBUG("Caching inode for path=%s,uid=%lli as %llu (packaged = %i)", path, (long long) fsuid, pathinfo->inode, pathinfo->packaged);
if (retval == 0) {
appfs_get_path_info_cache_add(path, fsuid, pathinfo);
} else {
APPFS_DEBUG("error: Invalid type for \"%s\" from Tcl", path);
}
return(retval);
}
static char *appfs_prepare_to_create(const char *path) {
Tcl_Interp *interp;
const char *real_path;
int tcl_ret;
appfs_get_path_info_cache_flush(appfs_get_fsuid(), -1);
interp = appfs_TclInterp();
if (interp == NULL) {
return(NULL);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
appfs_call_libtcl(
tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::prepare_to_create", path);
)
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("::appfs::prepare_to_create(%s) failed.", path);
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
appfs_call_libtcl(Tcl_Release(interp);)
return(NULL);
}
appfs_call_libtcl(
real_path = Tcl_GetStringResult(interp);
)
appfs_call_libtcl(Tcl_Release(interp);)
if (real_path == NULL) {
return(NULL);
}
return(strdup(real_path));
}
static char *appfs_localpath(const char *path) {
Tcl_Interp *interp;
const char *real_path;
int tcl_ret;
interp = appfs_TclInterp();
if (interp == NULL) {
return(NULL);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
appfs_call_libtcl(
tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::localpath", path);
)
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("::appfs::localpath(%s) failed.", path);
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
return(NULL);
}
appfs_call_libtcl(
real_path = Tcl_GetStringResult(interp);
)
appfs_call_libtcl(Tcl_Release(interp);)
if (real_path == NULL) {
return(NULL);
}
return(strdup(real_path));
}
#if (defined(DEBUG) && defined(APPFS_EXIT_PATH)) || defined(APPFS_EXIT_PATH_ENABLE_MAJOR_SECURITY_HOLE)
static void appfs_exit(void) {
int global_interp_reset_key;
global_interp_reset_key = __sync_fetch_and_add(&interp_reset_key, 0);
__sync_fetch_and_sub(&interp_reset_key, global_interp_reset_key);
while (__sync_sub_and_fetch(&interp_reset_key, 1) >= 0) {
/* Busy Loop */
}
global_interp_reset_key = __sync_fetch_and_add(&interp_reset_key, 0);
if (global_interp_reset_key != -1) {
APPFS_DEBUG("Error sending kill signal to all threads, aborting anyway.");
}
appfs_get_path_info_cache_flush(-1, -1);
fuse_exit(fuse_get_context()->fuse);
return;
}
#endif
#if defined(APPFS_EXEC_PATH_ENABLE_MAJOR_SECURITY_HOLE)
static void appfs_runTcl(const char *script, size_t scriptLen) {
Tcl_Interp *interp;
Tcl_Obj *scriptObj;
int tcl_ret;
interp = appfs_TclInterp();
if (interp == NULL) {
APPFS_DEBUG("Error creating an interpreter.");
return;
}
appfs_call_libtcl(scriptObj = Tcl_NewStringObj(script, scriptLen);)
if (scriptObj == NULL) {
APPFS_DEBUG("Error creating a script object.");
return;
}
appfs_call_libtcl(tcl_ret = Tcl_EvalObjEx(interp, scriptObj, TCL_EVAL_DIRECT);)
if (tcl_ret != TCL_OK) {
appfs_call_libtcl(
APPFS_DEBUG("Script returned error %i: %s", tcl_ret, Tcl_GetStringResult(interp));
)
}
return;
}
#endif
static int appfs_fuse_readlink(const char *path, char *buf, size_t size) {
struct appfs_pathinfo pathinfo;
int retval = 0;
APPFS_DEBUG("Enter (path = %s, ...)", path);
pathinfo.type = APPFS_PATHTYPE_INVALID;
retval = appfs_get_path_info(path, &pathinfo);
if (retval != 0) {
return(retval);
}
if (pathinfo.type != APPFS_PATHTYPE_SYMLINK) {
return(-EINVAL);
}
if ((strlen(pathinfo.typeinfo.symlink.source) + 1) > size) {
return(-ENAMETOOLONG);
}
memcpy(buf, pathinfo.typeinfo.symlink.source, strlen(pathinfo.typeinfo.symlink.source) + 1);
return(0);
}
static int appfs_fuse_getattr(const char *path, struct stat *stbuf) {
struct appfs_pathinfo pathinfo;
int changeOwnerToUserIfPackaged;
int retval;
retval = 0;
APPFS_DEBUG("Enter (path = %s, ...)", path);
#if (defined(DEBUG) && defined(APPFS_EXIT_PATH)) || defined(APPFS_EXIT_PATH_ENABLE_MAJOR_SECURITY_HOLE)
/*
* This is a major security issue so we cannot let it be compiled into
* any release
*/
if (strcmp(path, "/exit") == 0) {
appfs_exit();
}
#endif
#if defined(APPFS_EXEC_PATH_ENABLE_MAJOR_SECURITY_HOLE)
if (strcmp(path, "/exec") == 0) {
memset(stbuf, 0, sizeof(struct stat));
stbuf->st_mtime = 0;
stbuf->st_ctime = 0;
stbuf->st_atime = 0;
stbuf->st_ino = 3;
stbuf->st_mode = 0;
stbuf->st_mode = S_IFREG | 0600;
stbuf->st_nlink = 1;
stbuf->st_size = 0;
return(retval);
}
#endif
pathinfo.type = APPFS_PATHTYPE_INVALID;
retval = appfs_get_path_info(path, &pathinfo);
if (retval != 0) {
if (retval == -ENOENT) {
APPFS_DEBUG("get_path_info returned ENOENT, returning it as well.");
} else {
APPFS_DEBUG("error: get_path_info failed");
}
return(retval);
}
memset(stbuf, 0, sizeof(struct stat));
stbuf->st_mtime = pathinfo.time;
stbuf->st_ctime = pathinfo.time;
stbuf->st_atime = pathinfo.time;
stbuf->st_ino = pathinfo.inode;
stbuf->st_mode = 0;
changeOwnerToUserIfPackaged = 1;
switch (pathinfo.type) {
case APPFS_PATHTYPE_DIRECTORY:
stbuf->st_mode = S_IFDIR | 0555;
stbuf->st_nlink = 2 + pathinfo.typeinfo.dir.childcount;
break;
case APPFS_PATHTYPE_FILE:
stbuf->st_mode = S_IFREG | 0444;
if (pathinfo.typeinfo.file.executable) {
stbuf->st_mode |= 0111;
}
if (pathinfo.packaged) {
if (pathinfo.typeinfo.file.suidRoot) {
changeOwnerToUserIfPackaged = 0;
stbuf->st_mode |= 04000;
}
}
if (pathinfo.typeinfo.file.worldaccessible) {
stbuf->st_mode &= ~077;
}
stbuf->st_nlink = 1;
stbuf->st_size = pathinfo.typeinfo.file.size;
break;
case APPFS_PATHTYPE_SYMLINK:
stbuf->st_mode = S_IFLNK | 0555;
stbuf->st_nlink = 1;
stbuf->st_size = pathinfo.typeinfo.symlink.size;
break;
case APPFS_PATHTYPE_SOCKET:
stbuf->st_mode = S_IFSOCK | 0555;
stbuf->st_nlink = 1;
stbuf->st_size = 0;
break;
case APPFS_PATHTYPE_FIFO:
stbuf->st_mode = S_IFIFO | 0555;
stbuf->st_nlink = 1;
stbuf->st_size = 0;
break;
case APPFS_PATHTYPE_DOES_NOT_EXIST:
retval = -ENOENT;
break;
case APPFS_PATHTYPE_INVALID:
retval = -EIO;
break;
}
if ((pathinfo.packaged && changeOwnerToUserIfPackaged) || (!pathinfo.packaged)) {
stbuf->st_uid = appfs_get_fsuid();
stbuf->st_gid = appfs_get_fsgid();
stbuf->st_mode |= 0200;
}
return(retval);
}
static int appfs_fuse_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) {
Tcl_Interp *interp;
Tcl_Obj **children;
int children_count, idx;
int tcl_ret;
APPFS_DEBUG("Enter (path = %s, ...)", path);
interp = appfs_TclInterp();
if (interp == NULL) {
APPFS_DEBUG("error: Unable to get an interpreter");
return(0);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::getchildren", path);
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("::appfs::getchildren(%s) failed.", path);
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
appfs_call_libtcl(Tcl_Release(interp);)
return(0);
}
appfs_call_libtcl(
tcl_ret = Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), &children_count, &children);
)
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("Parsing list of children on path %s failed.", path);
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
appfs_call_libtcl(Tcl_Release(interp);)
return(0);
}
for (idx = 0; idx < children_count; idx++) {
appfs_call_libtcl(
filler(buf, Tcl_GetString(children[idx]), NULL, 0);
)
}
appfs_call_libtcl(Tcl_Release(interp);)
return(0);
}
static int appfs_fuse_open(const char *path, struct fuse_file_info *fi) {
Tcl_Interp *interp;
struct appfs_pathinfo pathinfo;
const char *real_path, *mode;
int gpi_ret, tcl_ret;
int fh;
APPFS_DEBUG("Enter (path = %s, ...)", path);
#if defined(APPFS_EXEC_PATH_ENABLE_MAJOR_SECURITY_HOLE)
if (strcmp(path, "/exec") == 0) {
fi->fh = 0;
return(0);
}
#endif
gpi_ret = appfs_get_path_info(path, &pathinfo);
if ((fi->flags & (O_WRONLY|O_CREAT)) == (O_CREAT|O_WRONLY)) {
/* The file will be created if it does not exist */
if (gpi_ret != 0 && gpi_ret != -ENOENT) {
APPFS_DEBUG("error: get_path_info failed");
return(gpi_ret);
}
mode = "create";
/*
* We have to clear the cache here so that the number of
* links gets maintained on the parent directory
*/
appfs_get_path_info_cache_flush(appfs_get_fsuid(), -1);
} else {
/* The file must already exist */
if (gpi_ret != 0) {
APPFS_DEBUG("error: get_path_info failed");
return(gpi_ret);
}
mode = "";
if ((fi->flags & O_WRONLY) == O_WRONLY) {
mode = "write";
}
}
if (pathinfo.type == APPFS_PATHTYPE_DIRECTORY) {
APPFS_DEBUG("error: Asked to open a directory.");
return(-EISDIR);
}
interp = appfs_TclInterp();
if (interp == NULL) {
APPFS_DEBUG("error: Unable to get an interpreter");
return(-EIO);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::openpath", path, mode);
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("::appfs::openpath(%s, %s) failed.", path, mode);
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
appfs_call_libtcl(Tcl_Release(interp);)
return(-EIO);
}
appfs_call_libtcl(
real_path = Tcl_GetStringResult(interp);
)
appfs_call_libtcl(Tcl_Release(interp);)
if (real_path == NULL) {
APPFS_DEBUG("error: real_path was NULL.")
return(-EIO);
}
APPFS_DEBUG("Translated request to open %s to opening %s (mode = \"%s\")", path, real_path, mode);
fh = open(real_path, fi->flags, 0600);
if (fh < 0) {
APPFS_DEBUG("error: open failed");
return(errno * -1);
}
fi->fh = fh;
APPFS_DEBUG("Opened \"%s\" (for \"%s\") with file descriptor %i", real_path, path, fh);
return(0);
}
static int appfs_fuse_close(const char *path, struct fuse_file_info *fi) {
int close_ret;
APPFS_DEBUG("Enter (path = %s, ...)", path);
#if defined(APPFS_EXEC_PATH_ENABLE_MAJOR_SECURITY_HOLE)
if (strcmp(path, "/exec") == 0) {
return(0);
}
#endif
appfs_get_path_info_cache_rm(path, appfs_get_fsuid());
close_ret = close(fi->fh);
if (close_ret != 0) {
APPFS_DEBUG("error: close failed");
return(errno * -1);
}
return(0);
}
static int appfs_fuse_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
ssize_t read_ret;
int retval;
APPFS_DEBUG("Enter (path = %s, buf, size = %lli, offset = %lli, fd = %lli)", path, (long long) size, (long long) offset, (long long) fi->fh);
retval = 0;
while (size != 0) {
read_ret = pread(fi->fh, buf, size, offset);
if (read_ret < 0) {
APPFS_DEBUG("error: read failed");
return(errno * -1);
}
if (read_ret == 0) {
break;
}
size -= read_ret;
buf += read_ret;
offset += read_ret;
retval += read_ret;
}
if (size != 0) {
APPFS_DEBUG("error: incomplete read (this might be an error because FUSE will request the exact length of the file)");
}
APPFS_DEBUG("Returning: %i", retval);
return(retval);
}
static int appfs_fuse_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi) {
ssize_t write_ret;
int retval;
APPFS_DEBUG("Enter (path = %s, ...)", path);
#if defined(APPFS_EXEC_PATH_ENABLE_MAJOR_SECURITY_HOLE)
if (strcmp(path, "/exec") == 0) {
appfs_runTcl(buf, size);
return(size);
}
#endif
appfs_get_path_info_cache_rm(path, appfs_get_fsuid());
retval = 0;
while (size != 0) {
write_ret = pwrite(fi->fh, buf, size, offset);
if (write_ret < 0) {
APPFS_DEBUG("error: write failed");
return(errno * -1);
}
if (write_ret == 0) {
break;
}
size -= write_ret;
buf += write_ret;
offset += write_ret;
retval += write_ret;
}
if (size != 0) {
APPFS_DEBUG("error: incomplete write");
}
return(retval);
}
static int appfs_fuse_mknod(const char *path, mode_t mode, dev_t device) {
char *real_path;
int mknod_ret;
APPFS_DEBUG("Enter (path = %s, ...)", path);
if ((mode & S_IFCHR) == S_IFCHR) {
return(-EPERM);
}
if ((mode & S_IFBLK) == S_IFBLK) {
return(-EPERM);
}
real_path = appfs_prepare_to_create(path);
if (real_path == NULL) {
return(-EIO);
}
appfs_simulate_user_fs_enter();
mknod_ret = mknod(real_path, mode, device);
appfs_simulate_user_fs_leave();
free(real_path);
if (mknod_ret != 0) {
return(errno * -1);
}
return(0);
}
static int appfs_fuse_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
char *real_path;
int fd;
APPFS_DEBUG("Enter (path = %s, ...)", path);
if ((mode & S_IFCHR) == S_IFCHR) {
return(-EPERM);
}
if ((mode & S_IFBLK) == S_IFBLK) {
return(-EPERM);
}
real_path = appfs_prepare_to_create(path);
if (real_path == NULL) {
return(-EIO);
}
appfs_simulate_user_fs_enter();
fd = creat(real_path, mode);
appfs_simulate_user_fs_leave();
free(real_path);
if (fd < 0) {
return(errno * -1);
}
fi->fh = fd;
return(0);
}
static int appfs_fuse_truncate(const char *path, off_t size) {
char *real_path;
int truncate_ret;
APPFS_DEBUG("Enter (path = %s, ...)", path);
#if defined(APPFS_EXEC_PATH_ENABLE_MAJOR_SECURITY_HOLE)
if (strcmp(path, "/exec") == 0) {
return(0);
}
#endif
real_path = appfs_localpath(path);
if (real_path == NULL) {
return(-EIO);
}
appfs_get_path_info_cache_rm(path, appfs_get_fsuid());
appfs_simulate_user_fs_enter();
truncate_ret = truncate(real_path, size);
appfs_simulate_user_fs_leave();
free(real_path);
if (truncate_ret != 0) {
return(errno * -1);
}
return(0);
}
static int appfs_fuse_unlink_rmdir(const char *path) {
Tcl_Interp *interp;
int tcl_ret;
APPFS_DEBUG("Enter (path = %s, ...)", path);
appfs_get_path_info_cache_flush(appfs_get_fsuid(), -1);
interp = appfs_TclInterp();
if (interp == NULL) {
return(-EIO);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
tcl_ret = appfs_Tcl_Eval(interp, 2, "::appfs::unlinkpath", path);
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("::appfs::unlinkpath(%s) failed.", path);
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
appfs_call_libtcl(Tcl_Release(interp);)
return(-EIO);
}
appfs_call_libtcl(Tcl_Release(interp);)
return(0);
}
static int appfs_fuse_mkdir(const char *path, mode_t mode) {
char *real_path;
int mkdir_ret;
APPFS_DEBUG("Enter (path = %s, ...)", path);
real_path = appfs_prepare_to_create(path);
if (real_path == NULL) {
return(-EIO);
}
appfs_simulate_user_fs_enter();
mkdir_ret = mkdir(real_path, mode);
appfs_simulate_user_fs_leave();
free(real_path);
if (mkdir_ret != 0) {
if (errno != EEXIST) {
return(errno * -1);
}
}
return(0);
}
static int appfs_fuse_chmod(const char *path, mode_t mode) {
Tcl_Interp *interp;
const char *real_path;
int tcl_ret, chmod_ret;
APPFS_DEBUG("Enter (path = %s, ...)", path);
appfs_get_path_info_cache_rm(path, appfs_get_fsuid());
interp = appfs_TclInterp();
if (interp == NULL) {
return(-EIO);
}
appfs_call_libtcl(Tcl_Preserve(interp);)
tcl_ret = appfs_Tcl_Eval(interp, 3, "::appfs::openpath", path, "write");
if (tcl_ret != TCL_OK) {
APPFS_DEBUG("::appfs::openpath(%s, %s) failed.", path, "write");
appfs_call_libtcl(
APPFS_DEBUG("Tcl Error is: %s", Tcl_GetStringResult(interp));
)
appfs_call_libtcl(Tcl_Release(interp);)
return(-EIO);
}
appfs_call_libtcl(
real_path = Tcl_GetStringResult(interp);
)
appfs_call_libtcl(Tcl_Release(interp);)
if (real_path == NULL) {
return(-EIO);
}
appfs_simulate_user_fs_enter();
chmod_ret = chmod(real_path, mode);
appfs_simulate_user_fs_leave();
return(chmod_ret);
}
static int appfs_fuse_symlink(const char *oldpath, const char *newpath) {
char *real_path;
int symlink_ret;
APPFS_DEBUG("Enter (path = %s, %s)", oldpath, newpath);
real_path = appfs_prepare_to_create(newpath);
if (real_path == NULL) {
return(-EIO);
}
appfs_simulate_user_fs_enter();
symlink_ret = symlink(oldpath, real_path);
appfs_simulate_user_fs_leave();
free(real_path);
if (symlink_ret != 0) {
return(errno * -1);
}
return(0);
}
/*
* SQLite3 mode: Execute raw SQL and return success or failure
*/
static int appfs_sqlite3(const char *sql) {
Tcl_Interp *interp;
const char *sql_ret;
int tcl_ret;
interp = appfs_create_TclInterp(NULL);
if (interp == NULL) {
fprintf(stderr, "Unable to create a Tcl interpreter. Aborting.\n");
return(1);
}
tcl_ret = appfs_Tcl_Eval(interp, 5, "::appfs::db", "eval", sql, "row", "unset -nocomplain row(*); parray row; puts \"----\"");
sql_ret = Tcl_GetStringResult(interp);
if (tcl_ret != TCL_OK) {
fprintf(stderr, "[error] %s\n", sql_ret);
return(1);
}
if (sql_ret && sql_ret[0] != '\0') {
printf("%s\n", sql_ret);
}
return(0);
}
/*
* Tcl mode: Execute raw Tcl and return success or failure
*/
static int appfs_tcl(const char *tcl) {
Tcl_Interp *interp;
const char *tcl_result;
int tcl_ret;
interp = appfs_create_TclInterp(NULL);
if (interp == NULL) {
fprintf(stderr, "Unable to create a Tcl interpreter. Aborting.\n");
return(1);
}
tcl_ret = Tcl_Eval(interp, tcl);
tcl_result = Tcl_GetStringResult(interp);
if (tcl_ret != TCL_OK) {
fprintf(stderr, "[error] %s\n", Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY));
return(1);
}
if (tcl_result && tcl_result[0] != '\0') {
printf("%s\n", tcl_result);
}
return(0);
}
/*
* AppFSd Package for Tcl:
* Bridge for I/O operations to request information about the current
* transaction
*/
/*
* Tcl interface to get the home directory for the user making the "current"
* FUSE I/O request
*/
static int tcl_appfs_get_homedir(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
char *homedir;
Tcl_Obj *homedir_obj;
uid_t fsuid;
static __thread Tcl_Obj *last_homedir_obj = NULL;
static __thread uid_t last_fsuid = -1;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, NULL);
return(TCL_ERROR);
}
fsuid = appfs_get_fsuid();
if (fsuid == last_fsuid && last_homedir_obj != NULL) {
homedir_obj = last_homedir_obj;
Tcl_IncrRefCount(homedir_obj);
} else {
homedir = appfs_get_homedir(appfs_get_fsuid());
if (homedir == NULL) {
return(TCL_ERROR);
}
homedir_obj = Tcl_NewStringObj(homedir, -1);
free(homedir);
Tcl_IncrRefCount(homedir_obj);
if (last_homedir_obj != NULL) {
Tcl_DecrRefCount(last_homedir_obj);
}
last_homedir_obj = homedir_obj;
last_fsuid = fsuid;
Tcl_IncrRefCount(homedir_obj);
}
Tcl_SetObjResult(interp, homedir_obj);
Tcl_DecrRefCount(homedir_obj);
return(TCL_OK);
}
static int tcl_appfs_simulate_user_fs_enter(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
appfs_simulate_user_fs_enter();
return(TCL_OK);
}
static int tcl_appfs_simulate_user_fs_leave(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
appfs_simulate_user_fs_leave();
return(TCL_OK);
}
static int tcl_appfs_get_fsuid(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
uid_t fsuid;
fsuid = appfs_get_fsuid();
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(fsuid));
return(TCL_OK);
}
static int tcl_appfs_get_fsgid(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
gid_t fsgid;
fsgid = appfs_get_fsgid();
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(fsgid));
return(TCL_OK);
}
static int tcl_appfs_get_path_info_cache_flush(ClientData cd, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) {
int tcl_ret;
int new_size;
new_size = -1;
if (objc == 2) {
tcl_ret = Tcl_GetIntFromObj(interp, objv[1], &new_size);
if (tcl_ret != TCL_OK) {
return(tcl_ret);
}
} else if (objc > 2 || objc < 1) {
Tcl_WrongNumArgs(interp, 1, objv, "?new_cache_size?");
return(TCL_ERROR);
}
appfs_get_path_info_cache_flush(-1, new_size);
return(TCL_OK);
}
static int Appfsd_Init(Tcl_Interp *interp) {
#ifdef USE_TCL_STUBS
if (Tcl_InitStubs(interp, TCL_VERSION, 0) == 0L) {
return(TCL_ERROR);
}
#endif
Tcl_CreateObjCommand(interp, "appfsd::get_homedir", tcl_appfs_get_homedir, NULL, NULL);
Tcl_CreateObjCommand(interp, "appfsd::get_fsuid", tcl_appfs_get_fsuid, NULL, NULL);
Tcl_CreateObjCommand(interp, "appfsd::get_fsgid", tcl_appfs_get_fsgid, NULL, NULL);
Tcl_CreateObjCommand(interp, "appfsd::simulate_user_fs_enter", tcl_appfs_simulate_user_fs_enter, NULL, NULL);
Tcl_CreateObjCommand(interp, "appfsd::simulate_user_fs_leave", tcl_appfs_simulate_user_fs_leave, NULL, NULL);
Tcl_CreateObjCommand(interp, "appfsd::get_path_info_cache_flush", tcl_appfs_get_path_info_cache_flush, NULL, NULL);
Tcl_PkgProvide(interp, "appfsd", "1.0");
return(TCL_OK);
}
/*
* Hot-restart support
*/
/* Initiate a hot-restart */
static void appfs_hot_restart(void) {
APPFS_DEBUG("Asked to initiate hot restart");
appfs_tcl_ResetInterps();
appfs_get_path_info_cache_flush(-1, -1);
return;
}
/*
* Signal handler
* SIGHUP initiates a hot restart
*/
static void appfs_signal_handler(int sig) {
/* Do not handle signals until FUSE has been started */
if (!appfs_fuse_started) {
return;
}
/* Request to perform a "hot" restart */
if (sig == SIGHUP) {
appfs_hot_restart();
}
return;
}
/*
* Terminate a thread
*/
static void appfs_terminate_interp_and_thread(void *_interp) {
Tcl_Interp *interp;
APPFS_DEBUG("Called: _interp = %p", _interp);
if (_interp != NULL) {
interp = _interp;
APPFS_DEBUG("Terminating interpreter due to thread termination");
appfs_call_libtcl(
Tcl_DeleteInterp(interp);
)
} else {
APPFS_DEBUG("Terminating thread with no interpreter");
}
appfs_call_libtcl(
Tcl_FinalizeThread();
)
return;
}
/*
* Command-line parsing tools
*/
static void appfs_print_help(FILE *channel) {
fprintf(channel, "Usage: {appfsd|mount.appfs} [-o <option>] [-dfsh] <cachedir> <mountpoint>\n");
fprintf(channel, "\n");
fprintf(channel, "Options:\n");
fprintf(channel, " -d Enable FUSE debug mode.\n");
fprintf(channel, " -f Run in foreground.\n");
fprintf(channel, " -s Enable single threaded mode.\n");
fprintf(channel, " -h Give this help.\n");
fprintf(channel, " -o nothreads Enable single threaded mode.\n");
fprintf(channel, " -o allow_other Allow other users to access this mountpoint (default\n");
fprintf(channel, " if root).\n");
return;
}
static int appfs_opt_parse(int argc, char **argv, struct fuse_args *args) {
int ch;
char *optstr, *optstr_next, *optstr_s;
char fake_arg[3] = {'-', 0, 0};
/*
* Default values
*/
#ifdef TCL_THREADS
appfs_threaded_tcl = 1;
#else
appfs_threaded_tcl = 0;
#endif
/**
** Add FUSE arguments which we always supply
**/
fuse_opt_add_arg(args, "-odefault_permissions,fsname=appfs,subtype=appfsd,use_ino,entry_timeout=0,attr_timeout=0,big_writes,intr,hard_remove");
if (getuid() == 0) {
fuse_opt_parse(args, NULL, NULL, NULL);
fuse_opt_add_arg(args, "-oallow_other");
/*
* This should generally be avoided, but if there are security
* concerns suid can be disabled completely on the commandline
*/
fuse_opt_parse(args, NULL, NULL, NULL);
fuse_opt_add_arg(args, "-osuid");
}
while ((ch = getopt(argc, argv, "dfshvo:")) != -1) {
switch (ch) {
case 'v':
/* Ignored */
break;
case 'o':
optstr_next = optstr = optstr_s = strdup(optarg);
while (1) {
optstr = optstr_next;
if (!optstr) {
break;
}
optstr_next = strchr(optstr, ',');
if (optstr_next) {
*optstr_next = '\0';
optstr_next++;
}
if (strcmp(optstr, "nothreads") == 0) {
APPFS_DEBUG("Passing option to FUSE: -s");
fuse_opt_parse(args, NULL, NULL, NULL);
fuse_opt_add_arg(args, "-s");
appfs_threaded_tcl = 0;
} else if (strcmp(optstr, "allow_other") == 0) {
APPFS_DEBUG("Passing option to FUSE: -o allow_other");
fuse_opt_parse(args, NULL, NULL, NULL);
fuse_opt_add_arg(args, "-oallow_other");
} else if (strcmp(optstr, "rw") == 0) {
/* Ignored */
} else {
fprintf(stderr, "appfsd: invalid option: \"-o %s\"\n", optstr);
free(optstr_s);
return(1);
}
}
free(optstr_s);
break;
case 'd':
case 'f':
case 's':
if (ch == 's') {
appfs_threaded_tcl = 0;
}
fake_arg[1] = ch;
APPFS_DEBUG("Passing option to FUSE: %s", fake_arg);
fuse_opt_parse(args, NULL, NULL, NULL);
fuse_opt_add_arg(args, fake_arg);
break;
case 'h':
appfs_print_help(stdout);
return(-1);
case ':':
case '?':
default:
appfs_print_help(stderr);
return(1);
}
}
if ((optind + 2) != argc) {
if ((optind + 2) < argc) {
fprintf(stderr, "Too many arguments\n");
} else {
fprintf(stderr, "Missing cachedir or mountpoint\n");
}
appfs_print_help(stderr);
return(1);
}
/*
* Set cache dir as first argument (the "device", essentially)
*/
appfs_cachedir = argv[optind];
/*
* Pass the remaining argument to FUSE as the directory
*/
fuse_opt_parse(args, NULL, NULL, NULL);
fuse_opt_add_arg(args, argv[optind + 1]);
return(0);
}
/*
* FUSE operations structure
*/
static struct fuse_operations appfs_operations = {
.getattr = appfs_fuse_getattr,
.readdir = appfs_fuse_readdir,
.readlink = appfs_fuse_readlink,
.open = appfs_fuse_open,
.release = appfs_fuse_close,
.read = appfs_fuse_read,
.write = appfs_fuse_write,
.mknod = appfs_fuse_mknod,
.create = appfs_fuse_create,
.truncate = appfs_fuse_truncate,
.unlink = appfs_fuse_unlink_rmdir,
.rmdir = appfs_fuse_unlink_rmdir,
.mkdir = appfs_fuse_mkdir,
.chmod = appfs_fuse_chmod,
.symlink = appfs_fuse_symlink,
};
/*
* Entry point into this program.
*/
int main(int argc, char **argv) {
Tcl_Interp *test_interp;
char *test_interp_error;
struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
struct rlimit number_open_files;
int pthread_ret, aop_ret, rlimit_ret;
void *signal_ret;
char *argv0;
rlim_t number_open_files_max;
/*
* Skip passed program name
*/
if (argc == 0 || argv == NULL) {
return(1);
}
argv0 = argv[0];
argc--;
argv++;
/*
* Set appropriate umask
*/
umask(022);
/*
* Set global variables, these should be configuration options.
*/
appfs_cachedir = APPFS_CACHEDIR;
/*
* Set global variable for "boot time" to set a time on directories
* that we fake.
*/
appfs_boottime = time(NULL);
/*
* Register "sha1" and "appfsd" package with libtcl so that any new
* interpreters created (which are done dynamically by FUSE) can have
* the appropriate configuration done automatically.
*/
Tcl_StaticPackage(NULL, "sha1", Sha1_Init, NULL);
Tcl_StaticPackage(NULL, "appfsd", Appfsd_Init, NULL);
/*
* Create a thread-specific-data (TSD) key for each thread to refer
* to its own Tcl interpreter. Tcl interpreters must be unique per
* thread and new threads are dynamically created by FUSE.
*/
pthread_ret = pthread_key_create(&interpKey, appfs_terminate_interp_and_thread);
if (pthread_ret != 0) {
fprintf(stderr, "Unable to create TSD key for Tcl. Aborting.\n");
return(1);
}
/*
* Manually specify cache directory, without FUSE callback
* This option only works when not using FUSE, since we
* do not process it with FUSEs option processing.
*/
if (argc >= 2) {
if (strcmp(argv[0], "--cachedir") == 0) {
appfs_cachedir = strdup(argv[1]);
argc -= 2;
argv += 2;
}
}
/*
* SQLite3 mode, for running raw SQL against the cache database
*/
if (argc == 2 && strcmp(argv[0], "--sqlite3") == 0) {
return(appfs_sqlite3(argv[1]));
}
/*
* Tcl mode, for running raw Tcl in the same environment AppFSd would
* run code.
*/
if (argc == 2 && strcmp(argv[0], "--tcl") == 0) {
return(appfs_tcl(argv[1]));
}
/*
* Register a signal handler for hot-restart requests
*/
signal_ret = signal(SIGHUP, appfs_signal_handler);
if (signal_ret == SIG_ERR) {
fprintf(stderr, "Unable to install signal handler for hot-restart\n");
fprintf(stderr, "Hot-restart will not be available.\n");
}
/*
* Parse command line arguments
*/
/**
** Restore argc/argv to original values, replacing argv[0] in case
** it was moified by --cachedir option.
**/
argc++;
argv--;
argv[0] = argv0;
/**
** Perform the argument parsing
**/
aop_ret = appfs_opt_parse(argc, argv, &args);
if (aop_ret != 0) {
if (aop_ret < 0) {
return(0);
}
return(aop_ret);
}
/*
* Create a Tcl interpreter just to verify that things are in working
* order before we become a daemon.
*/
test_interp = appfs_create_TclInterp(&test_interp_error);
if (test_interp == NULL) {
if (test_interp_error == NULL) {
test_interp_error = "Unknown error";
}
fprintf(stderr, "Unable to initialize Tcl interpreter for AppFSd:\n");
fprintf(stderr, "%s\n", test_interp_error);
return(1);
}
Tcl_DeleteInterp(test_interp);
if (appfs_threaded_tcl) {
Tcl_FinalizeNotifier(NULL);
}
/*
* Increase resource limits for number of open files
* to the maximum values, since we may be asked to
* hold open many files on behalf of many other processes
*/
number_open_files.rlim_cur = number_open_files.rlim_max = RLIM_INFINITY;
rlimit_ret = setrlimit(RLIMIT_NOFILE, &number_open_files);
if (rlimit_ret != 0) {
rlimit_ret = getrlimit(RLIMIT_NOFILE, &number_open_files);
if (rlimit_ret == 0) {
number_open_files_max = number_open_files.rlim_max;
if (number_open_files_max < (1024 * 1024)) {
number_open_files.rlim_cur = number_open_files.rlim_max = 1024 * 1024;
rlimit_ret = setrlimit(RLIMIT_NOFILE, &number_open_files);
} else {
number_open_files.rlim_cur = number_open_files.rlim_max;
}
rlimit_ret = setrlimit(RLIMIT_NOFILE, &number_open_files);
if (rlimit_ret != 0 && number_open_files.rlim_cur != number_open_files_max) {
number_open_files.rlim_cur = number_open_files.rlim_max = number_open_files_max;
setrlimit(RLIMIT_NOFILE, &number_open_files);
}
}
}
/*
* Enter the FUSE main loop -- this will process any arguments
* and start servicing requests.
*/
appfs_fuse_started = 1;
return(fuse_main(args.argc, args.argv, &appfs_operations, NULL));
}