switched from wpitchoune@gmail.com to jeanfi@gmail.com (my usual email)
[psensor.git] / src / httpd / httpd.c
1 /*
2     Copyright (C) 2010-2011 jeanfi@gmail.com
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU 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 #include "config.h"
20
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/select.h>
27 #include <sys/socket.h>
28 #include <getopt.h>
29 #include <stdint.h>
30 #include <pthread.h>
31 #include <unistd.h>
32 #include <microhttpd.h>
33 #include <json/json.h>
34
35 #ifdef HAVE_GTOP
36 #include <glibtop/cpu.h>
37 #endif
38
39 #define lua_c
40
41 #include "lua.h"
42
43 #include "lauxlib.h"
44 #include "lualib.h"
45
46 #include "plib/url.h"
47 #include "httpd.h"
48 #include "psensor.h"
49 #include "hdd.h"
50 #include "nvidia.h"
51 #include "lmsensor.h"
52
53 #define DEFAULT_PORT 3131
54
55
56 #define PAGE_NOT_FOUND \
57 "<html><body><p>Page not found - Go to <a href='/index.lua'>Main page</a>\
58 </p></body>"
59
60
61 #define PSENSOR_SERVER_OPTIONS "  --help    display this help and exit\n\
62   --version   output version information and exit\n\
63   --port=PORT webserver port\n\
64   --wdir=DIR  directory containing webserver pages"
65
66 #define PSENSOR_SERVER_USAGE "Usage: psensor-server [OPTION]"
67
68 #define PSENSOR_SERVER_DESCRIPTION \
69 "HTTP server for retrieving hardware temperatures."
70
71 #define PSENSOR_SERVER_CONTACT "Report bugs to: psensor@wpitchoune.net\n\
72 Psensor home page: <http://wpitchoune.net/psensor/>"
73
74 #define PSENSOR_SERVER_COPYRIGHT \
75 "Copyright (C) 2010-2011 jeanfi@gmail.com\n\
76 License GPLv2: GNU GPL version 2 or later \
77 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>\n\
78 This is free software: you are free to change and redistribute it.\n\
79 There is NO WARRANTY, to the extent permitted by law."
80
81 #define OPT_PORT "port"
82 #define OPT_HELP "help"
83 #define OPT_VERSION "version"
84 #define OPT_WWW_DIR "wdir"
85
86 static struct option long_options[] = {
87         {OPT_VERSION, no_argument, 0, 'v'},
88         {OPT_HELP, no_argument, 0, 'h'},
89         {OPT_PORT, required_argument, 0, 'p'},
90         {OPT_WWW_DIR, required_argument, 0, 'w'},
91         {0, 0, 0, 0}
92 };
93
94 static char *www_dir = DEFAULT_WWW_DIR;
95
96 #ifdef HAVE_GTOP
97 static float cpu_rate;
98 static glibtop_cpu *cpu;
99 #endif
100
101 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
102
103 json_object *sensor_to_json_object(struct psensor *s)
104 {
105         json_object *mo;
106         json_object *obj = json_object_new_object();
107         struct measure *m;
108
109         json_object_object_add(obj, "id", json_object_new_string(s->id));
110         json_object_object_add(obj, "name", json_object_new_string(s->name));
111         json_object_object_add(obj, "type", json_object_new_int(s->type));
112         json_object_object_add(obj, "min", json_object_new_double(s->min));
113         json_object_object_add(obj, "max", json_object_new_double(s->max));
114
115         m = psensor_get_current_measure(s);
116         mo = json_object_new_object();
117         json_object_object_add(mo, "value", json_object_new_double(m->value));
118         json_object_object_add(mo, "time",
119                                json_object_new_int((m->time).tv_sec));
120         json_object_object_add(obj, "last_measure", mo);
121
122         return obj;
123 }
124
125 char *sensor_to_json_string(struct psensor *s)
126 {
127         char *str;
128         json_object *obj = sensor_to_json_object(s);
129
130         str = strdup(json_object_to_json_string(obj));
131
132         json_object_put(obj);
133
134         return str;
135 }
136
137 char *sensors_to_json_string(struct psensor **sensors)
138 {
139         struct psensor **sensors_cur;
140         char *str;
141
142         json_object *obj = json_object_new_array();
143
144         sensors_cur = sensors;
145
146         while (*sensors_cur) {
147                 struct psensor *s = *sensors_cur;
148
149                 json_object_array_add(obj, sensor_to_json_object(s));
150
151                 sensors_cur++;
152         }
153
154         str = strdup(json_object_to_json_string(obj));
155
156         json_object_put(obj);
157
158         return str;
159 }
160
161 char *lua_to_html_page(struct psensor **sensors, const char *url)
162 {
163         struct psensor **s_cur;
164         char *page = NULL;
165         int i;
166         char *lua_fpath;
167
168         lua_State *L = lua_open();      /* create state */
169
170         if (!L)
171                 return NULL;
172
173         luaL_openlibs(L);
174
175 #ifdef HAVE_GTOP
176         lua_newtable(L);
177         lua_pushstring(L, "load");
178         lua_pushnumber(L, cpu_rate);
179         lua_settable(L, -3);
180         lua_setglobal(L, "cpu");
181 #endif
182
183         lua_newtable(L);
184
185         s_cur = sensors;
186         i = 0;
187         while (*s_cur) {
188
189                 lua_pushinteger(L, i);
190
191                 lua_newtable(L);
192
193                 lua_pushstring(L, "name");
194                 lua_pushstring(L, (*s_cur)->name);
195                 lua_settable(L, -3);
196
197                 lua_pushstring(L, "measure_last");
198                 lua_pushnumber(L, psensor_get_current_value(*s_cur));
199                 lua_settable(L, -3);
200
201                 lua_pushstring(L, "measure_min");
202                 lua_pushnumber(L, (*s_cur)->min);
203                 lua_settable(L, -3);
204
205                 lua_pushstring(L, "measure_max");
206                 lua_pushnumber(L, (*s_cur)->max);
207                 lua_settable(L, -3);
208
209                 lua_pushstring(L, "id");
210                 lua_pushstring(L, (*s_cur)->id);
211                 lua_settable(L, -3);
212
213                 lua_settable(L, -3);
214
215                 s_cur++;
216                 i++;
217         }
218
219         lua_setglobal(L, "sensors");
220
221         lua_fpath = malloc(strlen(www_dir) + strlen(url) + 1);
222         strcpy(lua_fpath, www_dir);
223         strcat(lua_fpath, url);
224
225         if (!luaL_loadfile(L, lua_fpath) && !lua_pcall(L, 0, 1, 0))
226                 page = strdup(lua_tostring(L, -1));
227
228         lua_close(L);
229
230         free(lua_fpath);
231
232         return page;
233 }
234
235 static int cbk_http_request(void *cls,
236                             struct MHD_Connection *connection,
237                             const char *url,
238                             const char *method,
239                             const char *version,
240                             const char *upload_data,
241                             size_t *upload_data_size, void **ptr)
242 {
243         struct psensor **sensors = cls;
244         static int dummy;
245         struct MHD_Response *response;
246         int ret;
247         char *nurl;
248         unsigned int resp_code;
249         char *page = NULL;
250
251         if (strcmp(method, "GET"))
252                 return MHD_NO;  /* unexpected method */
253
254         if (&dummy != *ptr) {
255                 /* The first time only the headers are valid, do not
256                    respond in the first round... */
257                 *ptr = &dummy;
258                 return MHD_YES;
259         }
260
261         if (*upload_data_size)
262                 return MHD_NO;  /* upload data in a GET!? */
263
264         *ptr = NULL;            /* clear context pointer */
265
266         nurl = url_normalize(url);
267
268         pthread_mutex_lock(&mutex);
269
270         if (!strcmp(nurl, URL_BASE_API_1_0_SENSORS)) {
271                 page = sensors_to_json_string(sensors);
272
273         } else if (!strncmp(nurl, URL_BASE_API_1_0_SENSORS,
274                             strlen(URL_BASE_API_1_0_SENSORS))
275                    && nurl[strlen(URL_BASE_API_1_0_SENSORS)] == '/') {
276
277                 char *sid = nurl + strlen(URL_BASE_API_1_0_SENSORS) + 1;
278                 struct psensor *s = psensor_list_get_by_id(sensors, sid);
279
280                 if (s)
281                         page = sensor_to_json_string(s);
282
283         } else {
284                 page = lua_to_html_page(sensors, nurl);
285         }
286
287         if (page) {
288                 resp_code = MHD_HTTP_OK;
289         } else {
290                 page = strdup(PAGE_NOT_FOUND);
291                 resp_code = MHD_HTTP_NOT_FOUND;
292         }
293
294         pthread_mutex_unlock(&mutex);
295
296         response = MHD_create_response_from_data(strlen(page),
297                                                  (void *)page, MHD_YES, MHD_NO);
298
299         ret = MHD_queue_response(connection, resp_code, response);
300         MHD_destroy_response(response);
301
302         free(nurl);
303
304         return ret;
305 }
306
307 int main(int argc, char *argv[])
308 {
309         float last_used = 0;
310         float last_total = 0;
311         struct MHD_Daemon *d;
312         struct psensor **sensors;
313         int port = DEFAULT_PORT;
314
315         while (1) {
316                 int c, option_index = 0;
317
318                 c = getopt_long(argc, argv, "vhpw:", long_options,
319                                 &option_index);
320
321                 if (c == -1)
322                         break;
323
324                 switch (c) {
325
326                 case 0:
327                         break;
328
329                 case 'w':
330                         if (optarg)
331                                 www_dir = strdup(optarg);
332                         break;
333
334                 case 'p':
335                         if (optarg)
336                                 port = atoi(optarg);
337
338                         break;
339
340                 case 'h':
341                         printf("%s\n%s\n\n%s\n\n%s\n",
342                                PSENSOR_SERVER_USAGE,
343                                PSENSOR_SERVER_DESCRIPTION,
344                                PSENSOR_SERVER_OPTIONS, PSENSOR_SERVER_CONTACT);
345                         exit(EXIT_SUCCESS);
346
347                 case 'v':
348                         printf("psensor-server %s\n", VERSION);
349                         printf("%s\n", PSENSOR_SERVER_COPYRIGHT);
350
351                         exit(EXIT_SUCCESS);
352
353                 case '?':
354                         break;
355
356                 default:
357                         abort();
358                 }
359         }
360
361         if (optind != argc) {
362                 fprintf(stderr, "Invalid usage.\n");
363                 exit(EXIT_FAILURE);
364         }
365
366         if (!lmsensor_init()) {
367                 fprintf(stderr, "failed to init lm-sensors\n");
368                 exit(EXIT_FAILURE);
369         }
370
371         sensors = get_all_sensors(1);
372
373         if (!sensors || !*sensors) {
374                 fprintf(stderr, "no sensors detected\n");
375                 exit(EXIT_FAILURE);
376         }
377
378         d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION,
379                              port,
380                              NULL, NULL, &cbk_http_request, sensors,
381                              MHD_OPTION_END);
382         if (!d) {
383                 fprintf(stderr, "Fail to create web server\n");
384                 exit(EXIT_FAILURE);
385         }
386
387         printf("Web server started on port: %d\n", port);
388         printf("Psensor URL: http://localhost:%d\n", port);
389
390         while (1) {
391                 unsigned long int used = 0;
392                 unsigned long int dt;
393
394                 pthread_mutex_lock(&mutex);
395
396 #ifdef HAVE_GTOP
397                 if (!cpu)
398                         cpu = malloc(sizeof(glibtop_cpu));
399
400                 glibtop_get_cpu(cpu);
401
402                 used = cpu->user + cpu->nice + cpu->sys;
403
404                 dt = cpu->total - last_total;
405
406                 if (dt)
407                         cpu_rate = (used - last_used) / dt;
408
409                 last_used = used;
410                 last_total = cpu->total;
411
412                 psensor_list_update_measures(sensors);
413 #endif
414
415                 pthread_mutex_unlock(&mutex);
416                 sleep(5);
417         }
418
419         MHD_stop_daemon(d);
420
421 #ifdef HAVE_GTOP
422         free(cpu);
423 #endif
424
425         return 0;
426 }