578 lines
12 KiB
C
578 lines
12 KiB
C
/* $NetBSD: quotarestore.c,v 1.3 2012/09/13 21:44:50 joerg Exp $ */
|
|
/*-
|
|
* Copyright (c) 2012 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by David A. Holland.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__RCSID("$NetBSD: quotarestore.c,v 1.3 2012/09/13 21:44:50 joerg Exp $");
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <getopt.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
#include <err.h>
|
|
|
|
#include <quota.h>
|
|
|
|
static const char ws[] = " \t\r\n";
|
|
|
|
static char **idtypenames;
|
|
static unsigned numidtypes;
|
|
|
|
static char **objtypenames;
|
|
static unsigned numobjtypes;
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// table of quota keys
|
|
|
|
struct qklist {
|
|
struct quotakey *keys;
|
|
unsigned num, max;
|
|
};
|
|
|
|
static struct qklist *
|
|
qklist_create(void)
|
|
{
|
|
struct qklist *l;
|
|
|
|
l = malloc(sizeof(*l));
|
|
if (l == NULL) {
|
|
err(EXIT_FAILURE, "malloc");
|
|
}
|
|
l->keys = 0;
|
|
l->num = 0;
|
|
l->max = 0;
|
|
return l;
|
|
}
|
|
|
|
static void
|
|
qklist_destroy(struct qklist *l)
|
|
{
|
|
free(l->keys);
|
|
free(l);
|
|
}
|
|
|
|
static void
|
|
qklist_truncate(struct qklist *l)
|
|
{
|
|
l->num = 0;
|
|
}
|
|
|
|
static void
|
|
qklist_add(struct qklist *l, const struct quotakey *qk)
|
|
{
|
|
assert(l->num <= l->max);
|
|
if (l->num == l->max) {
|
|
l->max = l->max ? l->max * 2 : 4;
|
|
l->keys = realloc(l->keys, l->max * sizeof(l->keys[0]));
|
|
if (l->keys == NULL) {
|
|
err(EXIT_FAILURE, "realloc");
|
|
}
|
|
}
|
|
l->keys[l->num++] = *qk;
|
|
}
|
|
|
|
static int
|
|
qk_compare(const void *av, const void *bv)
|
|
{
|
|
const struct quotakey *a = av;
|
|
const struct quotakey *b = bv;
|
|
|
|
if (a->qk_idtype < b->qk_idtype) {
|
|
return -1;
|
|
}
|
|
if (a->qk_idtype > b->qk_idtype) {
|
|
return 1;
|
|
}
|
|
|
|
if (a->qk_id < b->qk_id) {
|
|
return -1;
|
|
}
|
|
if (a->qk_id > b->qk_id) {
|
|
return 1;
|
|
}
|
|
|
|
if (a->qk_objtype < b->qk_objtype) {
|
|
return -1;
|
|
}
|
|
if (a->qk_objtype > b->qk_objtype) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
qklist_sort(struct qklist *l)
|
|
{
|
|
qsort(l->keys, l->num, sizeof(l->keys[0]), qk_compare);
|
|
}
|
|
|
|
static int
|
|
qklist_present(struct qklist *l, const struct quotakey *key)
|
|
{
|
|
void *p;
|
|
|
|
p = bsearch(key, l->keys, l->num, sizeof(l->keys[0]), qk_compare);
|
|
return p != NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// name tables and string conversion
|
|
|
|
static void
|
|
maketables(struct quotahandle *qh)
|
|
{
|
|
unsigned i;
|
|
|
|
numidtypes = quota_getnumidtypes(qh);
|
|
idtypenames = malloc(numidtypes * sizeof(idtypenames[0]));
|
|
if (idtypenames == NULL) {
|
|
err(EXIT_FAILURE, "malloc");
|
|
}
|
|
|
|
for (i=0; i<numidtypes; i++) {
|
|
idtypenames[i] = strdup(quota_idtype_getname(qh, i));
|
|
if (idtypenames[i] == NULL) {
|
|
err(EXIT_FAILURE, "strdup");
|
|
}
|
|
}
|
|
|
|
numobjtypes = quota_getnumobjtypes(qh);
|
|
objtypenames = malloc(numobjtypes * sizeof(objtypenames[0]));
|
|
if (objtypenames == NULL) {
|
|
err(EXIT_FAILURE, "malloc");
|
|
}
|
|
|
|
for (i=0; i<numobjtypes; i++) {
|
|
objtypenames[i] = strdup(quota_objtype_getname(qh, i));
|
|
if (objtypenames[i] == NULL) {
|
|
err(EXIT_FAILURE, "strdup");
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
getidtype(const char *name, int *ret)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i=0; i<numidtypes; i++) {
|
|
if (!strcmp(name, idtypenames[i])) {
|
|
*ret = i;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
getid(const char *name, int idtype, id_t *ret)
|
|
{
|
|
unsigned long val;
|
|
char *s;
|
|
|
|
if (!strcmp(name, "default")) {
|
|
*ret = QUOTA_DEFAULTID;
|
|
return 0;
|
|
}
|
|
errno = 0;
|
|
val = strtoul(name, &s, 10);
|
|
if (errno || *s != 0) {
|
|
return -1;
|
|
}
|
|
if (idtype == QUOTA_IDTYPE_USER && val > UID_MAX) {
|
|
return -1;
|
|
}
|
|
if (idtype == QUOTA_IDTYPE_GROUP && val > GID_MAX) {
|
|
return -1;
|
|
}
|
|
*ret = val;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
getobjtype(const char *name, int *ret)
|
|
{
|
|
unsigned i;
|
|
size_t len;
|
|
|
|
for (i=0; i<numobjtypes; i++) {
|
|
if (!strcmp(name, objtypenames[i])) {
|
|
*ret = i;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Sigh. Some early committed versions of quotadump used
|
|
* "blocks" and "files" instead of "block" and "file".
|
|
*/
|
|
len = strlen(name);
|
|
if (len == 0) {
|
|
return -1;
|
|
}
|
|
for (i=0; i<numobjtypes; i++) {
|
|
if (name[len-1] == 's' &&
|
|
!strncmp(name, objtypenames[i], len-1)) {
|
|
*ret = i;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
getlimit(const char *name, uint64_t *ret)
|
|
{
|
|
unsigned long long val;
|
|
char *s;
|
|
|
|
if (!strcmp(name, "-")) {
|
|
*ret = QUOTA_NOLIMIT;
|
|
return 0;
|
|
}
|
|
errno = 0;
|
|
val = strtoull(name, &s, 10);
|
|
if (errno || *s != 0) {
|
|
return -1;
|
|
}
|
|
*ret = val;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
gettime(const char *name, int64_t *ret)
|
|
{
|
|
long long val;
|
|
char *s;
|
|
|
|
if (!strcmp(name, "-")) {
|
|
*ret = QUOTA_NOTIME;
|
|
return 0;
|
|
}
|
|
errno = 0;
|
|
val = strtoll(name, &s, 10);
|
|
if (errno || *s != 0 || val < 0) {
|
|
return -1;
|
|
}
|
|
*ret = val;
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// parsing tools
|
|
|
|
static int
|
|
isws(int ch)
|
|
{
|
|
return ch != '\0' && strchr(ws, ch) != NULL;
|
|
}
|
|
|
|
static char *
|
|
skipws(char *s)
|
|
{
|
|
while (isws(*s)) {
|
|
s++;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// deletion of extra records
|
|
|
|
static void
|
|
scankeys(struct quotahandle *qh, struct qklist *seenkeys,
|
|
struct qklist *dropkeys)
|
|
{
|
|
struct quotacursor *qc;
|
|
#define MAX 8
|
|
struct quotakey keys[MAX];
|
|
struct quotaval vals[MAX];
|
|
int num, i;
|
|
|
|
qc = quota_opencursor(qh);
|
|
if (qc == NULL) {
|
|
err(EXIT_FAILURE, "quota_opencursor");
|
|
}
|
|
|
|
while (quotacursor_atend(qc) == 0) {
|
|
num = quotacursor_getn(qc, keys, vals, MAX);
|
|
if (num < 0) {
|
|
if (errno == EDEADLK) {
|
|
quotacursor_rewind(qc);
|
|
qklist_truncate(dropkeys);
|
|
continue;
|
|
}
|
|
err(EXIT_FAILURE, "quotacursor_getn");
|
|
}
|
|
for (i=0; i<num; i++) {
|
|
if (qklist_present(seenkeys, &keys[i]) == 0) {
|
|
qklist_add(dropkeys, &keys[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
quotacursor_close(qc);
|
|
}
|
|
|
|
static void
|
|
purge(struct quotahandle *qh, struct qklist *dropkeys)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i=0; i<dropkeys->num; i++) {
|
|
if (quota_delete(qh, &dropkeys->keys[i])) {
|
|
err(EXIT_FAILURE, "quota_delete");
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// dumpfile reader
|
|
|
|
static void
|
|
readdumpfile(struct quotahandle *qh, FILE *f, const char *path,
|
|
struct qklist *seenkeys)
|
|
{
|
|
char buf[128];
|
|
unsigned lineno;
|
|
unsigned long version;
|
|
char *s;
|
|
char *fields[8];
|
|
unsigned num;
|
|
char *x;
|
|
struct quotakey key;
|
|
struct quotaval val;
|
|
int ch;
|
|
|
|
lineno = 0;
|
|
if (fgets(buf, sizeof(buf), f) == NULL) {
|
|
errx(EXIT_FAILURE, "%s: EOF before quotadump header", path);
|
|
}
|
|
lineno++;
|
|
if (strncmp(buf, "@format netbsd-quota-dump v", 27) != 0) {
|
|
errx(EXIT_FAILURE, "%s: Missing quotadump header", path);
|
|
}
|
|
s = buf+27;
|
|
errno = 0;
|
|
version = strtoul(s, &s, 10);
|
|
if (errno) {
|
|
errx(EXIT_FAILURE, "%s: Corrupted quotadump header", path);
|
|
}
|
|
s = skipws(s);
|
|
if (*s != '\0') {
|
|
errx(EXIT_FAILURE, "%s: Trash after quotadump header", path);
|
|
}
|
|
|
|
switch (version) {
|
|
case 1: break;
|
|
default:
|
|
errx(EXIT_FAILURE, "%s: Unsupported quotadump version %lu",
|
|
path, version);
|
|
}
|
|
|
|
while (fgets(buf, sizeof(buf), f)) {
|
|
lineno++;
|
|
if (buf[0] == '#') {
|
|
continue;
|
|
}
|
|
if (!strncmp(buf, "@end", 4)) {
|
|
s = skipws(buf+4);
|
|
if (*s != '\0') {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid @end tag",
|
|
path, lineno);
|
|
}
|
|
break;
|
|
}
|
|
|
|
num = 0;
|
|
for (s = strtok_r(buf, ws, &x);
|
|
s != NULL;
|
|
s = strtok_r(NULL, ws, &x)) {
|
|
if (num < 8) {
|
|
fields[num++] = s;
|
|
} else {
|
|
errx(EXIT_FAILURE, "%s:%u: Too many fields",
|
|
path, lineno);
|
|
}
|
|
}
|
|
if (num < 8) {
|
|
errx(EXIT_FAILURE, "%s:%u: Not enough fields",
|
|
path, lineno);
|
|
}
|
|
|
|
if (getidtype(fields[0], &key.qk_idtype)) {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid/unknown ID type %s",
|
|
path, lineno, fields[0]);
|
|
}
|
|
if (getid(fields[1], key.qk_idtype, &key.qk_id)) {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid ID number %s",
|
|
path, lineno, fields[1]);
|
|
}
|
|
if (getobjtype(fields[2], &key.qk_objtype)) {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid/unknown object "
|
|
"type %s",
|
|
path, lineno, fields[2]);
|
|
}
|
|
|
|
if (getlimit(fields[3], &val.qv_hardlimit)) {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid hard limit %s",
|
|
path, lineno, fields[3]);
|
|
}
|
|
if (getlimit(fields[4], &val.qv_softlimit)) {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid soft limit %s",
|
|
path, lineno, fields[4]);
|
|
}
|
|
if (getlimit(fields[5], &val.qv_usage)) {
|
|
/*
|
|
* Make this nonfatal as it'll be ignored by
|
|
* quota_put() anyway.
|
|
*/
|
|
warnx("%s:%u: Invalid current usage %s",
|
|
path, lineno, fields[5]);
|
|
val.qv_usage = 0;
|
|
}
|
|
if (gettime(fields[6], &val.qv_expiretime)) {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid expire time %s",
|
|
path, lineno, fields[6]);
|
|
}
|
|
if (gettime(fields[7], &val.qv_grace)) {
|
|
errx(EXIT_FAILURE, "%s:%u: Invalid grace period %s",
|
|
path, lineno, fields[7]);
|
|
}
|
|
|
|
if (quota_put(qh, &key, &val)) {
|
|
err(EXIT_FAILURE, "%s:%u: quota_put", path, lineno);
|
|
}
|
|
|
|
if (seenkeys != NULL) {
|
|
qklist_add(seenkeys, &key);
|
|
}
|
|
}
|
|
if (feof(f)) {
|
|
return;
|
|
}
|
|
if (ferror(f)) {
|
|
errx(EXIT_FAILURE, "%s: Read error", path);
|
|
}
|
|
/* not at EOF, not an error... what's left? */
|
|
while (1) {
|
|
ch = fgetc(f);
|
|
if (ch == EOF)
|
|
break;
|
|
if (isws(ch)) {
|
|
continue;
|
|
}
|
|
warnx("%s:%u: Trash after @end tag", path, lineno);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
// top level control logic
|
|
|
|
__dead static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "usage: %s [-d] volume [dump-file]\n",
|
|
getprogname());
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int ch;
|
|
FILE *f;
|
|
struct quotahandle *qh;
|
|
|
|
int dflag = 0;
|
|
const char *volume = NULL;
|
|
const char *dumpfile = NULL;
|
|
|
|
while ((ch = getopt(argc, argv, "d")) != -1) {
|
|
switch (ch) {
|
|
case 'd': dflag = 1; break;
|
|
default: usage(); break;
|
|
}
|
|
}
|
|
|
|
if (optind >= argc) {
|
|
usage();
|
|
}
|
|
volume = argv[optind++];
|
|
if (optind < argc) {
|
|
dumpfile = argv[optind++];
|
|
}
|
|
if (optind < argc) {
|
|
usage();
|
|
}
|
|
|
|
qh = quota_open(volume);
|
|
if (qh == NULL) {
|
|
err(EXIT_FAILURE, "quota_open: %s", volume);
|
|
}
|
|
if (dumpfile != NULL) {
|
|
f = fopen(dumpfile, "r");
|
|
if (f == NULL) {
|
|
err(EXIT_FAILURE, "%s", dumpfile);
|
|
}
|
|
} else {
|
|
f = stdin;
|
|
dumpfile = "<stdin>";
|
|
}
|
|
|
|
maketables(qh);
|
|
|
|
if (dflag) {
|
|
struct qklist *seenkeys, *dropkeys;
|
|
|
|
seenkeys = qklist_create();
|
|
dropkeys = qklist_create();
|
|
|
|
readdumpfile(qh, f, dumpfile, seenkeys);
|
|
qklist_sort(seenkeys);
|
|
scankeys(qh, seenkeys, dropkeys);
|
|
purge(qh, dropkeys);
|
|
|
|
qklist_destroy(dropkeys);
|
|
qklist_destroy(seenkeys);
|
|
} else {
|
|
readdumpfile(qh, f, dumpfile, NULL);
|
|
}
|
|
|
|
if (f != stdin) {
|
|
fclose(f);
|
|
}
|
|
quota_close(qh);
|
|
return EXIT_SUCCESS;
|
|
}
|