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

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