quotarestore.c revision 1.1 1 /* $NetBSD: quotarestore.c,v 1.1 2012/02/13 01:35:09 dholland 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.1 2012/02/13 01:35:09 dholland 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 const 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 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