Imported Upstream version 1.1.2
[psensor-pkg-ubuntu.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, 0, 'v'},
68         {"help", no_argument, 0, 'h'},
69         {"port", required_argument, 0, 'p'},
70         {"wdir", required_argument, 0, 'w'},
71         {"debug", required_argument, 0, 'd'},
72         {"log-file", required_argument, 0, 'l'},
73         {"sensor-log-file", required_argument, 0, 0},
74         {"sensor-log-interval", required_argument, 0, 0},
75         {0, 0, 0, 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, void **ptr)
279 {
280         static int dummy;
281         struct MHD_Response *response;
282         int ret;
283         char *nurl;
284         unsigned int resp_code;
285
286         if (strcmp(method, "GET"))
287                 return MHD_NO;
288
289         if (&dummy != *ptr) {
290                 /* The first time only the headers are valid, do not
291                    respond in the first round... */
292                 *ptr = &dummy;
293                 return MHD_YES;
294         }
295
296         if (*upload_data_size)
297                 return MHD_NO;
298
299         *ptr = NULL;            /* clear context pointer */
300
301         log_debug(_("HTTP Request: %s"), url);
302
303         nurl = url_normalize(url);
304
305         pmutex_lock(&mutex);
306         response = create_response(nurl, method, &resp_code);
307         pmutex_unlock(&mutex);
308
309         ret = MHD_queue_response(connection, resp_code, response);
310         MHD_destroy_response(response);
311
312         free(nurl);
313
314         return ret;
315 }
316
317 int main(int argc, char *argv[])
318 {
319         struct MHD_Daemon *d;
320         int port, opti, optc, cmdok, ret, slog_interval;
321         char *log_file, *slog_file;
322
323         program_name = argv[0];
324
325         setlocale(LC_ALL, "");
326
327 #if ENABLE_NLS
328         bindtextdomain(PACKAGE, LOCALEDIR);
329         textdomain(PACKAGE);
330 #endif
331
332         server_data.www_dir = NULL;
333 #ifdef HAVE_GTOP
334         server_data.psysinfo.interfaces = NULL;
335 #endif
336         log_file = NULL;
337         slog_file = NULL;
338         slog_interval = 300;
339         port = DEFAULT_PORT;
340         cmdok = 1;
341
342         while ((optc = getopt_long(argc,
343                                    argv,
344                                    "vhp:w:d:l:",
345                                    long_options,
346                                    &opti)) != -1) {
347                 switch (optc) {
348                 case 'w':
349                         if (optarg)
350                                 server_data.www_dir = strdup(optarg);
351                         break;
352                 case 'p':
353                         if (optarg)
354                                 port = atoi(optarg);
355                         break;
356                 case 'h':
357                         print_help();
358                         exit(EXIT_SUCCESS);
359                 case 'v':
360                         print_version();
361                         exit(EXIT_SUCCESS);
362                 case 'd':
363                         log_level = atoi(optarg);
364                         log_info(_("Enables debug mode: %d"), log_level);
365                         break;
366                 case 'l':
367                         if (optarg)
368                                 log_file = strdup(optarg);
369                         break;
370                 case 0:
371                         if (!strcmp(long_options[opti].name, "sensor-log-file"))
372                                 slog_file = strdup(optarg);
373                         else if (!strcmp(long_options[opti].name,
374                                          "sensor-log-interval"))
375                                 slog_interval = atoi(optarg);
376                         break;
377                 default:
378                         cmdok = 0;
379                         break;
380                 }
381         }
382
383         if (!cmdok || optind != argc) {
384                 fprintf(stderr, _("Try `%s --help' for more information.\n"),
385                         program_name);
386                 exit(EXIT_FAILURE);
387         }
388
389         if (!server_data.www_dir)
390                 server_data.www_dir = strdup(DEFAULT_WWW_DIR);
391
392         if (!log_file)
393                 log_file = strdup(DEFAULT_LOG_FILE);
394
395         pmutex_init(&mutex);
396
397         log_open(log_file);
398
399         hddtemp_psensor_list_append(&server_data.sensors, 600);
400
401         lmsensor_psensor_list_append(&server_data.sensors, 600);
402
403 #ifdef HAVE_GTOP
404         server_data.cpu_usage = create_cpu_usage_sensor(600);
405 #endif
406
407         if (!*server_data.sensors)
408                 log_err(_("No sensors detected."));
409
410         d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION,
411                              port,
412                              NULL, NULL, &cbk_http_request, server_data.sensors,
413                              MHD_OPTION_END);
414         if (!d) {
415                 log_err(_("Failed to create Web server."));
416                 exit(EXIT_FAILURE);
417         }
418
419         log_info(_("Web server started on port: %d"), port);
420         log_info(_("WWW directory: %s"), server_data.www_dir);
421         log_info(_("URL: http://localhost:%d"), port);
422
423         if (slog_file) {
424                 if (slog_interval <= 0)
425                         slog_interval = 300;
426                 ret = slog_activate(slog_file,
427                                     server_data.sensors,
428                                     &mutex,
429                                     slog_interval);
430                 if (!ret)
431                         log_err(_("Failed to activate logging of sensors."));
432         }
433
434         while (!server_stop_requested) {
435                 pmutex_lock(&mutex);
436
437 #ifdef HAVE_GTOP
438                 sysinfo_update(&server_data.psysinfo);
439                 cpu_usage_sensor_update(server_data.cpu_usage);
440 #endif
441
442 #ifdef HAVE_ATASMART
443                 atasmart_psensor_list_update(server_data.sensors);
444 #endif
445
446                 hddtemp_psensor_list_update(server_data.sensors);
447
448                 lmsensor_psensor_list_update(server_data.sensors);
449
450                 psensor_log_measures(server_data.sensors);
451
452                 pmutex_unlock(&mutex);
453                 sleep(5);
454         }
455
456         slog_close();
457
458         MHD_stop_daemon(d);
459
460         /* sanity cleanup for valgrind */
461         psensor_list_free(server_data.sensors);
462 #ifdef HAVE_GTOP
463         psensor_free(server_data.cpu_usage);
464 #endif
465         free(server_data.www_dir);
466         sensors_cleanup();
467
468 #ifdef HAVE_GTOP
469         sysinfo_cleanup();
470 #endif
471
472         if (log_file != DEFAULT_LOG_FILE)
473                 free(log_file);
474
475         return EXIT_SUCCESS;
476 }