From 5b98dd1c37a6151e734e8fa3a7054248ae3455c6 Mon Sep 17 00:00:00 2001 From: Jeremy Tan Date: Sat, 19 Oct 2013 15:45:30 +0800 Subject: [PATCH] Use experiment folders instead of chdir --- server/actuator.c | 16 ++-- server/common.h | 1 + server/control.c | 192 ++++++++++++++++++++++++++-------------------- server/control.h | 4 +- server/fastcgi.c | 47 ++++++++---- server/fastcgi.h | 6 +- server/main.c | 21 +++-- server/options.h | 7 +- server/sensor.c | 11 ++- 9 files changed, 180 insertions(+), 125 deletions(-) diff --git a/server/actuator.c b/server/actuator.c index 9b9a1a8..2be4194 100644 --- a/server/actuator.c +++ b/server/actuator.c @@ -8,9 +8,6 @@ // Files containing GPIO and PWM definitions #include "bbb_pin.h" - - - /** Number of actuators **/ int g_num_actuators = 0; @@ -79,12 +76,17 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg) { case CONTROL_START: { + // Set filename char filename[BUFSIZ]; - const char *experiment_name = (const char*) arg; + const char *experiment_path = (const char*) arg; + int ret; + + ret = snprintf(filename, BUFSIZ, "%s/actuator_%d", experiment_path, a->id); - if (snprintf(filename, BUFSIZ, "%s_a%d", experiment_name, a->id) >= BUFSIZ) + if (ret >= BUFSIZ) { - Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); + Fatal("Experiment path \"%s\" too long (%d, limit %d)", + experiment_path, ret, BUFSIZ); } Log(LOGDEBUG, "Actuator %d with DataFile \"%s\"", a->id, filename); @@ -141,7 +143,7 @@ void Actuator_SetMode(Actuator * a, ControlModes mode, void *arg) */ void Actuator_SetModeAll(ControlModes mode, void * arg) { - for (int i = 0; i < ACTUATORS_MAX; i++) + for (int i = 0; i < g_num_actuators; i++) Actuator_SetMode(&g_actuators[i], mode, arg); } diff --git a/server/common.h b/server/common.h index d4defd9..fa997c5 100644 --- a/server/common.h +++ b/server/common.h @@ -46,6 +46,7 @@ extern bool PathExists(const char * path); +extern bool DirExists(const char * path); diff --git a/server/control.c b/server/control.c index c6b5823..59f9b73 100644 --- a/server/control.c +++ b/server/control.c @@ -8,16 +8,30 @@ #include "sensor.h" #include "actuator.h" #include +#include +#include typedef struct ControlData { ControlModes current_mode; pthread_mutex_t mutex; struct timeval start_time; char user_name[31]; // The user who owns the currently running experiment + char experiment_dir[BUFSIZ]; //Directory for experiment + char experiment_name[BUFSIZ]; } ControlData; ControlData g_controls = {CONTROL_STOP, PTHREAD_MUTEX_INITIALIZER, {0}}; +bool DirExists(const char *path) +{ + DIR *dir = opendir(path); + if (dir) { + closedir(dir); + return true; + } + return false; +} + bool PathExists(const char *path) { FILE *fp = fopen(path, "r"); @@ -28,6 +42,43 @@ bool PathExists(const char *path) return false; } +/** + * Lists all experiments for the current user. + * @param The context to work in + */ +void ListExperiments(FCGIContext *context) +{ + DIR * dir = opendir(context->user_dir); + if (dir == NULL) + { + FCGI_RejectJSON(context, "Failed to open user directory"); + return; + } + struct dirent * ent; + FCGI_BeginJSON(context, STATUS_OK); + FCGI_JSONKey("experiments"); + FCGI_PrintRaw("["); + + bool first = true; + while ((ent = readdir(dir)) != NULL) { + char *ext = strrchr(ent->d_name, '.'); + if (ext && !strcmp(ext, ".exp")) { + if (!first) { + FCGI_PrintRaw(", "); + } + + *ext = '\0'; // Ummm... probably not a great idea + FCGI_PrintRaw("\"%s\"", ent->d_name); + first = false; + } + } + FCGI_PrintRaw("]"); + FCGI_EndJSON(); + + closedir(dir); + return; +} + /** * System control handler. This covers high-level control, including * admin related functions and starting/stopping experiments.. @@ -40,10 +91,6 @@ void Control_Handler(FCGIContext *context, char *params) { bool force = false; ControlModes desired_mode; - - - - // Login/auth now handled entirely in fastcgi.c and login.c //TODO: Need to not have the ability for any user to stop someone else' experiment... // (achieve by storing the username of the person running the current experiment, even when they log out?) @@ -54,43 +101,19 @@ void Control_Handler(FCGIContext *context, char *params) { {"name", &name, FCGI_STRING_T} }; - if (!FCGI_ParseRequest(context, params, values, 3)) return; - //HACKETY HACK HACK (should really be a seperate function) - if (strcmp(action, "list") == 0) - { - DIR * dir = opendir(context->user_name); - if (dir == NULL) - { - FCGI_RejectJSON(context, "Failed to open user directory"); - return; - } - struct dirent * ent; + if (!strcmp(action, "identify")) { FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONKey("experiments"); - FCGI_PrintRaw("["); - - bool first = true; - while ((ent = readdir(dir)) != NULL) - { - char * c; - for (c = ent->d_name; *c != '\0' && *c != '.'; ++c); - - if (*c != '\0' && strcmp(c, ".exp") == 0) - { - if (!first) - FCGI_PrintRaw(","); - *c = '\0'; // Ummm... probably not a great idea - FCGI_PrintRaw(ent->d_name); - first = false; - } - } - FCGI_PrintRaw("]"); + FCGI_JSONLong("control_state_id", g_controls.current_mode); + FCGI_JSONPair("control_user_name", g_controls.user_name); + FCGI_JSONPair("control_experiment_name", g_controls.experiment_name); FCGI_EndJSON(); - - return; // Dear god this is terrible + return; + } else if (!strcmp(action, "list")) { + ListExperiments(context); + return; } //TODO: Need a "load" action to set data files (but not run) from a past experiment @@ -111,67 +134,71 @@ void Control_Handler(FCGIContext *context, char *params) { return; } - if (*g_controls.user_name != '\0' && strcmp(g_controls.user_name,context->user_name) != 0) + if (strcmp(g_controls.user_name, context->user_name) != 0) { - if (context->user_type != USER_ADMIN) - { + if (context->user_type != USER_ADMIN) { FCGI_RejectJSON(context, "Another user has an experiment in progress."); return; } - if (!force) - { + if (!force) { Log(LOGERR, "User %s is currently running an experiment!", g_controls.user_name); FCGI_RejectJSON(context, "Pass \"force\" to take control over another user's experiment"); return; } } - - - //HACK - chdir(context->user_name); - void *arg = NULL; + char experiment_dir[BUFSIZ] = {0}; if (desired_mode == CONTROL_START) { - if (PathExists(name) && !force) { - FCGI_RejectJSON(context, "An experiment with that name already exists."); - chdir(g_options.root_dir); // REVERSE HACK + int ret; + + if (*name == '\0') { + FCGI_RejectJSON(context, "An experiment name must be specified."); + return; + } if (strpbrk(name, INVALID_CHARACTERS)) { + FCGI_RejectJSON(context, + "An experiment name must not contain: " INVALID_CHARACTERS_JSON); return; } - char * c = (char*)name; - for (c = (char*)name; *c != '\0' && *c != '.'; ++c); - if (*c == '.') - { - FCGI_RejectJSON(context, "Can't include '.' characters in experiment names (at this point we don't care anymore, go find someone who does)."); - chdir(g_options.root_dir); // REVERSE HACK + + if (*(context->user_dir) == '\0') { + FCGI_RejectJSON(context, "Not creating experiment in root dir."); return; } - arg = (void*)name; - } - + ret = snprintf(experiment_dir, BUFSIZ, "%s/%s.exp", + context->user_dir, name); + if (ret >= BUFSIZ) { + FCGI_RejectJSON(context, "The experiment name is too long."); + return; + } else if (DirExists(experiment_dir) && !force) { + FCGI_RejectJSON(context, "An experiment with that name already exists."); + return; + } + + arg = (void*) experiment_dir; + } const char *ret; - if ((ret = Control_SetMode(desired_mode, arg)) != NULL) - { + if ((ret = Control_SetMode(desired_mode, arg)) != NULL) { FCGI_RejectJSON(context, ret); - } - else - { - if (desired_mode == CONTROL_STOP) + } else { + if (desired_mode == CONTROL_STOP) { g_controls.user_name[0] = '\0'; - else - { - snprintf(g_controls.user_name, sizeof(g_controls.user_name), context->user_name); + g_controls.experiment_dir[0] = '\0'; + g_controls.experiment_name[0] = '\0'; + } else if (desired_mode == CONTROL_START) { + snprintf(g_controls.user_name, sizeof(g_controls.user_name), + "%s", context->user_name); + snprintf(g_controls.experiment_dir, sizeof(g_controls.experiment_dir), + "%s", experiment_dir); + snprintf(g_controls.experiment_name, sizeof(g_controls.experiment_name), + "%s", name); } - FCGI_BeginJSON(context, STATUS_OK); - FCGI_JSONPair("description", "ok"); - FCGI_EndJSON(); - } - //REVERSE HACK - chdir(g_options.root_dir); + FCGI_AcceptJSON(context, "Ok", NULL); + } } /** @@ -192,18 +219,13 @@ const char* Control_SetMode(ControlModes desired_mode, void * arg) else switch (desired_mode) { case CONTROL_START: if (g_controls.current_mode == CONTROL_STOP) { - const char * name = arg; - if (!*name) - ret = "An experiment name must be specified"; - else if (strpbrk(name, INVALID_CHARACTERS)) - ret = "The experiment name must not contain: " INVALID_CHARACTERS_JSON; - else { - FILE *fp = fopen((const char*) arg, "a"); - if (fp) { - fclose(fp); - gettimeofday(&(g_controls.start_time), NULL); - } else - ret = "Cannot open experiment name marker"; + const char * path = arg; + if (mkdir(path, 0777) != 0 && errno != EEXIST) { + Log(LOGERR, "Couldn't create experiment directory %s - %s", + path, strerror(errno)); + ret = "Couldn't create experiment directory."; + } else { + gettimeofday(&(g_controls.start_time), NULL); } } else ret = "Cannot start when not in a stopped state."; diff --git a/server/control.h b/server/control.h index 7b3511a..3ceea9f 100644 --- a/server/control.h +++ b/server/control.h @@ -14,9 +14,9 @@ typedef enum ControlModes { } ControlModes; /** Invalid filename characters **/ -#define INVALID_CHARACTERS "\"*/:<>?\\|" +#define INVALID_CHARACTERS "\"*/:<>?\\|. " /** The same as INVALID_CHARACTERS, except escaped for use in JSON strings **/ -#define INVALID_CHARACTERS_JSON "\\\"*/:<>?\\\\|" +#define INVALID_CHARACTERS_JSON "\\\"*/:<>?\\\\|. " extern void Control_Handler(FCGIContext *context, char *params); extern const char* Control_SetMode(ControlModes desired_mode, void * arg); diff --git a/server/fastcgi.c b/server/fastcgi.c index a77a3a4..fd5839e 100644 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@ -35,9 +35,13 @@ */ static void IdentifyHandler(FCGIContext *context, char *params) { bool ident_sensors = false, ident_actuators = false; - bool has_control = FCGI_HasControl(context, getenv("COOKIE_STRING")); + char control_key[CONTROL_KEY_BUFSIZ]; + bool has_control; int i; + snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING")); + has_control = FCGI_HasControl(context, control_key); + FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T}, {"actuators", &ident_actuators, FCGI_BOOL_T}}; if (!FCGI_ParseRequest(context, params, values, 2)) @@ -89,6 +93,7 @@ bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType use // Get current time time_t now = time(NULL); bool expired = now - context->control_timestamp > CONTROL_TIMEOUT; + int i; // Can't lock control if: User not actually logged in (sanity), or key is still valid and the user is not an admin if (user_type == USER_UNAUTH || @@ -104,7 +109,7 @@ bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType use // Generate a SHA1 hash for the user SHA_CTX sha1ctx; unsigned char sha1[20]; - int i = rand(); + i = rand(); SHA1_Init(&sha1ctx); SHA1_Update(&sha1ctx, &now, sizeof(now)); SHA1_Update(&sha1ctx, &i, sizeof(i)); @@ -112,25 +117,38 @@ bool FCGI_LockControl(FCGIContext *context, const char * user_name, UserType use for (i = 0; i < sizeof(sha1); i++) sprintf(context->control_key + i * 2, "%02x", sha1[i]); - // Set the IP address + // Set the IPv4 address snprintf(context->control_ip, 16, "%s", getenv("REMOTE_ADDR")); + // Set the user name int uname_len = strlen(user_name); - if (snprintf(context->user_name, sizeof(context->user_name), "%s", user_name) < uname_len) - { - Log(LOGERR, "Username at %d characters too long (limit %d)", uname_len, sizeof(context->user_name)); + i = snprintf(context->user_name, sizeof(context->user_name), "%s", user_name); + if (i < uname_len) { + Log(LOGERR, "Username at %d characters too long (limit %d)", + uname_len, sizeof(context->user_name)); return false; // :-( } // Set the user type context->user_type = user_type; + + // Build the user directory + i = snprintf(context->user_dir, sizeof(context->user_dir), "%s/%s", + g_options.experiment_dir, context->user_name); + if (i >= sizeof(context->user_dir)) { + Log(LOGERR, "Experiment dir too long (required %d, limit %d)", + i, sizeof(context->user_dir)); + return false; + } + + Log(LOGDEBUG, "User dir: %s", context->user_dir); // Create directory - if (mkdir(user_name, 0777) != 0 && errno != EEXIST) + if (mkdir(context->user_dir, 0777) != 0 && errno != EEXIST) { - Log(LOGERR, "Couldn't create user directory %s/%s - %s", g_options.root_dir, user_name, strerror(errno)); + Log(LOGERR, "Couldn't create user directory %s - %s", + context->user_dir, strerror(errno)); return false; // :-( } - return true; // :-) } @@ -491,16 +509,17 @@ void * FCGI_RequestLoop (void *data) while (FCGI_Accept() >= 0) { ModuleHandler module_handler = NULL; - char module[BUFSIZ], params[BUFSIZ]; - //Don't need to copy if we're not modifying string contents - const char *cookie = getenv("COOKIE_STRING"); + char module[BUFSIZ], params[BUFSIZ], control_key[CONTROL_KEY_BUFSIZ]; //strncpy doesn't zero-truncate properly snprintf(module, BUFSIZ, "%s", getenv("DOCUMENT_URI_LOCAL")); snprintf(params, BUFSIZ, "%s", getenv("QUERY_STRING")); + //Hack to get the nameless cookie only + snprintf(control_key, CONTROL_KEY_BUFSIZ, "%s", getenv("COOKIE_STRING")); + Log(LOGDEBUG, "Got request #%d - Module %s, params %s", context.response_number, module, params); - Log(LOGDEBUG, "Cookie: %s", cookie); + Log(LOGDEBUG, "Control key: %s", control_key); //Remove trailing slashes (if present) from module query @@ -538,7 +557,7 @@ void * FCGI_RequestLoop (void *data) if (module_handler != Login_Handler && module_handler != IdentifyHandler && module_handler) //if (false) // Testing { - if (!FCGI_HasControl(&context, cookie)) + if (!FCGI_HasControl(&context, control_key)) { FCGI_RejectJSON(&context, "Please login. Invalid control key."); continue; diff --git a/server/fastcgi.h b/server/fastcgi.h index 85dab57..de02501 100644 --- a/server/fastcgi.h +++ b/server/fastcgi.h @@ -33,6 +33,8 @@ typedef enum StatusCodes { #define FCGI_RECEIVED(x) ((x) & FCGI_PARAM_RECEIVED) #define FCGI_TYPE(x) ((x) & ~(FCGI_PARAM_REQUIRED | FCGI_PARAM_RECEIVED)) +#define CONTROL_KEY_BUFSIZ 41 + typedef struct FCGIValue { const char *key; void *value; @@ -47,13 +49,15 @@ typedef struct /**The time of last valid user access possessing the control key**/ time_t control_timestamp; /**A SHA-1 hash that is the control key, determining who is logged in**/ - char control_key[41]; + char control_key[CONTROL_KEY_BUFSIZ]; /**The IPv4 address of the logged-in user**/ char control_ip[16]; /**Determines if the user is an admin or not**/ UserType user_type; /**Name of the logged in user**/ char user_name[31]; + /**User directory for the logged in user**/ + char user_dir[BUFSIZ]; /**The name of the current module**/ const char *current_module; /**For debugging purposes?**/ diff --git a/server/main.c b/server/main.c index 7933bab..e7edc7e 100644 --- a/server/main.c +++ b/server/main.c @@ -29,13 +29,11 @@ Options g_options; // options passed to program through command line arguments */ void ParseArguments(int argc, char ** argv) { - - g_options.program = argv[0]; // program name g_options.verbosity = LOGDEBUG; // default log level // Set the main directory - if (getcwd(g_options.root_dir, sizeof(g_options.root_dir)) == NULL) - Fatal("Couldn't get current working directory - %s", strerror(errno)); + //if (getcwd(g_options.root_dir, sizeof(g_options.root_dir)) == NULL) + // Fatal("Couldn't get current working directory - %s", strerror(errno)); gettimeofday(&(g_options.start_time), NULL); // Start time @@ -43,9 +41,8 @@ void ParseArguments(int argc, char ** argv) g_options.auth_method = AUTH_NONE; // Don't use authentication g_options.auth_uri = ""; // g_options.ldap_base_dn = ""; + g_options.experiment_dir = "."; - - for (int i = 1; i < argc; ++i) { if (argv[i][0] != '-') @@ -76,6 +73,10 @@ void ParseArguments(int argc, char ** argv) case 'd': g_options.ldap_base_dn = argv[++i]; break; + case 'e': + // Experiments directory + g_options.experiment_dir = argv[++i]; + break; default: Fatal("Unrecognised switch %s", argv[i]); break; @@ -89,7 +90,13 @@ void ParseArguments(int argc, char ** argv) Log(LOGDEBUG, "Pin Module Enabled: %d", g_options.enable_pin); Log(LOGDEBUG, "Auth URI: %s", g_options.auth_uri); Log(LOGDEBUG, "LDAP Base DN: %s", g_options.ldap_base_dn); - Log(LOGDEBUG, "Root directory: %s", g_options.root_dir); + //Log(LOGDEBUG, "Root directory: %s", g_options.root_dir); + Log(LOGDEBUG, "Experiment directory: %s", g_options.experiment_dir); + + if (!DirExists(g_options.experiment_dir)) + { + Fatal("Experiment directory '%s' does not exist.", g_options.experiment_dir); + } if (g_options.auth_uri[0] != '\0') { diff --git a/server/options.h b/server/options.h index bf668b3..385a05b 100644 --- a/server/options.h +++ b/server/options.h @@ -31,11 +31,8 @@ typedef struct /** Authentication method **/ enum {AUTH_NONE, AUTH_LDAP, AUTH_SHADOW} auth_method; - /** Starting directory **/ - char root_dir[BUFSIZ]; - - - + /** Experiments directory **/ + const char *experiment_dir; } Options; /** The only instance of the Options struct **/ diff --git a/server/sensor.c b/server/sensor.c index e136988..c41d39e 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -120,11 +120,15 @@ void Sensor_SetMode(Sensor * s, ControlModes mode, void * arg) { // Set filename char filename[BUFSIZ]; - const char *experiment_name = (const char*) arg; + const char *experiment_path = (const char*) arg; + int ret; + + ret = snprintf(filename, BUFSIZ, "%s/sensor_%d", experiment_path, s->id); - if (snprintf(filename, BUFSIZ, "%s_%d", experiment_name, s->id) >= BUFSIZ) + if (ret >= BUFSIZ) { - Fatal("Experiment name \"%s\" too long (>%d)", experiment_name, BUFSIZ); + Fatal("Experiment path \"%s\" too long (%d, limit %d)", + experiment_path, ret, BUFSIZ); } Log(LOGDEBUG, "Sensor %d with DataFile \"%s\"", s->id, filename); @@ -291,7 +295,6 @@ void Sensor_Handler(FCGIContext *context, char * params) struct timeval now; gettimeofday(&now, NULL); double current_time = TIMEVAL_DIFF(now, *Control_GetStartTime()); - int id = 0; const char * name = ""; double start_time = 0; -- 2.20.1