1#include "server.h" 2#include "connections.h" 3#include "response.h" 4#include "connections.h" 5#include "log.h" 6 7#include "plugin.h" 8#include <sys/types.h> 9 10#include <assert.h> 11#include <fcntl.h> 12#include <stdlib.h> 13#include <stdio.h> 14#include <string.h> 15#include <unistd.h> 16#include <errno.h> 17#include <time.h> 18 19#ifdef HAVE_FORK 20/* no need for waitpid if we don't have fork */ 21#include <sys/wait.h> 22#endif 23typedef struct { 24 buffer *path_rrdtool_bin; 25 buffer *path_rrd; 26 27 double requests, *requests_ptr; 28 double bytes_written, *bytes_written_ptr; 29 double bytes_read, *bytes_read_ptr; 30} plugin_config; 31 32typedef struct { 33 PLUGIN_DATA; 34 35 buffer *cmd; 36 buffer *resp; 37 38 int read_fd, write_fd; 39 pid_t rrdtool_pid; 40 41 int rrdtool_running; 42 43 plugin_config **config_storage; 44 plugin_config conf; 45} plugin_data; 46 47INIT_FUNC(mod_rrd_init) { 48 plugin_data *p; 49 50 p = calloc(1, sizeof(*p)); 51 52 p->resp = buffer_init(); 53 p->cmd = buffer_init(); 54 55 return p; 56} 57 58FREE_FUNC(mod_rrd_free) { 59 plugin_data *p = p_d; 60 size_t i; 61 62 if (!p) return HANDLER_GO_ON; 63 64 if (p->config_storage) { 65 for (i = 0; i < srv->config_context->used; i++) { 66 plugin_config *s = p->config_storage[i]; 67 68 if (NULL == s) continue; 69 70 buffer_free(s->path_rrdtool_bin); 71 buffer_free(s->path_rrd); 72 73 free(s); 74 } 75 } 76 buffer_free(p->cmd); 77 buffer_free(p->resp); 78 79 free(p->config_storage); 80 81 if (p->rrdtool_pid) { 82 int status; 83 close(p->read_fd); 84 close(p->write_fd); 85#ifdef HAVE_FORK 86 /* collect status */ 87 waitpid(p->rrdtool_pid, &status, 0); 88#endif 89 } 90 91 free(p); 92 93 return HANDLER_GO_ON; 94} 95 96static int mod_rrd_create_pipe(server *srv, plugin_data *p) { 97#ifdef HAVE_FORK 98 pid_t pid; 99 100 int to_rrdtool_fds[2]; 101 int from_rrdtool_fds[2]; 102 if (pipe(to_rrdtool_fds)) { 103 log_error_write(srv, __FILE__, __LINE__, "ss", 104 "pipe failed: ", strerror(errno)); 105 return -1; 106 } 107 108 if (pipe(from_rrdtool_fds)) { 109 log_error_write(srv, __FILE__, __LINE__, "ss", 110 "pipe failed: ", strerror(errno)); 111 return -1; 112 } 113 114 /* fork, execve */ 115 switch (pid = fork()) { 116 case 0: { 117 /* child */ 118 char **args; 119 int argc; 120 int i = 0; 121 char *dash = "-"; 122 123 /* move stdout to from_rrdtool_fd[1] */ 124 close(STDOUT_FILENO); 125 dup2(from_rrdtool_fds[1], STDOUT_FILENO); 126 close(from_rrdtool_fds[1]); 127 /* not needed */ 128 close(from_rrdtool_fds[0]); 129 130 /* move the stdin to to_rrdtool_fd[0] */ 131 close(STDIN_FILENO); 132 dup2(to_rrdtool_fds[0], STDIN_FILENO); 133 close(to_rrdtool_fds[0]); 134 /* not needed */ 135 close(to_rrdtool_fds[1]); 136 137 /* set up args */ 138 argc = 3; 139 args = malloc(sizeof(*args) * argc); 140 i = 0; 141 142 args[i++] = p->conf.path_rrdtool_bin->ptr; 143 args[i++] = dash; 144 args[i ] = NULL; 145 146 /* we don't need the client socket */ 147 for (i = 3; i < 256; i++) { 148 close(i); 149 } 150 151 /* exec the cgi */ 152 execv(args[0], args); 153 154 /* log_error_write(srv, __FILE__, __LINE__, "sss", "spawing rrdtool failed: ", strerror(errno), args[0]); */ 155 156 /* */ 157 SEGFAULT(); 158 break; 159 } 160 case -1: 161 /* error */ 162 log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno)); 163 break; 164 default: { 165 /* father */ 166 167 close(from_rrdtool_fds[1]); 168 close(to_rrdtool_fds[0]); 169 170 /* register PID and wait for them asyncronously */ 171 p->write_fd = to_rrdtool_fds[1]; 172 p->read_fd = from_rrdtool_fds[0]; 173 p->rrdtool_pid = pid; 174 175 fd_close_on_exec(p->write_fd); 176 fd_close_on_exec(p->read_fd); 177 178 break; 179 } 180 } 181 182 return 0; 183#else 184 return -1; 185#endif 186} 187 188/* read/write wrappers to catch EINTR */ 189 190/* write to blocking socket; blocks until all data is sent, write returns 0 or an error (apart from EINTR) occurs. */ 191static ssize_t safe_write(int fd, const void *buf, size_t count) { 192 ssize_t res, sum = 0; 193 194 for (;;) { 195 res = write(fd, buf, count); 196 if (res >= 0) { 197 sum += res; 198 /* do not try again if res == 0 */ 199 if (res == 0 || (size_t) res == count) return sum; 200 count -= res; 201 buf = (const char*) buf + res; 202 continue; 203 } 204 switch (errno) { 205 case EINTR: 206 continue; 207 default: 208 return -1; 209 } 210 } 211} 212 213/* this assumes we get enough data on a successful read */ 214static ssize_t safe_read(int fd, void *buf, size_t count) { 215 ssize_t res; 216 217 for (;;) { 218 res = read(fd, buf, count); 219 if (res >= 0) return res; 220 switch (errno) { 221 case EINTR: 222 continue; 223 default: 224 return -1; 225 } 226 } 227} 228 229static int mod_rrdtool_create_rrd(server *srv, plugin_data *p, plugin_config *s) { 230 struct stat st; 231 int r; 232 233 /* check if DB already exists */ 234 if (0 == stat(s->path_rrd->ptr, &st)) { 235 /* check if it is plain file */ 236 if (!S_ISREG(st.st_mode)) { 237 log_error_write(srv, __FILE__, __LINE__, "sb", 238 "not a regular file:", s->path_rrd); 239 return HANDLER_ERROR; 240 } 241 242 /* still create DB if it's empty file */ 243 if (st.st_size > 0) { 244 return HANDLER_GO_ON; 245 } 246 } 247 248 /* create a new one */ 249 buffer_copy_string_len(p->cmd, CONST_STR_LEN("create ")); 250 buffer_append_string_buffer(p->cmd, s->path_rrd); 251 buffer_append_string_len(p->cmd, CONST_STR_LEN( 252 " --step 60 " 253 "DS:InOctets:ABSOLUTE:600:U:U " 254 "DS:OutOctets:ABSOLUTE:600:U:U " 255 "DS:Requests:ABSOLUTE:600:U:U " 256 "RRA:AVERAGE:0.5:1:600 " 257 "RRA:AVERAGE:0.5:6:700 " 258 "RRA:AVERAGE:0.5:24:775 " 259 "RRA:AVERAGE:0.5:288:797 " 260 "RRA:MAX:0.5:1:600 " 261 "RRA:MAX:0.5:6:700 " 262 "RRA:MAX:0.5:24:775 " 263 "RRA:MAX:0.5:288:797 " 264 "RRA:MIN:0.5:1:600 " 265 "RRA:MIN:0.5:6:700 " 266 "RRA:MIN:0.5:24:775 " 267 "RRA:MIN:0.5:288:797\n")); 268 269 if (-1 == (safe_write(p->write_fd, CONST_BUF_LEN(p->cmd)))) { 270 log_error_write(srv, __FILE__, __LINE__, "ss", 271 "rrdtool-write: failed", strerror(errno)); 272 273 return HANDLER_ERROR; 274 } 275 276 buffer_string_prepare_copy(p->resp, 4095); 277 if (-1 == (r = safe_read(p->read_fd, p->resp->ptr, p->resp->size - 1))) { 278 log_error_write(srv, __FILE__, __LINE__, "ss", 279 "rrdtool-read: failed", strerror(errno)); 280 281 return HANDLER_ERROR; 282 } 283 284 buffer_commit(p->resp, r); 285 286 if (p->resp->ptr[0] != 'O' || 287 p->resp->ptr[1] != 'K') { 288 log_error_write(srv, __FILE__, __LINE__, "sbb", 289 "rrdtool-response:", p->cmd, p->resp); 290 291 return HANDLER_ERROR; 292 } 293 294 return HANDLER_GO_ON; 295} 296 297#define PATCH(x) \ 298 p->conf.x = s->x; 299static int mod_rrd_patch_connection(server *srv, connection *con, plugin_data *p) { 300 size_t i, j; 301 plugin_config *s = p->config_storage[0]; 302 303 PATCH(path_rrdtool_bin); 304 PATCH(path_rrd); 305 306 p->conf.bytes_written_ptr = &(s->bytes_written); 307 p->conf.bytes_read_ptr = &(s->bytes_read); 308 p->conf.requests_ptr = &(s->requests); 309 310 /* skip the first, the global context */ 311 for (i = 1; i < srv->config_context->used; i++) { 312 data_config *dc = (data_config *)srv->config_context->data[i]; 313 s = p->config_storage[i]; 314 315 /* condition didn't match */ 316 if (!config_check_cond(srv, con, dc)) continue; 317 318 /* merge config */ 319 for (j = 0; j < dc->value->used; j++) { 320 data_unset *du = dc->value->data[j]; 321 322 if (buffer_is_equal_string(du->key, CONST_STR_LEN("rrdtool.db-name"))) { 323 PATCH(path_rrd); 324 /* get pointers to double values */ 325 326 p->conf.bytes_written_ptr = &(s->bytes_written); 327 p->conf.bytes_read_ptr = &(s->bytes_read); 328 p->conf.requests_ptr = &(s->requests); 329 } 330 } 331 } 332 333 return 0; 334} 335#undef PATCH 336 337SETDEFAULTS_FUNC(mod_rrd_set_defaults) { 338 plugin_data *p = p_d; 339 size_t i; 340 341 config_values_t cv[] = { 342 { "rrdtool.binary", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ 343 { "rrdtool.db-name", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */ 344 { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } 345 }; 346 347 if (!p) return HANDLER_ERROR; 348 349 force_assert(srv->config_context->used > 0); 350 p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *)); 351 352 for (i = 0; i < srv->config_context->used; i++) { 353 data_config const* config = (data_config const*)srv->config_context->data[i]; 354 plugin_config *s; 355 356 s = calloc(1, sizeof(plugin_config)); 357 s->path_rrdtool_bin = buffer_init(); 358 s->path_rrd = buffer_init(); 359 s->requests = 0; 360 s->bytes_written = 0; 361 s->bytes_read = 0; 362 363 cv[0].destination = s->path_rrdtool_bin; 364 cv[1].destination = s->path_rrd; 365 366 p->config_storage[i] = s; 367 368 if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { 369 return HANDLER_ERROR; 370 } 371 372 if (i > 0 && !buffer_string_is_empty(s->path_rrdtool_bin)) { 373 /* path_rrdtool_bin is a global option */ 374 375 log_error_write(srv, __FILE__, __LINE__, "s", 376 "rrdtool.binary can only be set as a global option."); 377 378 return HANDLER_ERROR; 379 } 380 381 } 382 383 p->conf.path_rrdtool_bin = p->config_storage[0]->path_rrdtool_bin; 384 p->rrdtool_running = 0; 385 386 /* check for dir */ 387 388 if (buffer_string_is_empty(p->conf.path_rrdtool_bin)) { 389 log_error_write(srv, __FILE__, __LINE__, "s", 390 "rrdtool.binary has to be set"); 391 return HANDLER_ERROR; 392 } 393 394 /* open the pipe to rrdtool */ 395 if (mod_rrd_create_pipe(srv, p)) { 396 return HANDLER_ERROR; 397 } 398 399 p->rrdtool_running = 1; 400 401 return HANDLER_GO_ON; 402} 403 404TRIGGER_FUNC(mod_rrd_trigger) { 405 plugin_data *p = p_d; 406 size_t i; 407 408 if (!p->rrdtool_running) return HANDLER_GO_ON; 409 if ((srv->cur_ts % 60) != 0) return HANDLER_GO_ON; 410 411 for (i = 0; i < srv->config_context->used; i++) { 412 plugin_config *s = p->config_storage[i]; 413 int r; 414 415 if (buffer_string_is_empty(s->path_rrd)) continue; 416 417 /* write the data down every minute */ 418 419 if (HANDLER_GO_ON != mod_rrdtool_create_rrd(srv, p, s)) return HANDLER_ERROR; 420 421 buffer_copy_string_len(p->cmd, CONST_STR_LEN("update ")); 422 buffer_append_string_buffer(p->cmd, s->path_rrd); 423 buffer_append_string_len(p->cmd, CONST_STR_LEN(" N:")); 424 buffer_append_int(p->cmd, s->bytes_read); 425 buffer_append_string_len(p->cmd, CONST_STR_LEN(":")); 426 buffer_append_int(p->cmd, s->bytes_written); 427 buffer_append_string_len(p->cmd, CONST_STR_LEN(":")); 428 buffer_append_int(p->cmd, s->requests); 429 buffer_append_string_len(p->cmd, CONST_STR_LEN("\n")); 430 431 if (-1 == (r = safe_write(p->write_fd, CONST_BUF_LEN(p->cmd)))) { 432 p->rrdtool_running = 0; 433 434 log_error_write(srv, __FILE__, __LINE__, "ss", 435 "rrdtool-write: failed", strerror(errno)); 436 437 return HANDLER_ERROR; 438 } 439 440 buffer_string_prepare_copy(p->resp, 4095); 441 if (-1 == (r = safe_read(p->read_fd, p->resp->ptr, p->resp->size - 1))) { 442 p->rrdtool_running = 0; 443 444 log_error_write(srv, __FILE__, __LINE__, "ss", 445 "rrdtool-read: failed", strerror(errno)); 446 447 return HANDLER_ERROR; 448 } 449 450 buffer_commit(p->resp, r); 451 452 if (p->resp->ptr[0] != 'O' || 453 p->resp->ptr[1] != 'K') { 454 /* don't fail on this error if we just started (graceful restart, the old one might have just updated too) */ 455 if (!(strstr(p->resp->ptr, "(minimum one second step)") && (srv->cur_ts - srv->startup_ts < 3))) { 456 p->rrdtool_running = 0; 457 458 log_error_write(srv, __FILE__, __LINE__, "sbb", 459 "rrdtool-response:", p->cmd, p->resp); 460 461 return HANDLER_ERROR; 462 } 463 } 464 s->requests = 0; 465 s->bytes_written = 0; 466 s->bytes_read = 0; 467 } 468 469 return HANDLER_GO_ON; 470} 471 472REQUESTDONE_FUNC(mod_rrd_account) { 473 plugin_data *p = p_d; 474 475 mod_rrd_patch_connection(srv, con, p); 476 477 *(p->conf.requests_ptr) += 1; 478 *(p->conf.bytes_written_ptr) += con->bytes_written; 479 *(p->conf.bytes_read_ptr) += con->bytes_read; 480 481 return HANDLER_GO_ON; 482} 483 484int mod_rrdtool_plugin_init(plugin *p); 485int mod_rrdtool_plugin_init(plugin *p) { 486 p->version = LIGHTTPD_VERSION_ID; 487 p->name = buffer_init_string("rrd"); 488 489 p->init = mod_rrd_init; 490 p->cleanup = mod_rrd_free; 491 p->set_defaults= mod_rrd_set_defaults; 492 493 p->handle_trigger = mod_rrd_trigger; 494 p->handle_request_done = mod_rrd_account; 495 496 p->data = NULL; 497 498 return 0; 499} 500