xref: /dflybsd-src/sbin/mountctl/mountctl.c (revision e6f30c11b835a7878a0ca02133e6bbb9abfad4ab)
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.5 2005/07/06 06:04:32 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 exitCode;
73 static int freeze_opt;
74 static int start_opt;
75 static int close_opt;
76 static int abort_opt;
77 static int flush_opt;
78 static int reversable_opt;
79 static int twoway_opt;
80 static int64_t memfifo_opt;
81 static int64_t swapfifo_opt;
82 
83 int
84 main(int ac, char **av)
85 {
86     int fd;
87     int ch;
88     int aopt = 0;
89     int dopt = 0;
90     int fopt = 0;
91     int lopt = 0;
92     int mopt = 0;
93     int mimplied = 0;
94     const char *wopt = NULL;
95     const char *xopt = NULL;
96     const char *keyword = NULL;
97     const char *mountpt = NULL;
98     char *tmp;
99 
100     while ((ch = getopt(ac, av, "2adflo:mw:x:ACFSZ")) != -1) {
101 	switch(ch) {
102 	case '2':
103 	    twoway_opt = 1;
104 	    break;
105 	case 'a':
106 	    aopt = 1;
107 	    if (aopt + dopt + lopt + mopt != 1) {
108 		fprintf(stderr, "too many action options specified\n");
109 		usage();
110 	    }
111 	    break;
112 	case 'd':
113 	    dopt = 1;
114 	    if (aopt + dopt + lopt + mopt != 1) {
115 		fprintf(stderr, "too many action options specified\n");
116 		usage();
117 	    }
118 	    break;
119 	case 'f':
120 	    fopt = 1;
121 	    break;
122 	case 'l':
123 	    lopt = 1;
124 	    if (aopt + dopt + lopt + mopt != 1) {
125 		fprintf(stderr, "too many action options specified\n");
126 		usage();
127 	    }
128 	    break;
129 	case 'o':
130 	    parse_option_keyword(optarg, &wopt, &xopt);
131 	    break;
132 	case 'm':
133 	    mopt = 1;
134 	    if (aopt + dopt + lopt + mopt != 1) {
135 		fprintf(stderr, "too many action options specified\n");
136 		usage();
137 	    }
138 	    break;
139 	case 'w':
140 	    wopt = optarg;
141 	    mimplied = 1;
142 	    break;
143 	case 'x':
144 	    xopt = optarg;
145 	    mimplied = 1;
146 	    break;
147 	case 'A':
148 	    mimplied = 1;
149 	    abort_opt = 1;
150 	    break;
151 	case 'C':
152 	    mimplied = 1;
153 	    close_opt = 1;
154 	    break;
155 	case 'F':
156 	    mimplied = 1;
157 	    flush_opt = 1;
158 	    break;
159 	case 'S':
160 	    mimplied = 1;
161 	    start_opt = 1;
162 	    break;
163 	case 'Z':
164 	    mimplied = 1;
165 	    freeze_opt = 1;
166 	    break;
167 	default:
168 	    fprintf(stderr, "unknown option: -%c\n", optopt);
169 	    usage();
170 	}
171     }
172     ac -= optind;
173     av += optind;
174 
175     /*
176      * Parse the keyword and/or mount point.
177      */
178     switch(ac) {
179     case 0:
180 	if (aopt) {
181 	    fprintf(stderr, "action requires a tag and/or mount "
182 			    "point to be specified\n");
183 	    usage();
184 	}
185 	break;
186     case 1:
187 	if (av[0][0] == '/') {
188 	    mountpt = av[0];
189 	    if ((keyword = strchr(mountpt, ':')) != NULL) {
190 		++keyword;
191 		tmp = strdup(mountpt);
192 		*strchr(tmp, ':') = 0;
193 		mountpt = tmp;
194 	    }
195 	} else {
196 	    keyword = av[0];
197 	}
198 	break;
199     default:
200 	fprintf(stderr, "unexpected extra arguments to command\n");
201 	usage();
202     }
203 
204     /*
205      * Additional sanity checks
206      */
207     if (aopt + dopt + lopt + mopt + mimplied == 0) {
208 	fprintf(stderr, "no action or implied action options were specified\n");
209 	usage();
210     }
211     if (mimplied && aopt + dopt + lopt == 0)
212 	mopt = 1;
213     if ((wopt || xopt) && !(aopt || mopt)) {
214 	fprintf(stderr, "-w/-x/path/fd options may only be used with -m/-a\n");
215 	usage();
216     }
217     if (aopt && (keyword == NULL || mountpt == NULL)) {
218 	fprintf(stderr, "a keyword AND a mountpt must be specified "
219 			"when adding a journal\n");
220 	usage();
221     }
222     if (fopt == 0 && mopt + dopt && keyword == NULL && mountpt == NULL) {
223 	fprintf(stderr, "a keyword, a mountpt, or both must be specified "
224 			"when modifying or deleting a journal, unless "
225 			"-f is also specified for safety\n");
226 	usage();
227     }
228 
229     /*
230      * Open the journaling file descriptor if required.
231      */
232     if (wopt && xopt) {
233 	fprintf(stderr, "you must specify only one of -w/-x/path/fd\n");
234 	exit(1);
235     } else if (wopt) {
236 	if ((fd = open(wopt, O_RDWR|O_CREAT|O_APPEND, 0666)) < 0) {
237 	    fprintf(stderr, "unable to create %s: %s\n", wopt, strerror(errno));
238 	    exit(1);
239 	}
240     } else if (xopt) {
241 	fd = strtol(xopt, NULL, 0);
242     } else if (aopt) {
243 	fd = 1;		/* stdout default for -a */
244     } else {
245 	fd = -1;
246     }
247 
248     /*
249      * And finally execute the core command.
250      */
251     if (lopt)
252 	mountctl_scan(mountctl_list, keyword, mountpt, fd);
253     if (aopt)
254 	mountctl_add(keyword, mountpt, fd);
255     if (dopt) {
256 	ch = mountctl_scan(mountctl_delete, keyword, mountpt, -1);
257 	if (ch)
258 	    printf("%d journals deleted\n", ch);
259 	else
260 	    printf("Unable to locate any matching journals\n");
261     }
262     if (mopt) {
263 	ch = mountctl_scan(mountctl_modify, keyword, mountpt, fd);
264 	if (ch)
265 	    printf("%d journals modified\n", ch);
266 	else
267 	    printf("Unable to locate any matching journals\n");
268     }
269 
270     return(exitCode);
271 }
272 
273 static void
274 parse_option_keyword(const char *opt, const char **wopt, const char **xopt)
275 {
276     char *str = strdup(opt);
277     char *name;
278     char *val;
279     int negate;
280     int hasval;
281     int cannotnegate;
282 
283     /*
284      * multiple comma delimited options may be specified.
285      */
286     while ((name = strsep(&str, ",")) != NULL) {
287 	/*
288 	 * some options have associated data.
289 	 */
290 	if ((val = strchr(name, '=')) != NULL)
291 	    *val++ = 0;
292 
293 	/*
294 	 * options beginning with 'no' or 'non' are negated.  A positive
295 	 * number means not negated, a negative number means negated.
296 	 */
297 	negate = 1;
298 	cannotnegate = 0;
299 	hasval = 0;
300 	if (strncmp(name, "non", 3) == 0) {
301 	    name += 3;
302 	    negate = -1;
303 	} else if (strncmp(name, "no", 2) == 0) {
304 	    name += 2;
305 	    negate = -1;
306 	}
307 
308 	/*
309 	 * Parse supported options
310 	 */
311 	if (strcmp(name, "reversable") == 0) {
312 	    reversable_opt = negate;
313 	} else if (strcmp(name, "twoway") == 0) {
314 	    twoway_opt = negate;
315 	} else if (strcmp(name, "memfifo") == 0) {
316 	    cannotnegate = 1;
317 	    hasval = 1;
318 	    if (val) {
319 		if ((memfifo_opt = getsize(val)) == 0)
320 		    memfifo_opt = -1;
321 	    }
322 	} else if (strcmp(name, "swapfifo") == 0) {
323 	    if (val) {
324 		hasval = 1;
325 		if ((swapfifo_opt = getsize(val)) == 0)
326 		    swapfifo_opt = -1;
327 	    } else if (negate < 0) {
328 		swapfifo_opt = -1;
329 	    } else {
330 		hasval = 1;	/* force error */
331 	    }
332 	} else if (strcmp(name, "fd") == 0) {
333 	    cannotnegate = 1;
334 	    hasval = 1;
335 	    if (val)
336 		*xopt = val;
337 	} else if (strcmp(name, "path") == 0) {
338 	    cannotnegate = 1;
339 	    hasval = 1;
340 	    if (val)
341 		*wopt = val;
342 	} else if (strcmp(name, "freeze") == 0 || strcmp(name, "stop") == 0) {
343 	    if (negate < 0)
344 		start_opt = -negate;
345 	    else
346 		freeze_opt = negate;
347 	} else if (strcmp(name, "start") == 0) {
348 	    if (negate < 0)
349 		freeze_opt = -negate;
350 	    else
351 		start_opt = negate;
352 	} else if (strcmp(name, "close") == 0) {
353 	    close_opt = negate;
354 	} else if (strcmp(name, "abort") == 0) {
355 	    abort_opt = negate;
356 	} else if (strcmp(name, "flush") == 0) {
357 	    flush_opt = negate;
358 	} else {
359 	    fprintf(stderr, "unknown option keyword: %s\n", name);
360 	    exit(1);
361 	}
362 
363 	/*
364 	 * Sanity checks
365 	 */
366 	if (cannotnegate && negate < 0) {
367 	    fprintf(stderr, "option %s may not be negated\n", name);
368 	    exit(1);
369 	}
370 	if (hasval && val == NULL) {
371 	    fprintf(stderr, "option %s requires assigned data\n", name);
372 	    exit(1);
373 	}
374 	if (hasval == 0 && val) {
375 	    fprintf(stderr, "option %s does not take an assignment\n", name);
376 	    exit(1);
377 	}
378 
379     }
380 }
381 
382 static int
383 mountctl_scan(void (*func)(const char *, const char *, int, void *),
384 	    const char *keyword, const char *mountpt, int fd)
385 {
386     struct statfs *sfs;
387     int count;
388     int calls;
389     int i;
390     struct mountctl_status_journal statreq;
391     struct mountctl_journal_ret_status rstat[4];	/* BIG */
392 
393     calls = 0;
394     if (mountpt) {
395 	bzero(&statreq, sizeof(statreq));
396 	if (keyword) {
397 	    statreq.index = MC_JOURNAL_INDEX_ID;
398 	    count = strlen(keyword);
399 	    if (count > JIDMAX)
400 		count = JIDMAX;
401 	    bcopy(keyword, statreq.id, count);
402 	} else {
403 	    statreq.index = MC_JOURNAL_INDEX_ALL;
404 	}
405 	count = mountctl(mountpt, MOUNTCTL_STATUS_VFS_JOURNAL, -1,
406 			&statreq, sizeof(statreq), &rstat, sizeof(rstat));
407 	if (count > 0 && rstat[0].recsize != sizeof(rstat[0])) {
408 	    fprintf(stderr, "Unable to access status, "
409 			    "structure size mismatch\n");
410 	    exit(1);
411 	}
412 	if (count > 0) {
413 	    count /= sizeof(rstat[0]);
414 	    for (i = 0; i < count; ++i) {
415 		func(rstat[i].id, mountpt, fd, &rstat[i]);
416 		++calls;
417 	    }
418 	}
419     } else {
420 	if ((count = getmntinfo(&sfs, MNT_WAIT)) > 0) {
421 	    for (i = 0; i < count; ++i) {
422 		calls += mountctl_scan(func, keyword, sfs[i].f_mntonname, fd);
423 	    }
424 	} else if (count < 0) {
425 	    /* XXX */
426 	}
427     }
428     return(calls);
429 }
430 
431 static void
432 mountctl_list(const char *keyword, const char *mountpt, int __unused fd, void *info)
433 {
434     struct mountctl_journal_ret_status *rstat = info;
435 
436     printf("%s:%s\n", mountpt, rstat->id[0] ? rstat->id : "<NOID>");
437     printf("    membufsize=%s\n", numtostr(rstat->membufsize));
438     printf("    membufused=%s\n", numtostr(rstat->membufused));
439     printf("    membufunacked=%s\n", numtostr(rstat->membufunacked));
440     printf("    total_bytes=%s\n", numtostr(rstat->bytessent));
441     printf("    fifo_stalls=%lld\n", rstat->fifostalls);
442 }
443 
444 static void
445 mountctl_add(const char *keyword, const char *mountpt, int fd)
446 {
447     struct mountctl_install_journal joinfo;
448     struct stat st1;
449     struct stat st2;
450     int error;
451 
452     /*
453      * Make sure the file descriptor is not on the same filesystem as the
454      * mount point.  This isn't a perfect test, but it should catch most
455      * foot shooting.
456      */
457     if (fstat(fd, &st1) == 0 && S_ISREG(st1.st_mode) &&
458 	stat(mountpt, &st2) == 0 && st1.st_dev == st2.st_dev
459     ) {
460 	fprintf(stderr, "%s:%s failed to add, the journal cannot be on the "
461 			"same filesystem being journaled!\n",
462 			mountpt, keyword);
463 	exitCode = 1;
464 	return;
465     }
466 
467     /*
468      * Setup joinfo and issue the add
469      */
470     bzero(&joinfo, sizeof(joinfo));
471     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
472     if (memfifo_opt > 0)
473 	joinfo.membufsize = memfifo_opt;
474     if (twoway_opt > 0)
475 	joinfo.flags |= MC_JOURNAL_WANT_FULLDUPLEX;
476     if (reversable_opt > 0)
477 	joinfo.flags |= MC_JOURNAL_WANT_REVERSABLE;
478 
479     error = mountctl(mountpt, MOUNTCTL_INSTALL_VFS_JOURNAL, fd,
480 			&joinfo, sizeof(joinfo), NULL, 0);
481     if (error == 0) {
482 	fprintf(stderr, "%s:%s added\n", mountpt, joinfo.id);
483     } else {
484 	fprintf(stderr, "%s:%s failed to add, error %s\n", mountpt, joinfo.id, strerror(errno));
485 	exitCode = 1;
486     }
487 }
488 
489 static void
490 mountctl_delete(const char *keyword, const char *mountpt, int __unused fd, void __unused *info)
491 {
492     struct mountctl_remove_journal joinfo;
493     int error;
494 
495     bzero(&joinfo, sizeof(joinfo));
496     snprintf(joinfo.id, sizeof(joinfo.id), "%s", keyword);
497     error = mountctl(mountpt, MOUNTCTL_REMOVE_VFS_JOURNAL, -1,
498 			&joinfo, sizeof(joinfo), NULL, 0);
499     if (error == 0) {
500 	fprintf(stderr, "%s:%s deleted\n", mountpt, joinfo.id);
501     } else {
502 	fprintf(stderr, "%s:%s deletion failed, error %s\n", mountpt, joinfo.id, strerror(errno));
503     }
504 }
505 
506 static void
507 mountctl_modify(const char *keyword, const char *mountpt, int fd, void __unused *info)
508 {
509     fprintf(stderr, "modify not yet implemented\n");
510 }
511 
512 
513 static volatile
514 void
515 usage(void)
516 {
517     printf(
518 	" mountctl -l [tag/mountpt | mountpt:tag]\n"
519 	" mountctl -a [-w output_path] [-x filedesc]\n"
520 	"             [-o option] [-o option ...] mountpt:tag\n"
521 	" mountctl -d [tag/mountpt | mountpt:tag]\n"
522 	" mountctl -m [-o option] [-o option ...] [tag/mountpt | mountpt:tag]\n"
523 	" mountctl -FZSCA [tag/mountpt | mountpt:tag]\n"
524     );
525     exit(1);
526 }
527 
528 static
529 int64_t
530 getsize(const char *str)
531 {
532     const char *suffix;
533     int64_t val;
534 
535     val = strtoll(str, &suffix, 0);
536     if (suffix) {
537 	switch(*suffix) {
538 	case 'b':
539 	    break;
540 	case 't':
541 	    val *= 1024;
542 	    /* fall through */
543 	case 'g':
544 	    val *= 1024;
545 	    /* fall through */
546 	case 'm':
547 	    val *= 1024;
548 	    /* fall through */
549 	case 'k':
550 	    val *= 1024;
551 	    /* fall through */
552 	    break;
553 	default:
554 	    fprintf(stderr, "data value '%s' has unknown suffix\n", str);
555 	    exit(1);
556 	}
557     }
558     return(val);
559 }
560 
561 static
562 const char *
563 numtostr(int64_t num)
564 {
565     static char buf[64];
566     int n;
567     double v = num;
568 
569     if (num < 1024)
570 	snprintf(buf, sizeof(buf), "%lld", num);
571     else if (num < 10 * 1024)
572 	snprintf(buf, sizeof(buf), "%3.2fK", num / 1024.0);
573     else if (num < 1024 * 1024)
574 	snprintf(buf, sizeof(buf), "%3.0fK", num / 1024.0);
575     else if (num < 10 * 1024 * 1024)
576 	snprintf(buf, sizeof(buf), "%3.2fM", num / (1024.0 * 1024.0));
577     else if (num < 1024 * 1024 * 1024)
578 	snprintf(buf, sizeof(buf), "%3.0fM", num / (1024.0 * 1024.0));
579     else if (num < 10LL * 1024 * 1024 * 1024)
580 	snprintf(buf, sizeof(buf), "%3.2fG", num / (1024.0 * 1024.0 * 1024.0));
581     else
582 	snprintf(buf, sizeof(buf), "%3.0fG", num / (1024.0 * 1024.0 * 1024.0));
583     return(buf);
584 }
585 
586