xref: /dragonfly/sbin/mountctl/mountctl.c (revision 383684a1)
1 /*
2  * Copyright (c) 2003,2004 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/sbin/mountctl/mountctl.c,v 1.2 2005/01/09 03:06:14 dillon Exp $
35  */
36 /*
37  * This utility implements the userland mountctl command which is used to
38  * manage high level journaling on mount points.
39  */
40 
41 #include <sys/types.h>
42 #include <sys/param.h>
43 #include <sys/ucred.h>
44 #include <sys/mount.h>
45 #include <sys/time.h>
46 #include <sys/mountctl.h>
47 #include <stdio.h>
48 #include <fcntl.h>
49 #include <string.h>
50 #include <unistd.h>
51 #include <errno.h>
52 
53 static volatile void usage(void);
54 static void parse_option_keyword(const char *opt,
55 		const char **wopt, const char **xopt);
56 static int64_t getsize(const char *str);
57 static const char *numtostr(int64_t num);
58 
59 static int mountctl_scan(void (*func)(const char *, const char *, int, void *),
60 		const char *keyword, const char *mountpt, int fd);
61 static void mountctl_list(const char *keyword, const char *mountpt,
62 		int __unused fd, void *info);
63 static void mountctl_add(const char *keyword, const char *mountpt, int fd);
64 static void mountctl_delete(const char *keyword, const char *mountpt,
65 		int __unused fd, void __unused *);
66 static void mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *);
67 
68 /*
69  * For all options 0 means unspecified, -1 means noOPT or nonOPT, and a
70  * positive number indicates enabling or execution of the option.
71  */
72 static int freeze_opt;
73 static int start_opt;
74 static int close_opt;
75 static int abort_opt;
76 static int flush_opt;
77 static int reversable_opt;
78 static int twoway_opt;
79 static int64_t memfifo_opt;
80 static int64_t swapfifo_opt;
81 
82 int
83 main(int ac, char **av)
84 {
85     int fd;
86     int ch;
87     int aopt = 0;
88     int dopt = 0;
89     int fopt = 0;
90     int lopt = 0;
91     int mopt = 0;
92     int mimplied = 0;
93     const char *wopt = NULL;
94     const char *xopt = NULL;
95     const char *keyword = NULL;
96     const char *mountpt = NULL;
97     char *tmp;
98 
99     while ((ch = getopt(ac, av, "adflo:mw:x:ACFSZ")) != -1) {
100 	switch(ch) {
101 	case 'a':
102 	    aopt = 1;
103 	    if (aopt + dopt + lopt + mopt != 1) {
104 		fprintf(stderr, "too many action options specified\n");
105 		usage();
106 	    }
107 	    break;
108 	case 'd':
109 	    dopt = 1;
110 	    if (aopt + dopt + lopt + mopt != 1) {
111 		fprintf(stderr, "too many action options specified\n");
112 		usage();
113 	    }
114 	    break;
115 	case 'f':
116 	    fopt = 1;
117 	    break;
118 	case 'l':
119 	    lopt = 1;
120 	    if (aopt + dopt + lopt + mopt != 1) {
121 		fprintf(stderr, "too many action options specified\n");
122 		usage();
123 	    }
124 	    break;
125 	case 'o':
126 	    parse_option_keyword(optarg, &wopt, &xopt);
127 	    break;
128 	case 'm':
129 	    mopt = 1;
130 	    if (aopt + dopt + lopt + mopt != 1) {
131 		fprintf(stderr, "too many action options specified\n");
132 		usage();
133 	    }
134 	    break;
135 	case 'w':
136 	    wopt = optarg;
137 	    mimplied = 1;
138 	    break;
139 	case 'x':
140 	    xopt = optarg;
141 	    mimplied = 1;
142 	    break;
143 	case 'A':
144 	    mimplied = 1;
145 	    abort_opt = 1;
146 	    break;
147 	case 'C':
148 	    mimplied = 1;
149 	    close_opt = 1;
150 	    break;
151 	case 'F':
152 	    mimplied = 1;
153 	    flush_opt = 1;
154 	    break;
155 	case 'S':
156 	    mimplied = 1;
157 	    start_opt = 1;
158 	    break;
159 	case 'Z':
160 	    mimplied = 1;
161 	    freeze_opt = 1;
162 	    break;
163 	default:
164 	    fprintf(stderr, "unknown option: -%c\n", optopt);
165 	    usage();
166 	}
167     }
168     ac -= optind;
169     av += optind;
170 
171     /*
172      * Parse the keyword and/or mount point.
173      */
174     switch(ac) {
175     case 0:
176 	if (aopt) {
177 	    fprintf(stderr, "action requires a tag and/or mount "
178 			    "point to be specified\n");
179 	    usage();
180 	}
181 	break;
182     case 1:
183 	if (av[0][0] == '/') {
184 	    mountpt = av[0];
185 	    if ((keyword = strchr(mountpt, ':')) != NULL) {
186 		++keyword;
187 		tmp = strdup(mountpt);
188 		*strchr(tmp, ':') = 0;
189 		mountpt = tmp;
190 	    }
191 	} else {
192 	    keyword = av[0];
193 	}
194 	break;
195     default:
196 	fprintf(stderr, "unexpected extra arguments to command\n");
197 	usage();
198     }
199 
200     /*
201      * Additional sanity checks
202      */
203     if (aopt + dopt + lopt + mopt + mimplied == 0) {
204 	fprintf(stderr, "no action or implied action options were specified\n");
205 	usage();
206     }
207     if (mimplied && aopt + dopt + lopt == 0)
208 	mopt = 1;
209     if ((wopt || xopt) && !(aopt || mopt)) {
210 	fprintf(stderr, "-w/-x/path/fd options may only be used with -m/-a\n");
211 	usage();
212     }
213     if (aopt && (keyword == NULL || mountpt == NULL)) {
214 	fprintf(stderr, "a keyword AND a mountpt must be specified "
215 			"when adding a journal\n");
216 	usage();
217     }
218     if (fopt == 0 && mopt + dopt && keyword == NULL && mountpt == NULL) {
219 	fprintf(stderr, "a keyword, a mountpt, or both must be specified "
220 			"when modifying or deleting a journal, unless "
221 			"-f is also specified for safety\n");
222 	usage();
223     }
224 
225     /*
226      * Open the journaling file descriptor if required.
227      */
228     if (wopt && xopt) {
229 	fprintf(stderr, "you must specify only one of -w/-x/path/fd\n");
230 	exit(1);
231     } else if (wopt) {
232 	if ((fd = open(wopt, O_RDWR|O_CREAT|O_APPEND, 0666)) < 0) {
233 	    fprintf(stderr, "unable to create %s: %s\n", wopt, strerror(errno));
234 	    exit(1);
235 	}
236     } else if (xopt) {
237 	fd = strtol(xopt, NULL, 0);
238     } else if (aopt) {
239 	fd = 1;		/* stdout default for -a */
240     } else {
241 	fd = -1;
242     }
243 
244     /*
245      * And finally execute the core command.
246      */
247     if (lopt)
248 	mountctl_scan(mountctl_list, keyword, mountpt, fd);
249     if (aopt)
250 	mountctl_add(keyword, mountpt, fd);
251     if (dopt) {
252 	ch = mountctl_scan(mountctl_delete, keyword, mountpt, -1);
253 	if (ch)
254 	    printf("%d journals deleted\n", ch);
255 	else
256 	    printf("Unable to locate any matching journals\n");
257     }
258     if (mopt) {
259 	ch = mountctl_scan(mountctl_modify, keyword, mountpt, fd);
260 	if (ch)
261 	    printf("%d journals modified\n", ch);
262 	else
263 	    printf("Unable to locate any matching journals\n");
264     }
265 
266     return(0);
267 }
268 
269 static void
270 parse_option_keyword(const char *opt, const char **wopt, const char **xopt)
271 {
272     char *str = strdup(opt);
273     char *name;
274     char *val;
275     int negate;
276     int hasval;
277     int cannotnegate;
278 
279     /*
280      * multiple comma delimited options may be specified.
281      */
282     while ((name = strsep(&str, ",")) != NULL) {
283 	/*
284 	 * some options have associated data.
285 	 */
286 	if ((val = strchr(name, '=')) != NULL)
287 	    *val++ = 0;
288 
289 	/*
290 	 * options beginning with 'no' or 'non' are negated.  A positive
291 	 * number means not negated, a negative number means negated.
292 	 */
293 	negate = 1;
294 	cannotnegate = 0;
295 	hasval = 0;
296 	if (strncmp(name, "non", 3) == 0) {
297 	    name += 3;
298 	    negate = -1;
299 	} else if (strncmp(name, "no", 2) == 0) {
300 	    name += 2;
301 	    negate = -1;
302 	}
303 
304 	/*
305 	 * Parse supported options
306 	 */
307 	if (strcmp(name, "reversable") == 0) {
308 	    reversable_opt = negate;
309 	} else if (strcmp(name, "twoway") == 0) {
310 	    twoway_opt = negate;
311 	} else if (strcmp(name, "memfifo") == 0) {
312 	    cannotnegate = 1;
313 	    hasval = 1;
314 	    if (val) {
315 		if ((memfifo_opt = getsize(val)) == 0)
316 		    memfifo_opt = -1;
317 	    }
318 	} else if (strcmp(name, "swapfifo") == 0) {
319 	    if (val) {
320 		hasval = 1;
321 		if ((swapfifo_opt = getsize(val)) == 0)
322 		    swapfifo_opt = -1;
323 	    } else if (negate < 0) {
324 		swapfifo_opt = -1;
325 	    } else {
326 		hasval = 1;	/* force error */
327 	    }
328 	} else if (strcmp(name, "fd") == 0) {
329 	    cannotnegate = 1;
330 	    hasval = 1;
331 	    if (val)
332 		*xopt = val;
333 	} else if (strcmp(name, "path") == 0) {
334 	    cannotnegate = 1;
335 	    hasval = 1;
336 	    if (val)
337 		*wopt = val;
338 	} else if (strcmp(name, "freeze") == 0 || strcmp(name, "stop") == 0) {
339 	    if (negate < 0)
340 		start_opt = -negate;
341 	    else
342 		freeze_opt = negate;
343 	} else if (strcmp(name, "start") == 0) {
344 	    if (negate < 0)
345 		freeze_opt = -negate;
346 	    else
347 		start_opt = negate;
348 	} else if (strcmp(name, "close") == 0) {
349 	    close_opt = negate;
350 	} else if (strcmp(name, "abort") == 0) {
351 	    abort_opt = negate;
352 	} else if (strcmp(name, "flush") == 0) {
353 	    flush_opt = negate;
354 	} else {
355 	    fprintf(stderr, "unknown option keyword: %s\n", name);
356 	    exit(1);
357 	}
358 
359 	/*
360 	 * Sanity checks
361 	 */
362 	if (cannotnegate && negate < 0) {
363 	    fprintf(stderr, "option %s may not be negated\n", name);
364 	    exit(1);
365 	}
366 	if (hasval && val == NULL) {
367 	    fprintf(stderr, "option %s requires assigned data\n", name);
368 	    exit(1);
369 	}
370 	if (hasval == 0 && val) {
371 	    fprintf(stderr, "option %s does not take an assignment\n", name);
372 	    exit(1);
373 	}
374 
375     }
376 }
377 
378 static int
379 mountctl_scan(void (*func)(const char *, const char *, int, void *),
380 	    const char *keyword, const char *mountpt, int fd)
381 {
382     struct statfs *sfs;
383     int count;
384     int calls;
385     int i;
386     struct mountctl_status_journal statreq;
387     struct mountctl_journal_ret_status rstat[4];	/* BIG */
388 
389     calls = 0;
390     if (mountpt) {
391 	bzero(&statreq, sizeof(statreq));
392 	if (keyword) {
393 	    statreq.index = MC_JOURNAL_INDEX_ID;
394 	    count = strlen(keyword);
395 	    if (count > JIDMAX)
396 		count = JIDMAX;
397 	    bcopy(keyword, statreq.id, count);
398 	} else {
399 	    statreq.index = MC_JOURNAL_INDEX_ALL;
400 	}
401 	count = mountctl(mountpt, MOUNTCTL_STATUS_VFS_JOURNAL, -1,
402 			&statreq, sizeof(statreq), &rstat, sizeof(rstat));
403 	if (count > 0 && rstat[0].recsize != sizeof(rstat[0])) {
404 	    fprintf(stderr, "Unable to access status, "
405 			    "structure size mismatch\n");
406 	    exit(1);
407 	}
408 	if (count > 0) {
409 	    count /= sizeof(rstat[0]);
410 	    for (i = 0; i < count; ++i) {
411 		func(rstat[i].id, mountpt, fd, &rstat[i]);
412 		++calls;
413 	    }
414 	}
415     } else {
416 	if ((count = getmntinfo(&sfs, MNT_WAIT)) > 0) {
417 	    for (i = 0; i < count; ++i) {
418 		calls += mountctl_scan(func, keyword, sfs[i].f_mntonname, fd);
419 	    }
420 	} else if (count < 0) {
421 	    /* XXX */
422 	}
423     }
424     return(calls);
425 }
426 
427 static void
428 mountctl_list(const char *keyword, const char *mountpt, int __unused fd, void *info)
429 {
430     struct mountctl_journal_ret_status *rstat = info;
431 
432     printf("%s:%s\n", mountpt, rstat->id[0] ? rstat->id : "<NOID>");
433     printf("    membufsize=%s\n", numtostr(rstat->membufsize));
434     printf("    membufused=%s\n", numtostr(rstat->membufused));
435     printf("    membufiopend=%s\n", numtostr(rstat->membufiopend));
436     printf("    total_bytes=%s\n", numtostr(rstat->bytessent));
437 }
438 
439 static void
440 mountctl_add(const char *keyword, const char *mountpt, int fd)
441 {
442     struct mountctl_install_journal joinfo;
443     int error;
444 
445     bzero(&joinfo, sizeof(joinfo));
446     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
447 
448     error = mountctl(mountpt, MOUNTCTL_INSTALL_VFS_JOURNAL, fd,
449 			&joinfo, sizeof(joinfo), NULL, 0);
450     if (error == 0) {
451 	fprintf(stderr, "%s:%s added\n", mountpt, joinfo.id);
452     } else {
453 	fprintf(stderr, "%s:%s failed to add, error %s\n", mountpt, joinfo.id, strerror(errno));
454     }
455 }
456 
457 static void
458 mountctl_delete(const char *keyword, const char *mountpt, int __unused fd, void __unused *info)
459 {
460     struct mountctl_remove_journal joinfo;
461     int error;
462 
463     bzero(&joinfo, sizeof(joinfo));
464     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
465     error = mountctl(mountpt, MOUNTCTL_REMOVE_VFS_JOURNAL, -1,
466 			&joinfo, sizeof(joinfo), NULL, 0);
467     if (error == 0) {
468 	fprintf(stderr, "%s:%s deleted\n", mountpt, joinfo.id);
469     } else {
470 	fprintf(stderr, "%s:%s deletion failed, error %s\n", mountpt, joinfo.id, strerror(errno));
471     }
472 }
473 
474 static void
475 mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *info)
476 {
477     fprintf(stderr, "modify not yet implemented\n");
478 }
479 
480 
481 static volatile
482 void
483 usage(void)
484 {
485     printf(
486 	" mountctl -l [tag/mountpt | mountpt:tag]\n"
487 	" mountctl -a [-w output_path] [-x filedesc]\n"
488 	"             [-o option] [-o option ...] mountpt:tag\n"
489 	" mountctl -d [tag/mountpt | mountpt:tag]\n"
490 	" mountctl -m [-o option] [-o option ...] [tag/mountpt | mountpt:tag]\n"
491 	" mountctl -FZSCA [tag/mountpt | mountpt:tag]\n"
492     );
493     exit(1);
494 }
495 
496 static
497 int64_t
498 getsize(const char *str)
499 {
500     const char *suffix;
501     int64_t val;
502 
503     val = strtoll(str, &suffix, 0);
504     if (suffix) {
505 	switch(*suffix) {
506 	case 'b':
507 	    break;
508 	case 't':
509 	    val *= 1024;
510 	    /* fall through */
511 	case 'g':
512 	    val *= 1024;
513 	    /* fall through */
514 	case 'm':
515 	    val *= 1024;
516 	    /* fall through */
517 	case 'k':
518 	    val *= 1024;
519 	    /* fall through */
520 	    break;
521 	default:
522 	    fprintf(stderr, "data value '%s' has unknown suffix\n", str);
523 	    exit(1);
524 	}
525     }
526     return(val);
527 }
528 
529 static
530 const char *
531 numtostr(int64_t num)
532 {
533     static char buf[64];
534     int n;
535     double v = num;
536 
537     if (num < 1024)
538 	snprintf(buf, sizeof(buf), "%lld", num);
539     else if (num < 10 * 1024)
540 	snprintf(buf, sizeof(buf), "%3.2fK", num / 1024.0);
541     else if (num < 1024 * 1024)
542 	snprintf(buf, sizeof(buf), "%3.0fK", num / 1024.0);
543     else if (num < 10 * 1024 * 1024)
544 	snprintf(buf, sizeof(buf), "%3.2fM", num / (1024.0 * 1024.0));
545     else if (num < 1024 * 1024 * 1024)
546 	snprintf(buf, sizeof(buf), "%3.0fM", num / (1024.0 * 1024.0));
547     else if (num < 10LL * 1024 * 1024 * 1024)
548 	snprintf(buf, sizeof(buf), "%3.2fG", num / (1024.0 * 1024.0 * 1024.0));
549     else
550 	snprintf(buf, sizeof(buf), "%3.0fG", num / (1024.0 * 1024.0 * 1024.0));
551     return(buf);
552 }
553 
554