Skip to content

Commit

Permalink
cmd/flux-kvs: add ls subcommand
Browse files Browse the repository at this point in the history
Add new subcommand:
   flux kvs ls [-R] [-d] [-w COLS] [-1|-C] [-F] [key...]

This subcommand mimics ls(1) behavior and options,
as discussed in flux-framework#1158.
  • Loading branch information
garlick committed Aug 29, 2017
1 parent 0c87cfa commit 31462d8
Show file tree
Hide file tree
Showing 2 changed files with 281 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/cmd/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ AM_CPPFLAGS = \
$(ZMQ_CFLAGS)

fluxcmd_ldadd = \
$(top_builddir)/src/common/libkvs/libkvs.la \
$(top_builddir)/src/common/libflux-internal.la \
$(top_builddir)/src/common/libflux-core.la \
$(top_builddir)/src/common/libflux-optparse.la \
Expand Down
280 changes: 280 additions & 0 deletions src/cmd/flux-kvs.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,18 @@
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/ioctl.h>
#include <flux/core.h>
#include <flux/optparse.h>
#include <unistd.h>
#include <fcntl.h>
#include <jansson.h>
#include <czmq.h>

#include "src/common/libutil/xzmalloc.h"
#include "src/common/libutil/log.h"
#include "src/common/libutil/readall.h"
#include "src/common/libkvs/treeobj.h"

int cmd_get (optparse_t *p, int argc, char **argv);
int cmd_put (optparse_t *p, int argc, char **argv);
Expand All @@ -48,9 +51,12 @@ int cmd_dropcache (optparse_t *p, int argc, char **argv);
int cmd_copy (optparse_t *p, int argc, char **argv);
int cmd_move (optparse_t *p, int argc, char **argv);
int cmd_dir (optparse_t *p, int argc, char **argv);
int cmd_ls (optparse_t *p, int argc, char **argv);

static void dump_kvs_dir (kvsdir_t *dir, bool Ropt, bool dopt);

#define min(a,b) ((a)<(b)?(a):(b))

static struct optparse_option dir_opts[] = {
{ .name = "recursive", .key = 'R', .has_arg = 0,
.usage = "Recursively display keys under subdirectories",
Expand All @@ -61,6 +67,28 @@ static struct optparse_option dir_opts[] = {
OPTPARSE_TABLE_END
};

static struct optparse_option ls_opts[] = {
{ .name = "recursive", .key = 'R', .has_arg = 0,
.usage = "List directory recursively",
},
{ .name = "directory", .key = 'd', .has_arg = 0,
.usage = "List directory instead of contents",
},
{ .name = "width", .key = 'w', .has_arg = 1,
.usage = "Set output width to COLS. 0 means no limit",
},
{ .name = "1", .key = '1', .has_arg = 0,
.usage = "Force one entry per line",
},
{ .name = "classify", .key = 'F', .has_arg = 0,
.usage = "Append indicator (one of .@) to entries",
},
{ .name = "columns", .key = 'C', .has_arg = 0,
.usage = "List entries by columns",
},
OPTPARSE_TABLE_END
};

static struct optparse_option watch_opts[] = {
{ .name = "recursive", .key = 'R', .has_arg = 0,
.usage = "Recursively display keys under subdirectories",
Expand Down Expand Up @@ -116,6 +144,13 @@ static struct optparse_subcommand subcommands[] = {
0,
dir_opts
},
{ "ls",
"[-R] [-d] [-w COLS] [-1] [-W] [-C] [key...]",
"List directory",
cmd_ls,
0,
ls_opts
},
{ "unlink",
"key [key...]",
"Remove key",
Expand Down Expand Up @@ -816,6 +851,251 @@ int cmd_dir (optparse_t *p, int argc, char **argv)
return (0);
}

/* Find longest name in a directory.
*/
static int get_dir_maxname (kvsdir_t *dir)
{
kvsitr_t *itr;
const char *name;
int max = 0;

if (!(itr = kvsitr_create (dir)))
log_err_exit ("kvsitr_create");
while ((name = kvsitr_next (itr)))
if (max < strlen (name))
max = strlen (name);
kvsitr_destroy (itr);
return max;
}

/* Find longest name in a list of names.
*/
static int get_list_maxname (zlist_t *names)
{
const char *name;
int max = 0;

name = zlist_first (names);
while (name) {
if (max < strlen (name))
max = strlen (name);
name = zlist_next (names);
}
return max;
}

/* Determine terminal window width to use for columnar output.
* --width=COLS or -1 force the width to COLS or 1, respectively.
* If not forced and not a tty, use 1, unless -C, then use 80.
* Otherwise ask the tty how wide it is.
* N.B. --width=0 forces "unlimited" width.
*/
static int get_window_width (optparse_t *p, int fd)
{
struct winsize w;
int width;

if (optparse_hasopt (p, "1"))
return 1;
if ((width = optparse_get_int (p, "width", -1)) >= 0)
return width;
if (ioctl (fd, TIOCGWINSZ, &w) == 0)
return w.ws_col;
if (optparse_hasopt (p, "columns"))
return 80;
return 1;
}

/* List the content of 'dir', arranging output in columns that fit 'win_width'.
*/
static void list_kvs_dir_single (kvsdir_t *dir, int win_width, optparse_t *p)
{
kvsitr_t *itr;
const char *name;
int col_width = get_dir_maxname (dir) + 4;
int col = 0;
char *namebuf;

if (!(namebuf = malloc (col_width + 2)))
log_err_exit ("malloc");
if (!(itr = kvsitr_create (dir)))
log_err_exit ("kvsitr_create");
while ((name = kvsitr_next (itr))) {
if (win_width != 0 && col + col_width > win_width && col > 0) {
printf ("\n");
col = 0;
}
strcpy (namebuf, name);
if (optparse_hasopt (p, "classify")) {
if (kvsdir_isdir (dir, name))
strcat (namebuf, ".");
else if (kvsdir_issymlink (dir, name))
strcat (namebuf, "@");
}
printf ("%-*s", min (col_width, win_width), namebuf);
col += col_width;
}
printf ("\n");
kvsitr_destroy (itr);
free (namebuf);
}

/* List contents of directory pointed to by 'key'.
* This function is called recursively if the -R option was specified.
*/
static void list_kvs_dir (flux_t *h, const char *key, optparse_t *p,
int win_width, bool print_label, bool print_vspace)
{
flux_future_t *f;
kvsdir_t *dir;
kvsitr_t *itr;
const char *name, *json_str;

if (!(f = flux_kvs_lookup (h, FLUX_KVS_READDIR, key))
|| flux_kvs_lookup_get (f, &json_str) < 0) {
log_err_exit ("%s", key);
goto done;
}

if (print_label)
printf ("%s%s:\n", print_vspace ? "\n" : "", key);
if (!(dir = kvsdir_create (h, NULL, key, json_str)))
log_err_exit ("kvsdir_create");
list_kvs_dir_single (dir, win_width, p);

if (optparse_hasopt (p, "recursive")) {
if (!(itr = kvsitr_create (dir)))
log_err_exit ("kvsitr_create");
while ((name = kvsitr_next (itr))) {
if (kvsdir_isdir (dir, name)) {
char *nkey;
if (!(nkey = kvsdir_key_at (dir, name))) {
log_err ("%s: kvsdir_key_at failed", name);
continue;
}
list_kvs_dir (h, nkey, p, win_width, print_label, true);
free (nkey);
}
}
kvsdir_destroy (dir);
}
done:
flux_future_destroy (f);
}

/* List keys, arranging in columns so that they fit within 'win_width'.
* The keys are popped off the list and freed.
*/
static void list_kvs_keys (zlist_t *singles, int win_width)
{
char *name;
int col_width = get_list_maxname (singles) + 4;
int col = 0;

while ((name = zlist_pop (singles))) {
if (win_width != 0 && col + col_width > win_width && col > 0) {
printf ("\n");
col = 0;
}
printf ("%-*s", min (col_width, win_width), name);
col += col_width;
free (name);
}
printf ("\n");
}

/* Put key in 'dirs' or 'singles' list, depending on whether
* its contents are to be listed or not. If -F is specified,
* 'singles' key names are decorated based on their type.
*/
static void categorize_key (optparse_t *p, const char *key,
zlist_t *dirs, zlist_t *singles)
{
flux_t *h = (flux_t *)optparse_get_data (p, "flux_handle");
flux_future_t *f;
const char *json_str;
char *nkey;
json_t *treeobj = NULL;

if (!(f = flux_kvs_lookup (h, FLUX_KVS_TREEOBJ, key)))
log_err_exit ("flux_kvs_lookup");
if (flux_kvs_lookup_get (f, &json_str) < 0) {
fprintf (stderr, "%s: %s\n", key, flux_strerror (errno));
goto done;
}
if (!(treeobj = treeobj_decode (json_str)))
log_err_exit ("%s: metadata decode error", key);
if (treeobj_is_dir (treeobj) || treeobj_is_dirref (treeobj)) {
if (optparse_hasopt (p, "directory")) {
nkey = xasprintf ("%s%s", key,
optparse_hasopt (p, "classify") ? "." : "");
if (zlist_append (singles, nkey) < 0)
log_err_exit ("zlist_append");
}
else
if (zlist_append (dirs, xstrdup (key)) < 0)
log_err_exit ("zlist_append");
}
else if (treeobj_is_val (treeobj) || treeobj_is_valref (treeobj)) {
if (zlist_append (singles, xstrdup (key)) < 0)
log_err_exit ("zlist_append");
}
else if (treeobj_is_symlink (treeobj)) {
nkey = xasprintf ("%s%s", key,
optparse_hasopt (p, "classify") ? "@" : "");
if (zlist_append (singles, nkey) < 0)
log_err_exit ("zlist_append");
}
done:
json_decref (treeobj);
flux_future_destroy (f);
return;
}

/* Mimic ls(1).
* Strategy: sort keys from command line into two lists: 'dirs' for
* directories to be expanded, and 'singles' for others.
* First display the singles, then display directories, optionally recursing.
* Assume "." if no keys were specified on the command line.
*/
int cmd_ls (optparse_t *p, int argc, char **argv)
{
flux_t *h = (flux_t *)optparse_get_data (p, "flux_handle");
int optindex = optparse_option_index (p);
int win_width = get_window_width (p, STDOUT_FILENO);
zlist_t *dirs, *singles;
char *key;
bool print_label = false; // print "name:" before directory contents
bool print_vspace = false; // print vertical space before label

if (!(dirs = zlist_new ()) || !(singles = zlist_new ()))
log_err_exit ("zlist_new");

if (optindex == argc)
categorize_key (p, ".", dirs, singles);
while (optindex < argc)
categorize_key (p, argv[optindex++], dirs, singles);

if (zlist_size (singles) > 0) {
list_kvs_keys (singles, win_width);
print_label = true;
print_vspace = true;
}

if (optparse_hasopt (p, "recursive") || zlist_size (dirs) > 1)
print_label = true;
while ((key = zlist_pop (dirs))) {
list_kvs_dir (h, key, p, win_width, print_label, print_vspace);
print_vspace = true;
free (key);
}

zlist_destroy (&dirs);
zlist_destroy (&singles);

return (0);
}

int cmd_copy (optparse_t *p, int argc, char **argv)
{
flux_t *h = (flux_t *)optparse_get_data (p, "flux_handle");
Expand Down

0 comments on commit 31462d8

Please sign in to comment.