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