xref: /netbsd-src/usr.sbin/quotarestore/quotarestore.c (revision d16b7486a53dcb8072b60ec6fcb4373a2d0c27b7)
1 /*	$NetBSD: quotarestore.c,v 1.3 2012/09/13 21:44:50 joerg Exp $	*/
2 /*-
3  * Copyright (c) 2012 The NetBSD Foundation, Inc.
4  * All rights reserved.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by David A. Holland.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: quotarestore.c,v 1.3 2012/09/13 21:44:50 joerg Exp $");
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <assert.h>
38 #include <getopt.h>
39 #include <limits.h>
40 #include <errno.h>
41 #include <err.h>
42 
43 #include <quota.h>
44 
45 static const char ws[] = " \t\r\n";
46 
47 static char **idtypenames;
48 static unsigned numidtypes;
49 
50 static char **objtypenames;
51 static unsigned numobjtypes;
52 
53 ////////////////////////////////////////////////////////////
54 // table of quota keys
55 
56 struct qklist {
57 	struct quotakey *keys;
58 	unsigned num, max;
59 };
60 
61 static struct qklist *
62 qklist_create(void)
63 {
64 	struct qklist *l;
65 
66 	l = malloc(sizeof(*l));
67 	if (l == NULL) {
68 		err(EXIT_FAILURE, "malloc");
69 	}
70 	l->keys = 0;
71 	l->num = 0;
72 	l->max = 0;
73 	return l;
74 }
75 
76 static void
77 qklist_destroy(struct qklist *l)
78 {
79 	free(l->keys);
80 	free(l);
81 }
82 
83 static void
84 qklist_truncate(struct qklist *l)
85 {
86 	l->num = 0;
87 }
88 
89 static void
90 qklist_add(struct qklist *l, const struct quotakey *qk)
91 {
92 	assert(l->num <= l->max);
93 	if (l->num == l->max) {
94 		l->max = l->max ? l->max * 2 : 4;
95 		l->keys = realloc(l->keys, l->max * sizeof(l->keys[0]));
96 		if (l->keys == NULL) {
97 			err(EXIT_FAILURE, "realloc");
98 		}
99 	}
100 	l->keys[l->num++] = *qk;
101 }
102 
103 static int
104 qk_compare(const void *av, const void *bv)
105 {
106 	const struct quotakey *a = av;
107 	const struct quotakey *b = bv;
108 
109 	if (a->qk_idtype < b->qk_idtype) {
110 		return -1;
111 	}
112 	if (a->qk_idtype > b->qk_idtype) {
113 		return 1;
114 	}
115 
116 	if (a->qk_id < b->qk_id) {
117 		return -1;
118 	}
119 	if (a->qk_id > b->qk_id) {
120 		return 1;
121 	}
122 
123 	if (a->qk_objtype < b->qk_objtype) {
124 		return -1;
125 	}
126 	if (a->qk_objtype > b->qk_objtype) {
127 		return 1;
128 	}
129 
130 	return 0;
131 }
132 
133 static void
134 qklist_sort(struct qklist *l)
135 {
136 	qsort(l->keys, l->num, sizeof(l->keys[0]), qk_compare);
137 }
138 
139 static int
140 qklist_present(struct qklist *l, const struct quotakey *key)
141 {
142 	void *p;
143 
144 	p = bsearch(key, l->keys, l->num, sizeof(l->keys[0]), qk_compare);
145 	return p != NULL;
146 }
147 
148 ////////////////////////////////////////////////////////////
149 // name tables and string conversion
150 
151 static void
152 maketables(struct quotahandle *qh)
153 {
154 	unsigned i;
155 
156 	numidtypes = quota_getnumidtypes(qh);
157 	idtypenames = malloc(numidtypes * sizeof(idtypenames[0]));
158 	if (idtypenames == NULL) {
159 		err(EXIT_FAILURE, "malloc");
160 	}
161 
162 	for (i=0; i<numidtypes; i++) {
163 		idtypenames[i] = strdup(quota_idtype_getname(qh, i));
164 		if (idtypenames[i] == NULL) {
165 			err(EXIT_FAILURE, "strdup");
166 		}
167 	}
168 
169 	numobjtypes = quota_getnumobjtypes(qh);
170 	objtypenames = malloc(numobjtypes * sizeof(objtypenames[0]));
171 	if (objtypenames == NULL) {
172 		err(EXIT_FAILURE, "malloc");
173 	}
174 
175 	for (i=0; i<numobjtypes; i++) {
176 		objtypenames[i] = strdup(quota_objtype_getname(qh, i));
177 		if (objtypenames[i] == NULL) {
178 			err(EXIT_FAILURE, "strdup");
179 		}
180 	}
181 }
182 
183 static int
184 getidtype(const char *name, int *ret)
185 {
186 	unsigned i;
187 
188 	for (i=0; i<numidtypes; i++) {
189 		if (!strcmp(name, idtypenames[i])) {
190 			*ret = i;
191 			return 0;
192 		}
193 	}
194 	return -1;
195 }
196 
197 static int
198 getid(const char *name, int idtype, id_t *ret)
199 {
200 	unsigned long val;
201 	char *s;
202 
203 	if (!strcmp(name, "default")) {
204 		*ret = QUOTA_DEFAULTID;
205 		return 0;
206 	}
207 	errno = 0;
208 	val = strtoul(name, &s, 10);
209 	if (errno || *s != 0) {
210 		return -1;
211 	}
212 	if (idtype == QUOTA_IDTYPE_USER && val > UID_MAX) {
213 		return -1;
214 	}
215 	if (idtype == QUOTA_IDTYPE_GROUP && val > GID_MAX) {
216 		return -1;
217 	}
218 	*ret = val;
219 	return 0;
220 }
221 
222 static int
223 getobjtype(const char *name, int *ret)
224 {
225 	unsigned i;
226 	size_t len;
227 
228 	for (i=0; i<numobjtypes; i++) {
229 		if (!strcmp(name, objtypenames[i])) {
230 			*ret = i;
231 			return 0;
232 		}
233 	}
234 
235 	/*
236 	 * Sigh. Some early committed versions of quotadump used
237 	 * "blocks" and "files" instead of "block" and "file".
238 	 */
239 	len = strlen(name);
240 	if (len == 0) {
241 		return -1;
242 	}
243 	for (i=0; i<numobjtypes; i++) {
244 		if (name[len-1] == 's' &&
245 		    !strncmp(name, objtypenames[i], len-1)) {
246 			*ret = i;
247 			return 0;
248 		}
249 	}
250 	return -1;
251 }
252 
253 static int
254 getlimit(const char *name, uint64_t *ret)
255 {
256 	unsigned long long val;
257 	char *s;
258 
259 	if (!strcmp(name, "-")) {
260 		*ret = QUOTA_NOLIMIT;
261 		return 0;
262 	}
263 	errno = 0;
264 	val = strtoull(name, &s, 10);
265 	if (errno || *s != 0) {
266 		return -1;
267 	}
268 	*ret = val;
269 	return 0;
270 }
271 
272 static int
273 gettime(const char *name, int64_t *ret)
274 {
275 	long long val;
276 	char *s;
277 
278 	if (!strcmp(name, "-")) {
279 		*ret = QUOTA_NOTIME;
280 		return 0;
281 	}
282 	errno = 0;
283 	val = strtoll(name, &s, 10);
284 	if (errno || *s != 0 || val < 0) {
285 		return -1;
286 	}
287 	*ret = val;
288 	return 0;
289 }
290 
291 ////////////////////////////////////////////////////////////
292 // parsing tools
293 
294 static int
295 isws(int ch)
296 {
297 	return ch != '\0' && strchr(ws, ch) != NULL;
298 }
299 
300 static char *
301 skipws(char *s)
302 {
303 	while (isws(*s)) {
304 		s++;
305 	}
306 	return s;
307 }
308 
309 ////////////////////////////////////////////////////////////
310 // deletion of extra records
311 
312 static void
313 scankeys(struct quotahandle *qh, struct qklist *seenkeys,
314 	struct qklist *dropkeys)
315 {
316 	struct quotacursor *qc;
317 #define MAX 8
318 	struct quotakey keys[MAX];
319 	struct quotaval vals[MAX];
320 	int num, i;
321 
322 	qc = quota_opencursor(qh);
323 	if (qc == NULL) {
324 		err(EXIT_FAILURE, "quota_opencursor");
325 	}
326 
327 	while (quotacursor_atend(qc) == 0) {
328 		num = quotacursor_getn(qc, keys, vals, MAX);
329 		if (num < 0) {
330 			if (errno == EDEADLK) {
331 				quotacursor_rewind(qc);
332 				qklist_truncate(dropkeys);
333 				continue;
334 			}
335 			err(EXIT_FAILURE, "quotacursor_getn");
336 		}
337 		for (i=0; i<num; i++) {
338 			if (qklist_present(seenkeys, &keys[i]) == 0) {
339 				qklist_add(dropkeys, &keys[i]);
340 			}
341 		}
342 	}
343 
344 	quotacursor_close(qc);
345 }
346 
347 static void
348 purge(struct quotahandle *qh, struct qklist *dropkeys)
349 {
350 	unsigned i;
351 
352 	for (i=0; i<dropkeys->num; i++) {
353 		if (quota_delete(qh, &dropkeys->keys[i])) {
354 			err(EXIT_FAILURE, "quota_delete");
355 		}
356 	}
357 }
358 
359 ////////////////////////////////////////////////////////////
360 // dumpfile reader
361 
362 static void
363 readdumpfile(struct quotahandle *qh, FILE *f, const char *path,
364 	     struct qklist *seenkeys)
365 {
366 	char buf[128];
367 	unsigned lineno;
368 	unsigned long version;
369 	char *s;
370 	char *fields[8];
371 	unsigned num;
372 	char *x;
373 	struct quotakey key;
374 	struct quotaval val;
375 	int ch;
376 
377 	lineno = 0;
378 	if (fgets(buf, sizeof(buf), f) == NULL) {
379 		errx(EXIT_FAILURE, "%s: EOF before quotadump header", path);
380 	}
381 	lineno++;
382 	if (strncmp(buf, "@format netbsd-quota-dump v", 27) != 0) {
383 		errx(EXIT_FAILURE, "%s: Missing quotadump header", path);
384 	}
385 	s = buf+27;
386 	errno = 0;
387 	version = strtoul(s, &s, 10);
388 	if (errno) {
389 		errx(EXIT_FAILURE, "%s: Corrupted quotadump header", path);
390 	}
391 	s = skipws(s);
392 	if (*s != '\0') {
393 		errx(EXIT_FAILURE, "%s: Trash after quotadump header", path);
394 	}
395 
396 	switch (version) {
397 	    case 1: break;
398 	    default:
399 		errx(EXIT_FAILURE, "%s: Unsupported quotadump version %lu",
400 		     path, version);
401 	}
402 
403 	while (fgets(buf, sizeof(buf), f)) {
404 		lineno++;
405 		if (buf[0] == '#') {
406 			continue;
407 		}
408 		if (!strncmp(buf, "@end", 4)) {
409 			s = skipws(buf+4);
410 			if (*s != '\0') {
411 				errx(EXIT_FAILURE, "%s:%u: Invalid @end tag",
412 				     path, lineno);
413 			}
414 			break;
415 		}
416 
417 		num = 0;
418 		for (s = strtok_r(buf, ws, &x);
419 		     s != NULL;
420 		     s = strtok_r(NULL, ws, &x)) {
421 			if (num < 8) {
422 				fields[num++] = s;
423 			} else {
424 				errx(EXIT_FAILURE, "%s:%u: Too many fields",
425 				     path, lineno);
426 			}
427 		}
428 		if (num < 8) {
429 			errx(EXIT_FAILURE, "%s:%u: Not enough fields",
430 			     path, lineno);
431 		}
432 
433 		if (getidtype(fields[0], &key.qk_idtype)) {
434 			errx(EXIT_FAILURE, "%s:%u: Invalid/unknown ID type %s",
435 			     path, lineno, fields[0]);
436 		}
437 		if (getid(fields[1], key.qk_idtype, &key.qk_id)) {
438 			errx(EXIT_FAILURE, "%s:%u: Invalid ID number %s",
439 			     path, lineno, fields[1]);
440 		}
441 		if (getobjtype(fields[2], &key.qk_objtype)) {
442 			errx(EXIT_FAILURE, "%s:%u: Invalid/unknown object "
443 			     "type %s",
444 			     path, lineno, fields[2]);
445 		}
446 
447 		if (getlimit(fields[3], &val.qv_hardlimit)) {
448 			errx(EXIT_FAILURE, "%s:%u: Invalid hard limit %s",
449 			     path, lineno, fields[3]);
450 		}
451 		if (getlimit(fields[4], &val.qv_softlimit)) {
452 			errx(EXIT_FAILURE, "%s:%u: Invalid soft limit %s",
453 			     path, lineno, fields[4]);
454 		}
455 		if (getlimit(fields[5], &val.qv_usage)) {
456 			/*
457 			 * Make this nonfatal as it'll be ignored by
458 			 * quota_put() anyway.
459 			 */
460 			warnx("%s:%u: Invalid current usage %s",
461 			     path, lineno, fields[5]);
462 			val.qv_usage = 0;
463 		}
464 		if (gettime(fields[6], &val.qv_expiretime)) {
465 			errx(EXIT_FAILURE, "%s:%u: Invalid expire time %s",
466 			     path, lineno, fields[6]);
467 		}
468 		if (gettime(fields[7], &val.qv_grace)) {
469 			errx(EXIT_FAILURE, "%s:%u: Invalid grace period %s",
470 			     path, lineno, fields[7]);
471 		}
472 
473 		if (quota_put(qh, &key, &val)) {
474 			err(EXIT_FAILURE, "%s:%u: quota_put", path, lineno);
475 		}
476 
477 		if (seenkeys != NULL) {
478 			qklist_add(seenkeys, &key);
479 		}
480 	}
481 	if (feof(f)) {
482 		return;
483 	}
484 	if (ferror(f)) {
485 		errx(EXIT_FAILURE, "%s: Read error", path);
486 	}
487 	/* not at EOF, not an error... what's left? */
488 	while (1) {
489 		ch = fgetc(f);
490 		if (ch == EOF)
491 			break;
492 		if (isws(ch)) {
493 			continue;
494 		}
495 		warnx("%s:%u: Trash after @end tag", path, lineno);
496 	}
497 }
498 
499 ////////////////////////////////////////////////////////////
500 // top level control logic
501 
502 __dead static void
503 usage(void)
504 {
505 	fprintf(stderr, "usage: %s [-d] volume [dump-file]\n",
506 		getprogname());
507 	exit(EXIT_FAILURE);
508 }
509 
510 int
511 main(int argc, char *argv[])
512 {
513 	int ch;
514 	FILE *f;
515 	struct quotahandle *qh;
516 
517 	int dflag = 0;
518 	const char *volume = NULL;
519 	const char *dumpfile = NULL;
520 
521 	while ((ch = getopt(argc, argv, "d")) != -1) {
522 		switch (ch) {
523 		    case 'd': dflag = 1; break;
524 		    default: usage(); break;
525 		}
526 	}
527 
528 	if (optind >= argc) {
529 		usage();
530 	}
531 	volume = argv[optind++];
532 	if (optind < argc) {
533 		dumpfile = argv[optind++];
534 	}
535 	if (optind < argc) {
536 		usage();
537 	}
538 
539 	qh = quota_open(volume);
540 	if (qh == NULL) {
541 		err(EXIT_FAILURE, "quota_open: %s", volume);
542 	}
543 	if (dumpfile != NULL) {
544 		f = fopen(dumpfile, "r");
545 		if (f == NULL) {
546 			err(EXIT_FAILURE, "%s", dumpfile);
547 		}
548 	} else {
549 		f = stdin;
550 		dumpfile = "<stdin>";
551 	}
552 
553 	maketables(qh);
554 
555 	if (dflag) {
556 		struct qklist *seenkeys, *dropkeys;
557 
558 		seenkeys = qklist_create();
559 		dropkeys = qklist_create();
560 
561 		readdumpfile(qh, f, dumpfile, seenkeys);
562 		qklist_sort(seenkeys);
563 		scankeys(qh, seenkeys, dropkeys);
564 		purge(qh, dropkeys);
565 
566 		qklist_destroy(dropkeys);
567 		qklist_destroy(seenkeys);
568 	} else {
569 		readdumpfile(qh, f, dumpfile, NULL);
570 	}
571 
572 	if (f != stdin) {
573 		fclose(f);
574 	}
575 	quota_close(qh);
576 	return EXIT_SUCCESS;
577 }
578