#include "hammer.h"
struct softprune {
struct softprune *next;
struct statfs fs;
char *filesystem;
struct hammer_ioc_prune prune;
int maxelms;
int prune_min;
};
static void hammer_softprune_scandir(struct softprune **basep,
struct hammer_ioc_prune *template,
const char *dirname);
static int hammer_softprune_scanmeta(int fd, struct softprune *scan,
int delete_all);
static void hammer_meta_flushdelete(int fd, struct hammer_ioc_snapshot *dsnap);
static struct softprune *hammer_softprune_addentry(struct softprune **basep,
struct hammer_ioc_prune *template,
const char *dirpath, const char *denname,
struct stat *st,
const char *linkbuf, const char *tidptr);
static void hammer_softprune_addelm(struct softprune *scan, hammer_tid_t tid,
time_t ct, time_t mt);
static void hammer_softprune_finalize(struct softprune *scan);
static void softprune_usage(int code);
void
hammer_cmd_softprune(char **av, int ac, int everything_opt)
{
struct hammer_ioc_prune template;
struct hammer_ioc_pseudofs_rw pfs;
struct softprune *base, *scan;
int fd;
int rcode;
base = NULL;
rcode = 0;
if (TimeoutOpt > 0)
alarm(TimeoutOpt);
clrpfs(&pfs, NULL, -1);
bzero(&template, sizeof(template));
template.key_beg.localization = HAMMER_MIN_LOCALIZATION;
template.key_beg.obj_id = HAMMER_MIN_OBJID;
template.key_end.localization = HAMMER_MAX_LOCALIZATION;
template.key_end.obj_id = HAMMER_MAX_OBJID;
hammer_get_cycle(&template.key_end, NULL);
template.stat_oldest_tid = HAMMER_MAX_TID;
if (ac == 0 || ac > 1) {
softprune_usage(1);
}
if (everything_opt) {
const char *dummylink = "";
scan = hammer_softprune_addentry(&base, &template,
*av, NULL, NULL,
dummylink, dummylink);
if (scan == NULL) {
softprune_usage(1);
}
scan->prune.nelms = 0;
scan->prune.head.flags |= HAMMER_IOC_PRUNE_ALL;
} else {
hammer_softprune_scandir(&base, &template, *av);
if (base == NULL) {
const char *dummylink = "";
scan = hammer_softprune_addentry(&base, &template,
*av, NULL, NULL,
dummylink, dummylink);
if (scan == NULL) {
softprune_usage(1);
}
scan->prune.nelms = 0;
}
++av;
--ac;
}
if (base->next) {
errx(1, "Currently only one HAMMER filesystem may "
"be specified in the softlink scan");
}
for (scan = base; scan; scan = scan->next) {
fd = open(scan->filesystem, O_RDONLY);
if (fd < 0) {
warn("Unable to open %s", scan->filesystem);
rcode = 1;
continue;
}
if (ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs) < 0) {
warn("Filesystem %s is not HAMMER", scan->filesystem);
rcode = 1;
close(fd);
continue;
}
scan->prune_min = pfs.ondisk->prune_min;
if (hammer_softprune_scanmeta(fd, scan, everything_opt) < 0) {
warn("Filesystem %s could not scan meta-data snaps",
scan->filesystem);
rcode = 1;
close(fd);
continue;
}
hammer_softprune_finalize(scan);
if (everything_opt) {
printf("Prune %s: EVERYTHING\n",
scan->filesystem);
} else {
printf("Prune %s: %d snapshots\n",
scan->filesystem, scan->prune.nelms);
}
if (scan->prune.nelms == 0 &&
(scan->prune.head.flags & HAMMER_IOC_PRUNE_ALL) == 0) {
fprintf(stderr, "No snapshots found\n");
continue;
}
printf("Prune %s: objspace %016jx:%04x %016jx:%04x "
"pfs_id %d\n",
scan->filesystem,
(uintmax_t)scan->prune.key_beg.obj_id,
scan->prune.key_beg.localization,
(uintmax_t)scan->prune.key_end.obj_id,
scan->prune.key_end.localization,
pfs.pfs_id);
printf("Prune %s: prune_min is %dd/%02d:%02d:%02d\n",
scan->filesystem,
pfs.ondisk->prune_min / (24 * 60 * 60),
pfs.ondisk->prune_min / 60 / 60 % 24,
pfs.ondisk->prune_min / 60 % 60,
pfs.ondisk->prune_min % 60);
RunningIoctl = 1;
if (ioctl(fd, HAMMERIOC_PRUNE, &scan->prune) < 0) {
printf("Prune %s failed: %s\n",
scan->filesystem, strerror(errno));
rcode = 2;
} else if (scan->prune.head.flags & HAMMER_IOC_HEAD_INTR) {
printf("Prune %s interrupted by timer at "
"%016jx %04x\n",
scan->filesystem,
(uintmax_t)scan->prune.key_cur.obj_id,
scan->prune.key_cur.localization);
if (CyclePath)
hammer_set_cycle(&scan->prune.key_cur, 0);
rcode = 0;
} else {
if (CyclePath)
hammer_reset_cycle();
printf("Prune %s succeeded\n", scan->filesystem);
}
printf("Pruned %jd/%jd records (%jd directory entries) "
"and %jd bytes\n",
(uintmax_t)scan->prune.stat_rawrecords,
(uintmax_t)scan->prune.stat_scanrecords,
(uintmax_t)scan->prune.stat_dirrecords,
(uintmax_t)scan->prune.stat_bytes
);
RunningIoctl = 0;
close(fd);
}
if (rcode)
exit(rcode);
}
static
void
hammer_softprune_scandir(struct softprune **basep,
struct hammer_ioc_prune *template,
const char *dirname)
{
struct stat st;
struct dirent *den;
DIR *dir;
char *path;
int len;
char *linkbuf;
char *ptr;
path = NULL;
linkbuf = malloc(MAXPATHLEN);
if ((dir = opendir(dirname)) == NULL) {
err(1, "Cannot open directory %s", dirname);
}
while ((den = readdir(dir)) != NULL) {
if (strcmp(den->d_name, ".") == 0)
continue;
if (strcmp(den->d_name, "..") == 0)
continue;
if (path)
free(path);
asprintf(&path, "%s/%s", dirname, den->d_name);
if (lstat(path, &st) < 0)
continue;
if (!S_ISLNK(st.st_mode))
continue;
if ((len = readlink(path, linkbuf, MAXPATHLEN - 1)) < 0)
continue;
linkbuf[len] = 0;
if ((ptr = strrchr(linkbuf, '@')) &&
ptr > linkbuf && ptr[-1] == '@') {
hammer_softprune_addentry(basep, template,
dirname, den->d_name, &st,
linkbuf, ptr - 1);
}
}
free(linkbuf);
if (path)
free(path);
}
int
hammer_softprune_testdir(const char *dirname)
{
struct softprune *base = NULL;
struct hammer_ioc_prune dummy_template;
bzero(&dummy_template, sizeof(dummy_template));
hammer_softprune_scandir(&base, &dummy_template, dirname);
if (base)
return(1);
return(0);
}
static
int
hammer_softprune_scanmeta(int fd, struct softprune *scan, int delete_all)
{
struct hammer_ioc_version version;
struct hammer_ioc_snapshot snapshot;
struct hammer_ioc_snapshot dsnapshot;
hammer_snapshot_data_t snap;
time_t ct;
bzero(&version, sizeof(version));
if (ioctl(fd, HAMMERIOC_GET_VERSION, &version) < 0)
return(-1);
HammerVersion = version.cur_version;
if (version.cur_version < 3)
return(0);
bzero(&snapshot, sizeof(snapshot));
bzero(&dsnapshot, sizeof(dsnapshot));
for (;;) {
if (ioctl(fd, HAMMERIOC_GET_SNAPSHOT, &snapshot) < 0) {
printf("hammer prune: Unable to access "
"meta-data snaps: %s\n", strerror(errno));
return(-1);
}
while (snapshot.index < snapshot.count) {
snap = &snapshot.snaps[snapshot.index];
if (delete_all) {
dsnapshot.snaps[dsnapshot.count++] = *snap;
if (dsnapshot.count == HAMMER_SNAPS_PER_IOCTL)
hammer_meta_flushdelete(fd, &dsnapshot);
} else {
ct = snap->ts / 1000000ULL;
hammer_softprune_addelm(scan, snap->tid,
ct, ct);
}
++snapshot.index;
}
if (snapshot.head.flags & HAMMER_IOC_SNAPSHOT_EOF)
break;
snapshot.index = 0;
}
if (delete_all)
hammer_meta_flushdelete(fd, &dsnapshot);
return(0);
}
static
void
hammer_meta_flushdelete(int fd, struct hammer_ioc_snapshot *dsnap)
{
while (dsnap->index < dsnap->count) {
if (ioctl(fd, HAMMERIOC_DEL_SNAPSHOT, dsnap) < 0)
break;
if (dsnap->head.error == 0)
break;
++dsnap->index;
}
dsnap->index = 0;
dsnap->count = 0;
}
static
struct softprune *
hammer_softprune_addentry(struct softprune **basep,
struct hammer_ioc_prune *template,
const char *dirpath, const char *denname __unused,
struct stat *st,
const char *linkbuf, const char *tidptr)
{
struct softprune *scan;
struct statfs fs;
char *fspath;
if (linkbuf[0] == '/') {
asprintf(&fspath, "%*.*s",
(int)(tidptr - linkbuf), (int)(tidptr - linkbuf),
linkbuf);
} else {
asprintf(&fspath, "%s/%*.*s", dirpath,
(int)(tidptr - linkbuf), (int)(tidptr - linkbuf),
linkbuf);
}
if (statfs(fspath, &fs) < 0) {
free(fspath);
return(NULL);
}
for (scan = *basep; scan; scan = scan->next) {
if (bcmp(&fs.f_fsid, &scan->fs.f_fsid, sizeof(fs.f_fsid)) != 0)
continue;
if (strcmp(fs.f_mntonname, scan->fs.f_mntonname) != 0)
continue;
break;
}
if (scan == NULL) {
scan = malloc(sizeof(*scan));
bzero(scan, sizeof(*scan));
scan->fs = fs;
scan->filesystem = fspath;
scan->prune = *template;
scan->maxelms = 32;
scan->prune.elms = malloc(sizeof(struct hammer_ioc_prune_elm) *
scan->maxelms);
scan->next = *basep;
*basep = scan;
} else {
free(fspath);
}
hammer_softprune_addelm(scan,
(hammer_tid_t)strtoull(tidptr + 2, NULL, 0),
(st ? st->st_ctime : 0),
(st ? st->st_mtime : 0));
return(scan);
}
static
void
hammer_softprune_addelm(struct softprune *scan, hammer_tid_t tid,
time_t ct, time_t mt)
{
struct hammer_ioc_prune_elm *elm;
if (scan->prune.nelms >= scan->maxelms - 1) {
scan->maxelms = (scan->maxelms * 3 / 2);
scan->prune.elms = realloc(scan->prune.elms,
sizeof(*elm) * scan->maxelms);
}
elm = &scan->prune.elms[scan->prune.nelms];
elm->beg_tid = tid;
elm->end_tid = 0;
elm->mod_tid = 0;
if (ct < mt)
elm->mod_tid = ct;
else
elm->mod_tid = mt;
++scan->prune.nelms;
}
static
int
hammer_softprune_qsort_cmp(const void *arg1, const void *arg2)
{
const struct hammer_ioc_prune_elm *elm1 = arg1;
const struct hammer_ioc_prune_elm *elm2 = arg2;
if (elm1->beg_tid < elm2->beg_tid)
return(1);
if (elm1->beg_tid > elm2->beg_tid)
return(-1);
return(0);
}
static
void
hammer_softprune_finalize(struct softprune *scan)
{
struct hammer_ioc_prune_elm *elm;
time_t t;
long delta;
int i;
if (scan->prune.nelms == 0)
return;
qsort(scan->prune.elms, scan->prune.nelms, sizeof(*elm),
hammer_softprune_qsort_cmp);
for (i = 0; i < scan->prune.nelms; ++i) {
elm = &scan->prune.elms[i];
if (i == 0) {
elm->end_tid = HAMMER_MAX_TID;
} else if (elm[0].beg_tid == elm[-1].beg_tid) {
--scan->prune.nelms;
if (i != scan->prune.nelms) {
bcopy(elm + 1, elm,
(scan->prune.nelms - i) * sizeof(*elm));
}
--i;
continue;
} else {
elm->end_tid = elm[-1].beg_tid;
}
}
if (scan->prune_min) {
t = time(NULL);
for (i = scan->prune.nelms - 1; i >= 0; --i) {
elm = &scan->prune.elms[i];
if (elm->mod_tid == 0)
continue;
delta = (long)(t - (time_t)elm->mod_tid);
if (delta < scan->prune_min)
break;
}
++i;
if (i) {
printf("Prune %s: prune_min: Will not clean between "
"the teeth of the first %d snapshots\n",
scan->filesystem, i);
bcopy(&scan->prune.elms[i], &scan->prune.elms[0],
(scan->prune.nelms - i) * sizeof(scan->prune.elms[0]));
scan->prune.elms[0].end_tid = HAMMER_MAX_TID;
scan->prune.nelms -= i;
}
}
if (scan->prune.nelms) {
bcopy(&scan->prune.elms[1], &scan->prune.elms[0],
(scan->prune.nelms - 1) * sizeof(scan->prune.elms[0]));
--scan->prune.nelms;
}
if (scan->prune.nelms) {
assert(scan->prune.nelms < scan->maxelms);
elm = &scan->prune.elms[scan->prune.nelms];
elm->beg_tid = 1;
elm->end_tid = elm[-1].beg_tid;
++scan->prune.nelms;
}
for (i = 0; i < scan->prune.nelms; ++i) {
elm = &scan->prune.elms[i];
elm->mod_tid = elm->end_tid - elm->beg_tid;
printf("TID %016jx - %016jx\n",
(uintmax_t)elm->beg_tid, (uintmax_t)elm->end_tid);
}
}
static
void
softprune_usage(int code)
{
fprintf(stderr, "Badly formed prune command, use:\n");
fprintf(stderr, "hammer prune <softlink-dir>\n");
fprintf(stderr, "hammer prune-everything <filesystem>\n");
exit(code);
}