style
[psensor.git] / src / server / server.c
1 /*
2  * Copyright (C) 2010-2014 jeanfi@gmail.com
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  * 02110-1301 USA
18  */
19 #define _LARGEFILE_SOURCE 1
20 #include "config.h"
21
22 #include <locale.h>
23 #include <libintl.h>
24 #define _(str) gettext(str)
25
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <sys/select.h>
33 #include <sys/socket.h>
34 #include <getopt.h>
35 #include <stdint.h>
36 #include <pthread.h>
37 #include <unistd.h>
38 #include <microhttpd.h>
39
40 #ifdef HAVE_GTOP
41 #include "sysinfo.h"
42 #include <pgtop2.h>
43 #endif
44
45 #include <hdd.h>
46 #include <lmsensor.h>
47 #include <plog.h>
48 #include "psensor_json.h"
49 #include <pmutex.h>
50 #include "url.h"
51 #include "server.h"
52 #include "slog.h"
53
54 static const char *DEFAULT_LOG_FILE = "/var/log/psensor-server.log";
55
56 #define HTML_STOP_REQUESTED \
57 (_("<html><body><p>Server stop requested</p></body></html>"))
58
59 static const char *program_name;
60
61 static const int DEFAULT_PORT = 3131;
62
63 #define PAGE_NOT_FOUND (_("<html><body><p>"\
64 "Page not found - Go to <a href='/'>Main page</a></p></body>"))
65
66 static struct option long_options[] = {
67         {"version", no_argument, NULL, 'v'},
68         {"help", no_argument, NULL, 'h'},
69         {"port", required_argument, NULL, 'p'},
70         {"wdir", required_argument, NULL, 'w'},
71         {"debug", required_argument, NULL, 'd'},
72         {"log-file", required_argument, NULL, 'l'},
73         {"sensor-log-file", required_argument, NULL, 0},
74         {"sensor-log-interval", required_argument, NULL, 0},
75         {NULL, 0, NULL, 0}
76 };
77
78 static struct server_data server_data;
79
80 static pthread_mutex_t mutex;
81
82 static int server_stop_requested;
83
84 static void print_version()
85 {
86         printf("psensor-server %s\n", VERSION);
87         printf(_("Copyright (C) %s jeanfi@gmail.com\n"
88                  "License GPLv2: GNU GPL version 2 or later "
89                  "<http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>\n"
90                  "This is free software: you are free to change and redistribute it.\n"
91                  "There is NO WARRANTY, to the extent permitted by law.\n"),
92                "2010-2012");
93 }
94
95 static void print_help()
96 {
97         printf(_("Usage: %s [OPTION]...\n"), program_name);
98
99         puts(_("psensor-server is an HTTP server for monitoring hardware "
100                "sensors remotely."));
101
102         puts("");
103         puts("Options:");
104         puts(_("  -h, --help            display this help and exit\n"
105                "  -v, --version         display version information and exit"));
106
107         puts("");
108         puts(_("  -p,--port=PORT        webserver port\n"
109                "  -w,--wdir=DIR         directory containing webserver pages"));
110
111         puts("");
112         puts(_("  -d, --debug=LEVEL     "
113                "set the debug level, integer between 0 and 3"));
114         puts(_("  -l, --log-file=PATH   set the log file to PATH"));
115         puts(_("  --sensor-log-file=PATH set the sensor log file to PATH"));
116         puts(_("  --sensor-log-interval=S "
117                "set the sensor log interval to S (seconds)"));
118
119         puts("");
120         printf(_("Report bugs to: %s\n"), PACKAGE_BUGREPORT);
121         puts("");
122         printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
123 }
124
125 /*
126  * Returns the file path corresponding to a given URL
127  */
128 static char *get_path(const char *url, const char *www_dir)
129 {
130         const char *p;
131         char *res;
132
133         if (!strlen(url) || !strcmp(url, ".") || !strcmp(url, "/"))
134                 p = "/index.html";
135         else
136                 p = url;
137
138         res = malloc(strlen(www_dir)+strlen(p)+1);
139
140         strcpy(res, www_dir);
141         strcat(res, p);
142
143         return res;
144 }
145
146 #if MHD_VERSION >= 0x00090200
147 static ssize_t
148 file_reader(void *cls, uint64_t pos, char *buf, size_t max)
149 #else
150 static int
151 file_reader(void *cls, uint64_t pos, char *buf, int max)
152 #endif
153 {
154         FILE *file = cls;
155
156         fseeko(file, pos, SEEK_SET);
157         return fread(buf, 1, max, file);
158 }
159
160 static struct MHD_Response *
161 create_response_api(const char *nurl, const char *method, unsigned int *rp_code)
162 {
163         struct MHD_Response *resp;
164         struct psensor *s;
165         char *page = NULL;
166
167         if (!strcmp(nurl, URL_BASE_API_1_1_SENSORS))  {
168                 page = sensors_to_json_string(server_data.sensors);
169 #ifdef HAVE_GTOP
170         } else if (!strcmp(nurl, URL_API_1_1_SYSINFO)) {
171                 page = sysinfo_to_json_string(&server_data.psysinfo);
172         } else if (!strcmp(nurl, URL_API_1_1_CPU_USAGE)) {
173                 page = sensor_to_json_string(server_data.cpu_usage);
174 #endif
175         } else if (!strncmp(nurl, URL_BASE_API_1_1_SENSORS,
176                             strlen(URL_BASE_API_1_1_SENSORS))
177                    && nurl[strlen(URL_BASE_API_1_1_SENSORS)] == '/') {
178
179                 const char *sid = nurl + strlen(URL_BASE_API_1_1_SENSORS) + 1;
180
181                 s = psensor_list_get_by_id(server_data.sensors, sid);
182
183                 if (s)
184                         page = sensor_to_json_string(s);
185
186         } else if (!strcmp(nurl, URL_API_1_1_SERVER_STOP)) {
187
188                 server_stop_requested = 1;
189                 page = strdup(HTML_STOP_REQUESTED);
190         }
191
192         if (page) {
193                 *rp_code = MHD_HTTP_OK;
194
195                 resp = MHD_create_response_from_data(strlen(page), page,
196                                                      MHD_YES, MHD_NO);
197
198                 MHD_add_response_header(resp, MHD_HTTP_HEADER_CONTENT_TYPE,
199                                         "application/json");
200
201                 return resp;
202         }
203
204         return NULL;
205 }
206
207 static struct MHD_Response *create_response_file(const char *nurl,
208                                                  const char *method,
209                                                  unsigned int *rp_code,
210                                                  const char *fpath)
211 {
212         struct stat st;
213         int ret;
214         FILE *file;
215
216         ret = stat(fpath, &st);
217
218         if (!ret && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
219                 file = fopen(fpath, "rb");
220
221                 if (file) {
222                         *rp_code = MHD_HTTP_OK;
223
224                         if (!st.st_size) {
225                                 fclose(file);
226                                 return MHD_create_response_from_data
227                                         (0, NULL, MHD_NO, MHD_NO);
228                         }
229
230                         return MHD_create_response_from_callback
231                                 (st.st_size,
232                                  32 * 1024,
233                                  &file_reader,
234                                  file,
235                                  (MHD_ContentReaderFreeCallback)&fclose);
236
237                 } else {
238                         log_err("Failed to open: %s.", fpath);
239                 }
240         }
241
242         return NULL;
243 }
244
245 static struct MHD_Response *
246 create_response(const char *nurl, const char *method, unsigned int *rp_code)
247 {
248         struct MHD_Response *resp = NULL;
249
250         if (!strncmp(nurl, URL_BASE_API_1_1, strlen(URL_BASE_API_1_1))) {
251                 resp = create_response_api(nurl, method, rp_code);
252         } else {
253                 char *fpath = get_path(nurl, server_data.www_dir);
254
255                 resp = create_response_file(nurl, method, rp_code, fpath);
256
257                 free(fpath);
258         }
259
260         if (resp)
261                 return resp;
262
263         char *page = strdup(PAGE_NOT_FOUND);
264         *rp_code = MHD_HTTP_NOT_FOUND;
265
266         return MHD_create_response_from_data(strlen(page),
267                                              page,
268                                              MHD_YES,
269                                              MHD_NO);
270 }
271
272 static int cbk_http_request(void *cls,
273                             struct MHD_Connection *connection,
274                             const char *url,
275                             const char *method,
276                             const char *version,
277                             const char *upload_data,
278                             size_t *upload_data_size,
279                             void **ptr)
280 {
281         static int dummy;
282         struct MHD_Response *response;
283         int ret;
284         char *nurl;
285         unsigned int resp_code;
286
287         if (strcmp(method, "GET"))
288                 return MHD_NO;
289
290         if (&dummy != *ptr) {
291                 /* The first time only the headers are valid, do not
292                    respond in the first round... */
293                 *ptr = &dummy;
294                 return MHD_YES;
295         }
296
297         if (*upload_data_size)
298                 return MHD_NO;
299
300         *ptr = NULL;            /* clear context pointer */
301
302         log_debug(_("HTTP Request: %s"), url);
303
304         nurl = url_normalize(url);
305
306         pmutex_lock(&mutex);
307         response = create_response(nurl, method, &resp_code);
308         pmutex_unlock(&mutex);
309
310         ret = MHD_queue_response(connection, resp_code, response);
311         MHD_destroy_response(response);
312
313         free(nurl);
314
315         return ret;
316 }
317
318 int main(int argc, char *argv[])
319 {
320         struct MHD_Daemon *d;
321         int port, opti, optc, cmdok, ret, slog_interval;
322         char *log_file, *slog_file;
323
324         program_name = argv[0];
325
326         setlocale(LC_ALL, "");
327
328 #if ENABLE_NLS
329         bindtextdomain(PACKAGE, LOCALEDIR);
330         textdomain(PACKAGE);
331 #endif
332
333         server_data.www_dir = NULL;
334 #ifdef HAVE_GTOP
335         server_data.psysinfo.interfaces = NULL;
336 #endif
337         log_file = NULL;
338         slog_file = NULL;
339         slog_interval = 300;
340         port = DEFAULT_PORT;
341         cmdok = 1;
342
343         while ((optc = getopt_long(argc,
344                                    argv,
345                                    "vhp:w:d:l:",
346                                    long_options,
347                                    &opti)) != -1) {
348                 switch (optc) {
349                 case 'w':
350                         if (optarg)
351                                 server_data.www_dir = strdup(optarg);
352                         break;
353                 case 'p':
354                         if (optarg)
355                                 port = atoi(optarg);
356                         break;
357                 case 'h':
358                         print_help();
359                         exit(EXIT_SUCCESS);
360                 case 'v':
361                         print_version();
362                         exit(EXIT_SUCCESS);
363                 case 'd':
364                         log_level = atoi(optarg);
365                         log_info(_("Enables debug mode: %d"), log_level);
366                         break;
367                 case 'l':
368                         if (optarg)
369                                 log_file = strdup(optarg);
370                         break;
371                 case 0:
372                         if (!strcmp(long_options[opti].name, "sensor-log-file"))
373                                 slog_file = strdup(optarg);
374                         else if (!strcmp(long_options[opti].name,
375                                          "sensor-log-interval"))
376                                 slog_interval = atoi(optarg);
377                         break;
378                 default:
379                         cmdok = 0;
380                         break;
381                 }
382         }
383
384         if (!cmdok || optind != argc) {
385                 fprintf(stderr, _("Try `%s --help' for more information.\n"),
386                         program_name);
387                 exit(EXIT_FAILURE);
388         }
389
390         if (!server_data.www_dir)
391                 server_data.www_dir = strdup(DEFAULT_WWW_DIR);
392
393         if (!log_file)
394                 log_file = strdup(DEFAULT_LOG_FILE);
395
396         pmutex_init(&mutex);
397
398         log_open(log_file);
399
400         hddtemp_psensor_list_append(&server_data.sensors, 600);
401
402         lmsensor_psensor_list_append(&server_data.sensors, 600);
403
404 #ifdef HAVE_GTOP
405         server_data.cpu_usage = create_cpu_usage_sensor(600);
406 #endif
407
408         if (!*server_data.sensors)
409                 log_err(_("No sensors detected."));
410
411         d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION,
412                              port,
413                              NULL, NULL, &cbk_http_request, server_data.sensors,
414                              MHD_OPTION_END);
415         if (!d) {
416                 log_err(_("Failed to create Web server."));
417                 exit(EXIT_FAILURE);
418         }
419
420         log_info(_("Web server started on port: %d"), port);
421         log_info(_("WWW directory: %s"), server_data.www_dir);
422         log_info(_("URL: http://localhost:%d"), port);
423
424         if (slog_file) {
425                 if (slog_interval <= 0)
426                         slog_interval = 300;
427                 ret = slog_activate(slog_file,
428                                     server_data.sensors,
429                                     &mutex,
430                                     slog_interval);
431                 if (!ret)
432                         log_err(_("Failed to activate logging of sensors."));
433         }
434
435         while (!server_stop_requested) {
436                 pmutex_lock(&mutex);
437
438 #ifdef HAVE_GTOP
439                 sysinfo_update(&server_data.psysinfo);
440                 cpu_usage_sensor_update(server_data.cpu_usage);
441 #endif
442
443 #ifdef HAVE_ATASMART
444                 atasmart_psensor_list_update(server_data.sensors);
445 #endif
446
447                 hddtemp_psensor_list_update(server_data.sensors);
448
449                 lmsensor_psensor_list_update(server_data.sensors);
450
451                 psensor_log_measures(server_data.sensors);
452
453                 pmutex_unlock(&mutex);
454                 sleep(5);
455         }
456
457         slog_close();
458
459         MHD_stop_daemon(d);
460
461         /* sanity cleanup for valgrind */
462         psensor_list_free(server_data.sensors);
463 #ifdef HAVE_GTOP
464         psensor_free(server_data.cpu_usage);
465 #endif
466         free(server_data.www_dir);
467         sensors_cleanup();
468
469 #ifdef HAVE_GTOP
470         sysinfo_cleanup();
471 #endif
472
473         if (log_file != DEFAULT_LOG_FILE)
474                 free(log_file);
475
476         return EXIT_SUCCESS;
477 }