static int handle_request_list (HANDLER_PROTO) /* {{{ */
{
- char *filename;
+ char *filename = NULL;
+ char *rec = NULL;
+ int recursive = 0;
char *list, *start_ptr, *end_ptr, *ptr;
char fullpath[PATH_MAX], current[PATH_MAX], absolute[PATH_MAX];
char bwc[PATH_MAX], bwd[PATH_MAX];
return send_response(sock, RESP_ERR, "No base directory defined\n");
}
- /* Get pathname */
+ /* get 'RECURSIVE' option */
+ status = buffer_get_field(&buffer, &buffer_size, &rec);
+ if (status == 0) {
+ /* as 'RECURSIVE' is optional, the first argument may be the filename */
+ if (rec[0] != '/' && strcmp(rec, "RECURSIVE") != 0) {
+ return syntax_error(sock, cmd);
+ }
+
+ if (rec[0] == '/') {
+ filename = rec;
+
+ } else if (strcmp(rec, "RECURSIVE") == 0) {
+ recursive = 1;
+ }
+ }
+
+ /* Get pathname if not done already */
+ if (!filename) {
status = buffer_get_field(&buffer, &buffer_size, &filename);
if (status != 0)
return syntax_error(sock,cmd);
+ }
/* get full pathname */
snprintf(fullpath, PATH_MAX, "%s%s%s",
base = &bwd[0];
}
- list = rrd_list_r(fullpath);
+ list = rrd_list_r(recursive, fullpath);
if (list == NULL) {
/* Empty directory listing */
"LIST",
handle_request_list,
CMD_CONTEXT_CLIENT,
- "LIST\n",
- "This command lists the RRD files in the storage base directory.\n"
+ "LIST [RECURSIVE] /[<path>]\n",
+ "This command lists the RRD files in the storage base directory (/).\n"
"Note that this is the list of RRD files on storage as of the last update.\n"
"There may be pending updates in the queue, so a FLUSH may have to be run\n"
"beforehand.\n"
+ "When invoked with 'LIST RECURSIVE /<path>' it will behave similarly to\n"
+ "'ls -R' but limited to rrd files (listing all the rrd bases in the subtree\n"
+ " of <path>, skipping empty directories).\n"
},
{
"QUIT",
#include "rrd_tool.h"
#include "rrd_client.h"
-char *rrd_list_r(char *dirname)
+static char *move_past_prefix(const char *prefix, const char *string)
{
-#define SANE_ASPRINTF(_dest_str, _format, ...) \
+ int index = 0;
+
+ if (strlen(prefix) > strlen(string)) {
+ return (char *)string;
+ }
+
+ while (prefix[index] != '\0') {
+ if (prefix[index] != string[index]) {
+ break;
+ }
+ index++;
+ }
+
+ return (char *)&(string[index]);
+}
+
+static char *rrd_list_rec(int recursive, char *root, char *dirname)
+{
+#define SANE_ASPRINTF2(_dest_str, _format, ...) \
if (asprintf(&_dest_str, _format, __VA_ARGS__) == -1) { \
if (out != NULL) { \
free(out); \
} \
+ closedir(dir); \
errno = ENOMEM; \
return NULL; \
}
-#define SANE_ASPRINTF2(_dest_str, _format, ...) \
+
+ struct stat st;
+ struct dirent *entry;
+ DIR *dir;
+ char *out = NULL, *out_rec, *out_short, *tmp, *ptr;
+ char current[PATH_MAX], fullpath[PATH_MAX];
+
+ dir = opendir(dirname);
+
+ if (dir == NULL) {
+ /* opendir sets errno */
+ return NULL;
+ }
+
+ while ((entry = readdir(dir)) != NULL) {
+
+ if ((strcmp(entry->d_name, ".") == 0) ||
+ (strcmp(entry->d_name, "..") == 0)) {
+ continue;
+ }
+
+ if (strlen(dirname) + strlen(entry->d_name) + 1 >= PATH_MAX) {
+ continue;
+ }
+
+ snprintf(¤t[0], PATH_MAX, "%s/%s", dirname, entry->d_name);
+
+ /* NOTE: stat(2) follows symlinks and gives info on target. */
+ if (stat(current, &st) != 0) {
+ continue;
+ }
+
+ if (S_ISDIR(st.st_mode) && recursive) {
+ snprintf(&fullpath[0], PATH_MAX, "%s/%s",
+ dirname, entry->d_name);
+ out_rec = rrd_list_rec(recursive, root, fullpath);
+
+ if (out_rec == NULL) {
+ continue;
+ }
+
+ if (out == NULL) {
+ SANE_ASPRINTF2(out, "%s", out_rec);
+
+ } else {
+ tmp = out;
+ SANE_ASPRINTF2(out, "%s%s", out, out_rec);
+ free(tmp);
+ }
+ free(out_rec);
+
+ } else {
+
+ if (S_ISREG(st.st_mode)) {
+
+ ptr = strstr(entry->d_name, ".rrd");
+
+ /* strlen() used to make sure it's matching
+ * the end of string. Non-rrd-suffixed regular
+ * files are skipped.
+ */
+ if (ptr == NULL || strlen(ptr) != 4) {
+ continue;
+ }
+ }
+ snprintf(&fullpath[0], PATH_MAX, "%s/%s",
+ dirname, entry->d_name);
+ out_short = move_past_prefix(root, fullpath);
+
+ /* don't start output with a '/' */
+ if (out_short[0] == '/') {
+ out_short++;
+ }
+
+ if (out == NULL) {
+ SANE_ASPRINTF2(out, "%s\n", out_short);
+
+ } else {
+ tmp = out;
+ SANE_ASPRINTF2(out, "%s%s\n", out, out_short);
+ free(tmp);
+ }
+ }
+ }
+ closedir(dir);
+
+ return out;
+}
+
+char *rrd_list_r(int recursive, char *dirname)
+{
+#define SANE_ASPRINTF(_dest_str, _format, ...) \
if (asprintf(&_dest_str, _format, __VA_ARGS__) == -1) { \
if (out != NULL) { \
free(out); \
} \
- closedir(dir); \
errno = ENOMEM; \
return NULL; \
}
char *out = NULL, *tmp;
- char current[PATH_MAX];
glob_t buf;
char *ptr;
unsigned int i;
struct stat st;
- DIR *dir;
- struct dirent *entry;
/* Prevent moving up the directory tree */
if (strstr(dirname, "..")) {
/* if filename contains wildcards, then use glob() */
if (strchr(dirname, '*') || strchr(dirname, '?')) {
+ /* recursive list + globbing forbidden */
+ if (recursive) {
+ errno = EINVAL;
+ return NULL;
+ }
+
if (glob(dirname, 0, NULL, &buf)) {
globfree(&buf);
errno = ENOENT;
}
/* Process directory */
- dir = opendir(dirname);
-
- if (dir == NULL) {
- /* opendir sets errno */
+ if (stat(dirname, &st) != 0) {
return NULL;
}
- while ((entry = readdir(dir)) != NULL) {
-
- if ((strcmp(entry->d_name, ".") == 0) ||
- (strcmp(entry->d_name, "..") == 0)) {
- continue;
- }
-
- if (strlen(dirname) + strlen(entry->d_name) + 1 >= PATH_MAX) {
- continue;
- }
- snprintf(¤t[0], PATH_MAX, "%s/%s", dirname, entry->d_name);
-
- /* Only return directories and rrd files.
- * NOTE: stat(2) follows symlinks and gives info on target. */
- if (stat(current, &st) != 0) {
- continue;
- }
-
- if (!S_ISDIR(st.st_mode)) {
-
- if (S_ISREG(st.st_mode)) {
-
- ptr = strstr(entry->d_name, ".rrd");
-
- /* strlen() used to make sure it's matching
- * the end of string.
- */
- if (ptr == NULL || strlen(ptr) != 4) {
- continue;
- }
-
- } else {
- continue;
- }
- }
-
- if (out == NULL) {
- SANE_ASPRINTF2(out, "%s\n", entry->d_name);
-
- } else {
- tmp = out;
- SANE_ASPRINTF2(out, "%s%s\n", out, entry->d_name);
- free(tmp);
- }
+ if (!S_ISDIR(st.st_mode)) {
+ return NULL;
}
- closedir(dir);
-
- return out;
+ return rrd_list_rec(recursive, dirname, dirname);
}
char *rrd_list(int argc, char **argv)
char *opt_daemon = NULL;
int status;
int flushfirst = 1;
+ int recursive = 0;
char *list;
static struct optparse_long long_options[] = {
{"daemon", 'd', OPTPARSE_REQUIRED},
{"noflush", 'F', OPTPARSE_NONE},
+ {"recursive", 'r', OPTPARSE_NONE},
{0},
};
struct optparse options;
flushfirst = 0;
break;
+ case 'r':
+ recursive=1;
+ break;
case '?':
if (opt_daemon)
}
if ((argc - options.optind) != 1) {
- rrd_set_error ("Usage: rrdtool %s [--daemon <addr> [--noflush]] <directory>",
+ rrd_set_error ("Usage: rrdtool %s [--daemon <addr> [--noflush]] [--recursive] <directory>",
argv[0]);
if (opt_daemon != NULL) {
rrdc_connect (opt_daemon);
if (rrdc_is_connected (opt_daemon)) {
- list = rrdc_list(argv[options.optind]);
+ list = rrdc_list(recursive, argv[options.optind]);
rrdc_disconnect();
} else {
free(opt_daemon);
return NULL;
}
- list = rrd_list_r(argv[options.optind]);
+ list = rrd_list_r(recursive, argv[options.optind]);
if (list == NULL) {
fprintf(stderr, "%s", strerror(errno));
BUILD=$BUILDDIR/`basename $0`
LIST_DIR=$BUILDDIR/`basename $0`_dir
-# This is used both for 'direct' tests and for tests via rrdcached
+# This is used both for 'direct' tests and for tests via rrdcached
# (when RRDCACHED_ADDRESS is exported). In that case the 'root' directory
# is BASEDIR (see '-b' in functions::run_cached) and the paths in tests
# must be changed accordingly (see $LIST_TEST_DIR, $rrd)
cp ${BUILD}.rrd "$LIST_TEST_DIR"/
cp ${BUILD}.rrd "$LIST_TEST_DIR"/second.rrd
cp ${BUILD}.rrd "$LIST_TEST_DIR"/third.rrd
- list_count=`$RRDTOOL list "$LIST_TEST_DIR" | wc -l`
+
+ list_count=`$RRDTOOL list "/$1" | wc -l`
test $list_count -eq 3
report "directory with several RRDs"
touch "$LIST_TEST_DIR"/not_an_rrd
- list_count=`$RRDTOOL list "$LIST_TEST_DIR" | wc -l`
+ list_count=`$RRDTOOL list "/$1" | wc -l`
test $list_count -eq 3
report "only lists files with .rrd suffix"
mkdir -p "$LIST_TEST_DIR"/new_dir
- list_count=`$RRDTOOL list "$LIST_TEST_DIR" | wc -l`
+ list_count=`$RRDTOOL list "/$1" | wc -l`
test $list_count -eq 4
report "only lists RRDs and directories"
+
+ mkdir -p "$LIST_TEST_DIR"/new_dir2
+ mkdir -p "$LIST_TEST_DIR"/new_dir3
+ mkdir -p "$LIST_TEST_DIR"/new_dir4
+ cp ${BUILD}.rrd "$LIST_TEST_DIR"/new_dir2/fourth.rrd
+ cp ${BUILD}.rrd "$LIST_TEST_DIR"/new_dir2/fifth.rrd
+ list_count=`$RRDTOOL list --recursive "/$1" | wc -l`
+ test $list_count -eq 5
+ report "recursive list only lists rrd files"
}
################################################################################
fi
if is_cached; then
- mkdir -p "$LIST_DIR"
+ mkdir -p "$LIST_DIR"
# This relies on '-b' setting in functions::run_cached()
CACHED_DIR=`echo "$LIST_DIR" | sed "s|^$BASEDIR/||"`
do_list_tests "$CACHED_DIR"