1 /*
2  * This file is subject to the terms and conditions of the GNU General
3  * Public License.  See the file "COPYING" in the main directory of
4  * this archive for more details.
5  *
6  * Copyright (C) 2005 by Christian Limpach
7  * Copyright (C) 2005 XenSource Ltd.
8  *
9  */
10 
11 #include <err.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <getopt.h>
15 #include <stdarg.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <termios.h>
20 #include <unistd.h>
21 #include <xenstore.h>
22 
23 #include <sys/ioctl.h>
24 
25 #define PATH_SEP '/'
26 #define MAX_PATH_LEN 256
27 
28 
29 enum mode {
30     MODE_unknown,
31     MODE_chmod,
32     MODE_exists,
33     MODE_list,
34     MODE_ls,
35     MODE_read,
36     MODE_rm,
37     MODE_write,
38     MODE_watch,
39 };
40 
41 static char *output_buf = NULL;
42 static int output_pos = 0;
43 static struct expanding_buffer ebuf;
44 
45 static int output_size = 0;
46 
47 /* make sure there is at least 'len' more space in output_buf */
expand_buffer(size_t len)48 static void expand_buffer(size_t len)
49 {
50     if (output_pos + len > output_size) {
51         output_size += len + 1024;
52         output_buf = realloc(output_buf, output_size);
53         if (output_buf == NULL)
54             err(1, "malloc");
55     }
56 }
57 
58 static void
output(const char * fmt,...)59 output(const char *fmt, ...) {
60     va_list ap;
61     int len;
62     char buf[1];
63 
64     va_start(ap, fmt);
65     len = vsnprintf(buf, 1, fmt, ap);
66     if (len < 0)
67 	err(1, "output");
68     va_end(ap);
69     expand_buffer(len + 1);
70     va_start(ap, fmt);
71     if (vsnprintf(&output_buf[output_pos], len + 1, fmt, ap) != len)
72 	err(1, "output");
73     va_end(ap);
74     output_pos += len;
75 }
76 
77 static void
output_raw(const char * data,int len)78 output_raw(const char *data, int len)
79 {
80     expand_buffer(len);
81     memcpy(&output_buf[output_pos], data, len);
82     output_pos += len;
83 }
84 
85 static void
usage(enum mode mode,int incl_mode,const char * progname)86 usage(enum mode mode, int incl_mode, const char *progname)
87 {
88     const char *mstr = NULL;
89 
90     switch (mode) {
91     case MODE_unknown:
92 	errx(1, "Usage: %s <mode> [-h] [...]", progname);
93     case MODE_read:
94 	mstr = incl_mode ? "read " : "";
95 	errx(1, "Usage: %s %s[-h] [-p] [-s] [-R] key [...]", progname, mstr);
96     case MODE_write:
97 	mstr = incl_mode ? "write " : "";
98 	errx(1, "Usage: %s %s[-h] [-s] [-R] key value [...]", progname, mstr);
99     case MODE_rm:
100 	mstr = incl_mode ? "rm " : "";
101 	errx(1, "Usage: %s %s[-h] [-s] [-t] key [...]", progname, mstr);
102     case MODE_exists:
103 	mstr = incl_mode ? "exists " : "";
104 	/* fallthrough */
105     case MODE_list:
106 	mstr = mstr ? : incl_mode ? "list " : "";
107 	errx(1, "Usage: %s %s[-h] [-p] [-s] key [...]", progname, mstr);
108     case MODE_ls:
109 	mstr = mstr ? : incl_mode ? "ls " : "";
110 	errx(1, "Usage: %s %s[-h] [-f] [-p] [-s] [path]", progname, mstr);
111     case MODE_chmod:
112 	mstr = incl_mode ? "chmod " : "";
113 	errx(1, "Usage: %s %s[-h] [-u] [-r] [-s] key <mode [modes...]>", progname, mstr);
114     case MODE_watch:
115 	mstr = incl_mode ? "watch " : "";
116 	errx(1, "Usage: %s %s[-h] [-n NR] key", progname, mstr);
117     }
118 }
119 
120 
121 static int
do_rm(char * path,struct xs_handle * xsh,xs_transaction_t xth)122 do_rm(char *path, struct xs_handle *xsh, xs_transaction_t xth)
123 {
124     if (xs_rm(xsh, xth, path)) {
125         return 0;
126     }
127     else {
128         warnx("could not remove path %s", path);
129         return 1;
130     }
131 }
132 
133 #define STRING_MAX XENSTORE_ABS_PATH_MAX+1024
134 static int max_width = 80;
135 static int desired_width = 60;
136 static int show_whole_path = 0;
137 
138 #define TAG " = \"...\""
139 #define TAG_LEN strlen(TAG)
140 
141 #define MIN(a, b) (((a) < (b))? (a) : (b))
142 
do_ls(struct xs_handle * h,char * path,int cur_depth,int show_perms)143 static void do_ls(struct xs_handle *h, char *path, int cur_depth, int show_perms)
144 {
145     char **e;
146     char *newpath, *val;
147     int newpath_len;
148     int i;
149     unsigned int num, len;
150 
151     e = xs_directory(h, XBT_NULL, path, &num);
152     if (e == NULL) {
153         if (cur_depth && errno == ENOENT) {
154             /* If a node disappears while recursing, silently move on. */
155             return;
156         }
157 
158         err(1, "xs_directory (%s)", path);
159     }
160 
161     newpath = malloc(STRING_MAX);
162     if (!newpath)
163       err(1, "malloc in do_ls");
164 
165     for (i = 0; i<num; i++) {
166         char buf[MAX_STRLEN(unsigned int)+1];
167         struct xs_permissions *perms;
168         unsigned int nperms;
169         int linewid;
170 
171         /* Compose fullpath */
172         newpath_len = snprintf(newpath, STRING_MAX, "%s%s%s", path,
173                 path[strlen(path)-1] == '/' ? "" : "/",
174                 e[i]);
175 
176         /* Print indent and path basename */
177         linewid = 0;
178         if (show_whole_path) {
179             fputs(newpath, stdout);
180         } else {
181             for (; linewid<cur_depth; linewid++) {
182                 putchar(' ');
183             }
184             linewid += printf("%.*s",
185                               (int) (max_width - TAG_LEN - linewid), e[i]);
186         }
187 
188 	/* Fetch value */
189         if ( newpath_len < STRING_MAX ) {
190             val = xs_read(h, XBT_NULL, newpath, &len);
191         }
192         else {
193             /* Path was truncated and thus invalid */
194             val = NULL;
195             len = 0;
196         }
197 
198         /* Print value */
199         if (val == NULL) {
200             printf(":\n");
201         }
202         else {
203             if (max_width < (linewid + len + TAG_LEN)) {
204                 printf(" = \"%.*s\\...\"",
205                        (int)(max_width - TAG_LEN - linewid),
206 		       sanitise_value(&ebuf, val, len));
207             }
208             else {
209                 linewid += printf(" = \"%s\"",
210 				  sanitise_value(&ebuf, val, len));
211                 if (show_perms) {
212                     putchar(' ');
213                     for (linewid++;
214                          linewid < MIN(desired_width, max_width);
215                          linewid++)
216                         putchar((linewid & 1)? '.' : ' ');
217                 }
218             }
219         }
220         free(val);
221 
222         if (show_perms) {
223             perms = xs_get_permissions(h, XBT_NULL, newpath, &nperms);
224             if (perms == NULL) {
225                 warn("\ncould not access permissions for %s", e[i]);
226             }
227             else {
228                 int i;
229                 fputs("  (", stdout);
230                 for (i = 0; i < nperms; i++) {
231                     if (i)
232                         putchar(',');
233                     xs_perm_to_string(perms+i, buf, sizeof(buf));
234                     fputs(buf, stdout);
235                 }
236                 putchar(')');
237             }
238         }
239 
240         putchar('\n');
241 
242         do_ls(h, newpath, cur_depth+1, show_perms);
243     }
244     free(e);
245     free(newpath);
246 }
247 
248 static void
do_chmod(char * path,struct xs_permissions * perms,int nperms,int upto,int recurse,struct xs_handle * xsh,xs_transaction_t xth)249 do_chmod(char *path, struct xs_permissions *perms, int nperms, int upto,
250 	 int recurse, struct xs_handle *xsh, xs_transaction_t xth)
251 {
252     int ret;
253 
254     if (!path[0])
255 	return;
256 
257     ret = xs_set_permissions(xsh, xth, path, perms, nperms);
258     if (!ret)
259 	err(1, "Error occurred setting permissions on '%s'", path);
260 
261     if (upto) {
262 	/* apply same permissions to all parent entries: */
263 	char *path_sep_ptr = strrchr(path, PATH_SEP);
264 	if (!path_sep_ptr)
265 	    errx(1, "Unable to locate path separator '%c' in '%s'",
266 		 PATH_SEP, path);
267 
268 	*path_sep_ptr = '\0'; /* truncate path */
269 
270 	do_chmod(path, perms, nperms, 1, 0, xsh, xth);
271 
272 	*path_sep_ptr = PATH_SEP;
273     }
274 
275     if (recurse) {
276 	char buf[MAX_PATH_LEN];
277 
278 	/* apply same permissions to all child entries: */
279 	unsigned int xsval_n;
280 	char **xsval = xs_directory(xsh, xth, path, &xsval_n);
281 
282 	if (xsval) {
283 	    int i;
284 	    for (i = 0; i < xsval_n; i++) {
285 		snprintf(buf, MAX_PATH_LEN, "%s/%s", path, xsval[i]);
286 
287 		do_chmod(buf, perms, nperms, 0, 1, xsh, xth);
288 	    }
289 
290 	    free(xsval);
291 	}
292     }
293 }
294 
295 static void
do_watch(struct xs_handle * xsh,int max_events)296 do_watch(struct xs_handle *xsh, int max_events)
297 {
298     int count = 0;
299     char **vec = NULL;
300 
301     for ( count = 0; max_events == -1 || count < max_events; count++ ) {
302 	unsigned int num;
303 
304 	vec = xs_read_watch(xsh, &num);
305 	if (vec == NULL)
306 	    continue;
307 
308 	printf("%s\n", vec[XS_WATCH_PATH]);
309 	fflush(stdout);
310 	free(vec);
311     }
312 }
313 
314 static int
perform(enum mode mode,int optind,int argc,char ** argv,struct xs_handle * xsh,xs_transaction_t xth,int prefix,int tidy,int upto,int recurse,int nr_watches,int raw)315 perform(enum mode mode, int optind, int argc, char **argv, struct xs_handle *xsh,
316         xs_transaction_t xth, int prefix, int tidy, int upto, int recurse, int nr_watches,
317         int raw)
318 {
319     switch (mode) {
320     case MODE_ls:
321 	if (optind == argc)
322 	{
323 	    optind=0;
324 	    argc=1;
325 	    argv[0] = "/";
326 	}
327 	break;
328     default:
329 	break;
330     }
331 
332     while (optind < argc) {
333         switch (mode) {
334         case MODE_unknown:
335             /* CANNOT BE REACHED */
336             errx(1, "invalid mode %d", mode);
337         case MODE_read: {
338             unsigned len;
339             char *val = xs_read(xsh, xth, argv[optind], &len);
340             if (val == NULL) {
341                 warnx("couldn't read path %s", argv[optind]);
342                 return 1;
343             }
344             if (prefix)
345                 output("%s: ", argv[optind]);
346             if (raw)
347                 output_raw(val, len);
348             else
349                 output("%s\n", sanitise_value(&ebuf, val, len));
350             free(val);
351             optind++;
352             break;
353         }
354         case MODE_write: {
355             char *val_spec = argv[optind + 1];
356             char *val;
357             unsigned len;
358             if (raw) {
359                 val = val_spec;
360                 len = strlen(val_spec);
361             } else {
362                 expanding_buffer_ensure(&ebuf, strlen(val_spec)+1);
363                 unsanitise_value(ebuf.buf, &len, val_spec);
364                 val = ebuf.buf;
365             }
366             if (!xs_write(xsh, xth, argv[optind], val, len)) {
367                 warnx("could not write path %s", argv[optind]);
368                 return 1;
369             }
370             optind += 2;
371         } break;
372         case MODE_rm: {
373             /* Remove the specified path.  If the tidy flag is set, then also
374                remove any containing directories that are both empty and have no
375                value attached, and repeat, recursing all the way up to the root if
376                necessary.
377             */
378 
379             char *slash, *path = argv[optind];
380 
381             if (tidy) {
382                 /* Copy path, because we can't modify argv because we will need it
383                    again if xs_transaction_end gives us EAGAIN. */
384                 char *p = malloc(strlen(path) + 1);
385                 strcpy(p, path);
386                 path = p;
387 
388             again:
389                 if (do_rm(path, xsh, xth)) {
390                     return 1;
391                 }
392 
393                 slash = strrchr(p, '/');
394                 if (slash) {
395                     char *val;
396                     unsigned len;
397                     *slash = '\0';
398                     val = xs_read(xsh, xth, p, &len);
399                     if (val && len == 0) {
400                         unsigned int num;
401                         char ** list = xs_directory(xsh, xth, p, &num);
402 
403                         if (list) {
404                             free(list);
405                             if (num == 0){
406                                 free(val);
407                                 goto again;
408                             }
409                         }
410                     }
411                     free(val);
412                 }
413 
414                 free(path);
415             }
416             else {
417                 if (do_rm(path, xsh, xth)) {
418                     return 1;
419                 }
420             }
421 
422             optind++;
423             break;
424         }
425         case MODE_exists: {
426             char *val = xs_read(xsh, xth, argv[optind], NULL);
427             if (val == NULL) {
428                 return 1;
429             }
430             free(val);
431             optind++;
432             break;
433         }
434         case MODE_list: {
435             unsigned int i, num;
436             char **list = xs_directory(xsh, xth, argv[optind], &num);
437             if (list == NULL) {
438                 warnx("could not list path %s", argv[optind]);
439                 return 1;
440             }
441             for (i = 0; i < num; i++) {
442                 if (prefix)
443                     output("%s/", argv[optind]);
444                 output("%s\n", list[i]);
445             }
446             free(list);
447             optind++;
448             break;
449         }
450         case MODE_ls: {
451             do_ls(xsh, argv[optind], 0, prefix);
452             optind++;
453             break;
454         }
455         case MODE_chmod: {
456             /* save path pointer: */
457             char *path = argv[optind++];
458             int nperms = argc - optind;
459             struct xs_permissions perms[nperms];
460             int i;
461             for (i = 0; argv[optind]; optind++, i++)
462             {
463                 perms[i].id = atoi(argv[optind]+1);
464 
465                 switch (argv[optind][0])
466                 {
467                 case 'n':
468                     perms[i].perms = XS_PERM_NONE;
469                     break;
470                 case 'r':
471                     perms[i].perms = XS_PERM_READ;
472                     break;
473                 case 'w':
474                     perms[i].perms = XS_PERM_WRITE;
475                     break;
476                 case 'b':
477                     perms[i].perms = XS_PERM_READ | XS_PERM_WRITE;
478                     break;
479                 default:
480                     errx(1, "Invalid permission specification: '%c'",
481                          argv[optind][0]);
482                 }
483             }
484 
485             do_chmod(path, perms, nperms, upto, recurse, xsh, xth);
486             break;
487         }
488         case MODE_watch: {
489             for (; argv[optind]; optind++) {
490                 const char *w = argv[optind];
491 
492                 if (!xs_watch(xsh, w, w))
493                     errx(1, "Unable to add watch on %s\n", w);
494             }
495             do_watch(xsh, nr_watches);
496         }
497         }
498     }
499 
500     return 0;
501 }
502 
lookup_mode(const char * m)503 static enum mode lookup_mode(const char *m)
504 {
505     if (strcmp(m, "read") == 0)
506 	return MODE_read;
507     else if (strcmp(m, "chmod") == 0)
508 	return MODE_chmod;
509     else if (strcmp(m, "exists") == 0)
510 	return MODE_exists;
511     else if (strcmp(m, "list") == 0)
512 	return MODE_list;
513     else if (strcmp(m, "ls") == 0)
514 	return MODE_ls;
515     else if (strcmp(m, "rm") == 0)
516 	return MODE_rm;
517     else if (strcmp(m, "write") == 0)
518 	return MODE_write;
519     else if (strcmp(m, "read") == 0)
520 	return MODE_read;
521     else if (strcmp(m, "watch") == 0)
522 	return MODE_watch;
523 
524     errx(1, "unknown mode %s\n", m);
525     return 0;
526 }
527 
528 int
main(int argc,char ** argv)529 main(int argc, char **argv)
530 {
531     struct xs_handle *xsh;
532     xs_transaction_t xth = XBT_NULL;
533     int ret = 0, socket = 0;
534     int prefix = 0;
535     int tidy = 0;
536     int upto = 0;
537     int recurse = 0;
538     int nr_watches = -1;
539     int transaction;
540     int raw = 0;
541     struct winsize ws;
542     enum mode mode;
543 
544     const char *_command = strrchr(argv[0], '/');
545     const char *command = _command ? &_command[1] : argv[0];
546     int switch_argv = -1; /* which element of argv did we switch on */
547 
548     if (strncmp(command, "xenstore-", strlen("xenstore-")) == 0)
549     {
550 	switch_argv = 0;
551 	command = command + strlen("xenstore-");
552     }
553     else if (argc < 2)
554 	usage(MODE_unknown, 0, argv[0]);
555     else
556     {
557 	command = argv[1];
558 	switch_argv = 1;
559     }
560 
561     mode = lookup_mode(command);
562 
563     while (1) {
564 	int c, index = 0;
565 	static struct option long_options[] = {
566 	    {"help",    0, 0, 'h'},
567 	    {"flat",    0, 0, 'f'}, /* MODE_ls */
568 	    {"socket",  0, 0, 's'},
569 	    {"prefix",  0, 0, 'p'}, /* MODE_read || MODE_list || MODE_ls */
570 	    {"tidy",    0, 0, 't'}, /* MODE_rm */
571 	    {"upto",    0, 0, 'u'}, /* MODE_chmod */
572 	    {"recurse", 0, 0, 'r'}, /* MODE_chmod */
573 	    {"number",  1, 0, 'n'}, /* MODE_watch */
574 	    {"raw",     0, 0, 'R'}, /* MODE_read || MODE_write */
575 	    {0, 0, 0, 0}
576 	};
577 
578 	c = getopt_long(argc - switch_argv, argv + switch_argv, "hfspturn:R",
579 			long_options, &index);
580 	if (c == -1)
581 	    break;
582 
583 	switch (c) {
584 	case 'h':
585 	    usage(mode, switch_argv, argv[0]);
586 	    /* NOTREACHED */
587         case 'f':
588 	    if ( mode == MODE_ls ) {
589 		max_width = INT_MAX/2;
590 		desired_width = 0;
591 		show_whole_path = 1;
592 	    } else {
593 		usage(mode, switch_argv, argv[0]);
594 	    }
595             break;
596         case 's':
597             socket = 1;
598             break;
599 	case 'p':
600 	    if ( mode == MODE_read || mode == MODE_list || mode == MODE_ls )
601 		prefix = 1;
602 	    else
603 		usage(mode, switch_argv, argv[0]);
604 	    break;
605 	case 't':
606 	    if ( mode == MODE_rm )
607 		tidy = 1;
608 	    else
609 		usage(mode, switch_argv, argv[0]);
610 	    break;
611 	case 'u':
612 	    if ( mode == MODE_chmod )
613 		upto = 1;
614 	    else
615 		usage(mode, switch_argv, argv[0]);
616 	    break;
617 	case 'r':
618 	    if ( mode == MODE_chmod )
619 		recurse = 1;
620 	    else
621 		usage(mode, switch_argv, argv[0]);
622 	    break;
623 	case 'n':
624 	    if ( mode == MODE_watch )
625 		nr_watches = atoi(optarg);
626 	    else
627 		usage(mode, switch_argv, argv[0]);
628 	    break;
629 	case 'R':
630 	    if ( mode == MODE_read || mode == MODE_write )
631 		raw = 1;
632 	    else
633 		usage(mode, switch_argv, argv[0]);
634 	    break;
635 	}
636     }
637 
638     switch (mode) {
639     case MODE_ls:
640 	break;
641     case MODE_write:
642 	if ((argc - switch_argv - optind) % 2 == 1) {
643 	    usage(mode, switch_argv, argv[0]);
644 	    /* NOTREACHED */
645 	}
646 	/* DROP-THRU */
647     default:
648 	if (optind == argc - switch_argv) {
649 	    usage(mode, switch_argv, argv[0]);
650 	    /* NOTREACHED */
651 	}
652     }
653 
654     switch (mode) {
655     case MODE_read:
656 	transaction = (argc - switch_argv - optind) > 1;
657 	break;
658     case MODE_write:
659 	transaction = (argc - switch_argv - optind) > 2;
660 	break;
661     case MODE_ls:
662     case MODE_watch:
663 	transaction = 0;
664 	break;
665     default:
666 	transaction = 1;
667 	break;
668     }
669 
670     if ( mode == MODE_ls )
671     {
672 	memset(&ws, 0, sizeof(ws));
673 	ret = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
674 	if (!ret)
675 	    max_width = ws.ws_col - 2;
676     }
677 
678     xsh = xs_open(socket ? XS_OPEN_SOCKETONLY : 0);
679     if (xsh == NULL) err(1, "xs_open");
680 
681 again:
682     if (transaction) {
683 	xth = xs_transaction_start(xsh);
684 	if (xth == XBT_NULL)
685 	    errx(1, "couldn't start transaction");
686     }
687 
688     ret = perform(mode, optind, argc - switch_argv, argv + switch_argv, xsh, xth, prefix, tidy, upto, recurse, nr_watches, raw);
689 
690     if (transaction && !xs_transaction_end(xsh, xth, ret)) {
691 	if (ret == 0 && errno == EAGAIN) {
692 	    output_pos = 0;
693 	    goto again;
694 	}
695 	errx(1, "couldn't end transaction");
696     }
697 
698     if (output_pos)
699         fwrite(output_buf, 1, output_pos, stdout);
700 
701     free(output_buf);
702     free(ebuf.buf);
703 
704     if (xsh)
705         xs_close(xsh);
706 
707     return ret;
708 }
709