+ /* obtain filename */
+ status = buffer_get_field(&buffer, &buffer_size, &file);
+ if (status != 0)
+ return syntax_error(sock,cmd);
+ /* get full pathname */
+ get_abs_path(&file, file_tmp);
+ if (!check_file_access(file, sock)) {
+ return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+ }
+ /* get data */
+ rrd_clear_error ();
+ info = rrd_info_r(file);
+ if(!info) {
+ return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+ }
+ for (rrd_info_t *data = info; data != NULL; data = data->next) {
+ switch (data->type) {
+ case RD_I_VAL:
+ if (isnan(data->value.u_val))
+ add_response_info(sock,"%s %d NaN\n",data->key, data->type);
+ else
+ add_response_info(sock,"%s %d %0.10e\n", data->key, data->type, data->value.u_val);
+ break;
+ case RD_I_CNT:
+ add_response_info(sock,"%s %d %lu\n", data->key, data->type, data->value.u_cnt);
+ break;
+ case RD_I_INT:
+ add_response_info(sock,"%s %d %d\n", data->key, data->type, data->value.u_int);
+ break;
+ case RD_I_STR:
+ add_response_info(sock,"%s %d %s\n", data->key, data->type, data->value.u_str);
+ break;
+ case RD_I_BLO:
+ add_response_info(sock,"%s %d %lu\n", data->key, data->type, data->value.u_blo.size);
+ break;
+ }
+ }
+
+ rrd_info_free(info);
+
+ return send_response(sock, RESP_OK, "Info for %s follows\n",file);
+} /* }}} static int handle_request_info */
+
+static int handle_request_first (HANDLER_PROTO) /* {{{ */
+{
+ char *i, *file, file_tmp[PATH_MAX];
+ int status;
+ int idx;
+ time_t t;
+
+ /* obtain filename */
+ status = buffer_get_field(&buffer, &buffer_size, &file);
+ if (status != 0)
+ return syntax_error(sock,cmd);
+ /* get full pathname */
+ get_abs_path(&file, file_tmp);
+ if (!check_file_access(file, sock)) {
+ return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+ }
+
+ status = buffer_get_field(&buffer, &buffer_size, &i);
+ if (status != 0)
+ return syntax_error(sock,cmd);
+ idx = atoi(i);
+ if(idx<0) {
+ return send_response(sock, RESP_ERR, "Invalid index specified (%d)\n", idx);
+ }
+
+ /* get data */
+ rrd_clear_error ();
+ t = rrd_first_r(file,idx);
+ if(t<1) {
+ return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+ }
+ return send_response(sock, RESP_OK, "%lu\n",(unsigned)t);
+} /* }}} static int handle_request_first */
+
+
+static int handle_request_last (HANDLER_PROTO) /* {{{ */
+{
+ char *file, file_tmp[PATH_MAX];
+ int status;
+ time_t t, from_file, step;
+ rrd_file_t * rrd_file;
+ cache_item_t * ci;
+ rrd_t rrd;
+
+ /* obtain filename */
+ status = buffer_get_field(&buffer, &buffer_size, &file);
+ if (status != 0)
+ return syntax_error(sock,cmd);
+ /* get full pathname */
+ get_abs_path(&file, file_tmp);
+ if (!check_file_access(file, sock)) {
+ return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+ }
+ rrd_clear_error();
+ rrd_init(&rrd);
+ rrd_file = rrd_open(file,&rrd,RRD_READONLY);
+ if(!rrd_file) {
+ return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+ }
+ from_file = rrd.live_head->last_up;
+ step = rrd.stat_head->pdp_step;
+ rrd_close(rrd_file);
+ pthread_mutex_lock(&cache_lock);
+ ci = g_tree_lookup(cache_tree, file);
+ if (ci)
+ t = ci->last_update_stamp;
+ else
+ t = from_file;
+ pthread_mutex_unlock(&cache_lock);
+ t -= t % step;
+ rrd_free(&rrd);
+ if(t<1) {
+ return send_response(sock, RESP_ERR, "Error: rrdcached: Invalid timestamp returned\n");
+ }
+ return send_response(sock, RESP_OK, "%lu\n",(unsigned)t);
+} /* }}} static int handle_request_last */
+
+static int handle_request_create (HANDLER_PROTO) /* {{{ */
+{
+ char *file, file_tmp[PATH_MAX];
+ char *tok;
+ int ac = 0;
+ char *av[128];
+ int status;
+ unsigned long step = 300;
+ time_t last_up = time(NULL)-10;
+ int no_overwrite = opt_no_overwrite;
+
+
+ /* obtain filename */
+ status = buffer_get_field(&buffer, &buffer_size, &file);
+ if (status != 0)
+ return syntax_error(sock,cmd);
+ /* get full pathname */
+ get_abs_path(&file, file_tmp);
+ if (!check_file_access(file, sock)) {
+ return send_response(sock, RESP_ERR, "Cannot read: %s\n", file);
+ }
+ RRDD_LOG(LOG_INFO, "rrdcreate request for %s",file);
+
+ while ((status = buffer_get_field(&buffer, &buffer_size, &tok)) == 0 && tok) {
+ if( ! strncmp(tok,"-b",2) ) {
+ status = buffer_get_field(&buffer, &buffer_size, &tok );
+ if (status != 0) return syntax_error(sock,cmd);
+ last_up = (time_t) atol(tok);
+ continue;
+ }
+ if( ! strncmp(tok,"-s",2) ) {
+ status = buffer_get_field(&buffer, &buffer_size, &tok );
+ if (status != 0) return syntax_error(sock,cmd);
+ step = atol(tok);
+ continue;
+ }
+ if( ! strncmp(tok,"-O",2) ) {
+ no_overwrite = 1;
+ continue;
+ }
+ if( ! strncmp(tok,"DS:",3) ) { av[ac++]=tok; continue; }
+ if( ! strncmp(tok,"RRA:",4) ) { av[ac++]=tok; continue; }
+ return syntax_error(sock,cmd);
+ }
+ if(step<1) {
+ return send_response(sock, RESP_ERR, "The step size cannot be less than 1 second.\n");
+ }
+ if (last_up < 3600 * 24 * 365 * 10) {
+ return send_response(sock, RESP_ERR, "The first entry must be after 1980.\n");
+ }
+
+ rrd_clear_error ();
+ status = rrd_create_r2(file,step,last_up,no_overwrite,ac,(const char **)av);
+
+ if(!status) {
+ return send_response(sock, RESP_OK, "RRD created OK\n");
+ }
+ return send_response(sock, RESP_ERR, "RRD Error: %s\n", rrd_get_error());
+} /* }}} static int handle_request_create */
+
+/* start "BATCH" processing */
+static int batch_start (HANDLER_PROTO) /* {{{ */
+{
+ int status;
+ if (sock->batch_start)
+ return send_response(sock, RESP_ERR, "Already in BATCH\n");
+
+ status = send_response(sock, RESP_OK,
+ "Go ahead. End with dot '.' on its own line.\n");
+ sock->batch_start = time(NULL);
+ sock->batch_cmd = 0;
+
+ return status;
+} /* }}} static int batch_start */
+
+/* finish "BATCH" processing and return results to the client */
+static int batch_done (HANDLER_PROTO) /* {{{ */
+{
+ assert(sock->batch_start);
+ sock->batch_start = 0;
+ sock->batch_cmd = 0;
+ return send_response(sock, RESP_OK, "errors\n");
+} /* }}} static int batch_done */
+
+static int handle_request_quit (HANDLER_PROTO) /* {{{ */
+{
+ return -1;
+} /* }}} static int handle_request_quit */
+
+static command_t list_of_commands[] = { /* {{{ */
+ {
+ "UPDATE",
+ handle_request_update,
+ CMD_CONTEXT_ANY,
+ "UPDATE <filename> <values> [<values> ...]\n"
+ ,
+ "Adds the given file to the internal cache if it is not yet known and\n"
+ "appends the given value(s) to the entry. See the rrdcached(1) manpage\n"
+ "for details.\n"
+ "\n"
+ "Each <values> has the following form:\n"
+ " <values> = <time>:<value>[:<value>[...]]\n"
+ "See the rrdupdate(1) manpage for details.\n"
+ },
+ {
+ "WROTE",
+ handle_request_wrote,
+ CMD_CONTEXT_JOURNAL,
+ NULL,
+ NULL
+ },
+ {
+ "FLUSH",
+ handle_request_flush,
+ CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
+ "FLUSH <filename>\n"
+ ,
+ "Adds the given filename to the head of the update queue and returns\n"
+ "after it has been dequeued.\n"
+ },
+ {
+ "FLUSHALL",
+ handle_request_flushall,
+ CMD_CONTEXT_CLIENT,
+ "FLUSHALL\n"
+ ,
+ "Triggers writing of all pending updates. Returns immediately.\n"
+ },
+ {
+ "PENDING",
+ handle_request_pending,
+ CMD_CONTEXT_CLIENT,
+ "PENDING <filename>\n"
+ ,
+ "Shows any 'pending' updates for a file, in order.\n"
+ "The updates shown have not yet been written to the underlying RRD file.\n"
+ },
+ {
+ "FORGET",
+ handle_request_forget,
+ CMD_CONTEXT_ANY,
+ "FORGET <filename>\n"
+ ,
+ "Removes the file completely from the cache.\n"
+ "Any pending updates for the file will be lost.\n"
+ },
+ {
+ "QUEUE",
+ handle_request_queue,
+ CMD_CONTEXT_CLIENT,
+ "QUEUE\n"
+ ,
+ "Shows all files in the output queue.\n"
+ "The output is zero or more lines in the following format:\n"
+ "(where <num_vals> is the number of values to be written)\n"
+ "\n"
+ "<num_vals> <filename>\n"
+ },
+ {
+ "STATS",
+ handle_request_stats,
+ CMD_CONTEXT_CLIENT,
+ "STATS\n"
+ ,
+ "Returns some performance counters, see the rrdcached(1) manpage for\n"
+ "a description of the values.\n"
+ },