edited dilatometer
[matches/MCTX3420.git] / server / control.c
1 /**
2  * @file control.c
3  * @brief Handles all client control requests (admin related)
4  */
5 #include "common.h"
6 #include "options.h"
7 #include "control.h"
8 #include "sensor.h"
9 #include "actuator.h"
10 #include <dirent.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13
14 typedef struct ControlData {
15         ControlModes current_mode;
16         pthread_mutex_t mutex;
17         struct timespec start_time;
18         char user_name[31]; // The user who owns the currently running experiment
19         char experiment_dir[BUFSIZ]; //Directory for experiment
20         char experiment_name[BUFSIZ];
21 } ControlData;
22
23 ControlData g_controls = {CONTROL_STOP, PTHREAD_MUTEX_INITIALIZER, {0}};
24
25 bool DirExists(const char *path)
26 {
27         DIR *dir = opendir(path);
28         if (dir) {
29                 closedir(dir);
30                 return true;
31         }
32         return false;
33 }
34
35 bool PathExists(const char *path) 
36 {
37         FILE *fp = fopen(path, "r");
38         if (fp) {
39                 fclose(fp);
40                 return true;
41         }
42         return false;
43 }
44
45 /**
46  * Lists all experiments for the current user.
47  * @param The context to work in
48  */
49 void ListExperiments(FCGIContext *context) 
50 {
51         DIR * dir = opendir(context->user_dir);
52         if (dir == NULL)
53         {
54                 FCGI_RejectJSON(context, "Failed to open user directory");
55                 return;
56         }
57         struct dirent * ent;
58         FCGI_BeginJSON(context, STATUS_OK);
59         FCGI_JSONKey("experiments");
60         FCGI_PrintRaw("[");
61
62         bool first = true;
63         while ((ent = readdir(dir)) != NULL) {
64                 char *ext = strrchr(ent->d_name, '.');
65                 if (ext && !strcmp(ext, ".exp")) {
66                         if (!first) {
67                                 FCGI_PrintRaw(", ");
68                         }
69
70                         *ext = '\0'; // Ummm... probably not a great idea
71                         FCGI_PrintRaw("\"%s\"", ent->d_name);
72                         first = false;
73                 }
74         }
75         FCGI_PrintRaw("]");
76         FCGI_EndJSON();
77         
78         closedir(dir);
79         return;
80 }
81
82 /**
83  * System control handler. This covers high-level control, including
84  * admin related functions and starting/stopping experiments..
85  * @param context The context to work in
86  * @param params The input parameters
87  */
88 void Control_Handler(FCGIContext *context, char *params) {
89         const char *action = "";
90         const char *name = "";
91         bool force = false;
92         ControlModes desired_mode;
93         
94         // Login/auth now handled entirely in fastcgi.c and login.c
95         //TODO: Need to not have the ability for any user to stop someone else' experiment...
96         // (achieve by storing the username of the person running the current experiment, even when they log out?)
97         // (Our program should only realisitically support a single experiment at a time, so that should be sufficient)
98         FCGIValue values[3] = {
99                 {"action", &action, FCGI_REQUIRED(FCGI_STRING_T)},
100                 {"force", &force, FCGI_BOOL_T},
101                 {"name", &name, FCGI_STRING_T}
102         };
103
104         if (!FCGI_ParseRequest(context, params, values, 3))
105                 return;
106
107         if (!strcmp(action, "identify")) {
108                 FCGI_BeginJSON(context, STATUS_OK);
109                 FCGI_JSONLong("control_state_id", g_controls.current_mode);
110                 FCGI_JSONPair("control_user_name", g_controls.user_name);
111                 FCGI_JSONPair("control_experiment_name", g_controls.experiment_name);
112                 FCGI_EndJSON();
113                 return;
114         } else if (!strcmp(action, "list")) {
115                 ListExperiments(context);
116                 return;
117         }
118         //TODO: Need a "load" action to set data files (but not run) from a past experiment
119
120         //TODO: Need a "delete" action so that people can overwrite experiments (without all this "force" shenanigans)
121         
122         if (!strcmp(action, "emergency")) {
123                 desired_mode = CONTROL_EMERGENCY;
124         } else if (!strcmp(action, "start")) {
125                 desired_mode = CONTROL_START;
126         } else if (!strcmp(action, "pause")) {
127                 desired_mode = CONTROL_PAUSE;
128         } else if (!strcmp(action, "resume")) {
129                 desired_mode = CONTROL_RESUME;
130         } else if (!strcmp(action, "stop")) {
131                 desired_mode = CONTROL_STOP; 
132         } else {
133                 FCGI_RejectJSON(context, "Unknown action specified.");
134                 return;
135         }
136
137         if ((*g_controls.user_name) != '\0' && strcmp(g_controls.user_name, context->user_name) != 0)
138         {
139                 if (context->user_type != USER_ADMIN) {
140                         FCGI_RejectJSON(context, "Another user has an experiment in progress.");
141                         return;
142                 }
143                 
144                 if (!force) {
145                         Log(LOGERR, "User %s is currently running an experiment!", g_controls.user_name);
146                         FCGI_RejectJSON(context, "Pass \"force\" to take control over another user's experiment");
147                         return;
148                 }
149         }
150
151         void *arg = NULL;
152         char experiment_dir[BUFSIZ] = {0};
153         if (desired_mode == CONTROL_START) {
154                 int ret;
155
156                 if (*name == '\0') {
157                         FCGI_RejectJSON(context, "An experiment name must be specified.");
158                         return;
159                 } if (strpbrk(name, INVALID_CHARACTERS)) {
160                         FCGI_RejectJSON(context, 
161                                 "An experiment name must not contain: " INVALID_CHARACTERS_JSON);
162                         return;
163                 }
164
165                 if (*(context->user_dir) == '\0') {
166                         FCGI_RejectJSON(context, "Not creating experiment in root dir.");
167                         return;
168                 }
169
170                 ret = snprintf(experiment_dir, BUFSIZ, "%s/%s.exp", 
171                                                 context->user_dir, name);
172                 if (ret >= BUFSIZ) {
173                         FCGI_RejectJSON(context, "The experiment name is too long.");
174                         return;
175                 } else if (DirExists(experiment_dir) && !force) {
176                         FCGI_RejectJSON(context, "An experiment with that name already exists.");
177                         return;
178                 }
179
180                 arg = (void*) experiment_dir;
181         }
182
183         const char *ret;
184         if ((ret = Control_SetMode(desired_mode, arg)) != NULL) {
185                 FCGI_RejectJSON(context, ret);
186         } else {
187                 if (desired_mode == CONTROL_STOP) {
188                         g_controls.user_name[0] = '\0';
189                         g_controls.experiment_dir[0] = '\0';
190                         g_controls.experiment_name[0] = '\0';
191                 } else if (desired_mode == CONTROL_START) {
192                         snprintf(g_controls.user_name, sizeof(g_controls.user_name), 
193                                                 "%s", context->user_name);
194                         snprintf(g_controls.experiment_dir, sizeof(g_controls.experiment_dir),
195                                                 "%s", experiment_dir);
196                         snprintf(g_controls.experiment_name, sizeof(g_controls.experiment_name),
197                                                 "%s", name);
198                 }
199
200                 FCGI_AcceptJSON(context, "Ok");
201         }
202 }
203
204 /**
205  * Sets the mode to the desired mode, if possible.
206  * @param desired_mode The mode to be set to
207  * @param arg An argument specific to the mode to be set. 
208  * @return NULL on success, an error message on failure.
209  */
210 const char* Control_SetMode(ControlModes desired_mode, void * arg)
211 {
212         const char *ret = NULL;
213
214         pthread_mutex_lock(&(g_controls.mutex));
215         if (g_controls.current_mode == desired_mode)
216                 ret = "Already in the desired mode.";
217         else if (g_controls.current_mode == CONTROL_EMERGENCY && desired_mode != CONTROL_STOP)
218                 ret = "In emergency mode. You must stop before continuing.";
219         else switch (desired_mode) {
220                 case CONTROL_START:
221                         if (g_controls.current_mode == CONTROL_STOP) {
222                                 const char * path = arg;
223                                 if (mkdir(path, 0777) != 0 && errno != EEXIST) {
224                                         Log(LOGERR, "Couldn't create experiment directory %s - %s", 
225                                                 path, strerror(errno));
226                                         ret = "Couldn't create experiment directory.";
227                                 } else {
228                                         clock_gettime(CLOCK_MONOTONIC, &(g_controls.start_time));
229                                 }
230                         } else 
231                                 ret = "Cannot start when not in a stopped state.";
232                 break;
233                 case CONTROL_PAUSE:
234                         if (g_controls.current_mode != CONTROL_START)
235                                 ret = "Cannot pause when not in a running state.";
236                 break;
237                 case CONTROL_RESUME:
238                         if (g_controls.current_mode != CONTROL_PAUSE)
239                                 ret = "Cannot resume when not in a paused state.";
240                 break;
241                 case CONTROL_EMERGENCY:
242                         if (g_controls.current_mode != CONTROL_START) //pfft
243                                 ret = "Not running so how can there be an emergency?";
244                 break;
245                 default:
246                 break;
247         }
248         
249         if (ret == NULL) {
250                 Actuator_SetModeAll(desired_mode, arg);
251                 Sensor_SetModeAll(desired_mode, arg);
252                 if (desired_mode != CONTROL_RESUME)
253                         g_controls.current_mode = desired_mode;
254                 else
255                         g_controls.current_mode = CONTROL_START;
256         }
257         pthread_mutex_unlock(&(g_controls.mutex));
258         return ret;
259 }
260
261 /**
262  * Gets a string representation of the current mode
263  * @param mode The mode to get a string representation of
264  * @return The string representation of the mode
265  */
266 const char * Control_GetModeName() {
267         const char * ret = "Unknown";
268
269         switch (g_controls.current_mode) {
270                 case CONTROL_START: ret = "Running"; break;
271                 case CONTROL_PAUSE: ret = "Paused"; break;
272                 case CONTROL_RESUME: ret = "Resumed"; break;
273                 case CONTROL_STOP: ret = "Stopped"; break;
274                 case CONTROL_EMERGENCY: ret = "Emergency mode"; break;
275         }
276         return ret;
277 }
278
279 /**
280  * Gets the start time for the current experiment
281  * @return the start time
282  */
283 const struct timespec * Control_GetStartTime() {
284         return &g_controls.start_time;
285 }

UCC git Repository :: git.ucc.asn.au