#include <sys/param.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <libnvpair.h>
#include "ndmpd_log.h"
#include "ndmpd.h"
#define NDMP_DUMPDATES "dumpdates"
#define E_MONTH 4
#define E_DAY 8
#define E_HOUR 11
#define E_MINUTE 14
#define E_SECOND 17
#define E_YEAR 20
typedef struct dumpdates {
char dd_name[TLM_MAX_PATH_NAME];
char dd_level;
time_t dd_ddate;
struct dumpdates *dd_next;
} dumpdates_t;
static char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
mutex_t ndmp_dd_lock = DEFAULTMUTEX;
int ndmp_isdst = -1;
char *zfs_dumpdate_props[] = {
"dumpdates:level0",
"dumpdates:level1",
"dumpdates:level2",
"dumpdates:level3",
"dumpdates:level4",
"dumpdates:level5",
"dumpdates:level6",
"dumpdates:level7",
"dumpdates:level8",
"dumpdates:level9",
};
static int
lookup(char *str)
{
register char *cp, *cp2;
if (!str)
return (-1);
for (cp = months, cp2 = str; *cp != '\0'; cp += 3)
if (strncmp(cp, cp2, 3) == 0)
return ((cp-months) / 3);
return (-1);
}
static int
unctime(char *str, time_t *t)
{
struct tm then;
char dbuf[26];
if (!str || !t)
return (-1);
(void) memset(&then, 0, sizeof (then));
(void) strlcpy(dbuf, str, sizeof (dbuf) - 1);
dbuf[sizeof (dbuf) - 1] = '\0';
dbuf[E_MONTH+3] = '\0';
if ((then.tm_mon = lookup(&dbuf[E_MONTH])) < 0)
return (-1);
then.tm_mday = atoi(&dbuf[E_DAY]);
then.tm_hour = atoi(&dbuf[E_HOUR]);
then.tm_min = atoi(&dbuf[E_MINUTE]);
then.tm_sec = atoi(&dbuf[E_SECOND]);
then.tm_year = atoi(&dbuf[E_YEAR]) - 1900;
then.tm_isdst = ndmp_isdst;
NDMP_LOG(LOG_DEBUG,
"yday %d wday %d %d/%d/%d %02d:%02d:%02d",
then.tm_yday, then.tm_wday, then.tm_year, then.tm_mon,
then.tm_mday, then.tm_hour, then.tm_min, then.tm_sec);
*t = mktime(&then);
return (0);
}
static char *
ddates_pathname(char *buf)
{
return (ndmpd_make_bk_dir_path(buf, NDMP_DUMPDATES));
}
static char *
getaline(FILE *fp, char *line, int llen)
{
char *save;
int len;
if (!fp || !line)
return (NULL);
*(save = line) = '\0';
do {
if (fgets(line, llen, fp) != line)
return (NULL);
if (*line == '#')
continue;
len = strlen(line);
if (len <= 0)
continue;
line += len-1;
if (*line != '\n')
return (NULL);
*line = '\0';
if (--len <= 0)
break;
if (*(line-1) != '\\')
break;
*(line-1) = '\n';
llen -= len;
} while (llen > 0);
return (save);
}
static char *
get_ddname(char **bpp)
{
char *h, *t, *save;
if (!bpp || !*bpp)
return (NULL);
*bpp += strspn(*bpp, "\t ");
save = h = t = *bpp;
while (*t) {
if (*t == '\t' || *t == ' ') {
t++;
break;
}
if (*t == '\\')
switch (*(t+1)) {
case '\t':
case ' ':
t++;
default:
break;
}
*h++ = *t++;
}
*bpp = t;
*h++ = '\0';
return (save);
}
static int
get_ddlevel(char **bpp)
{
char *t, *save;
if (!bpp || !*bpp)
return (-1);
*bpp += strspn(*bpp, "\t ");
save = t = *bpp;
if (IS_LBR_BKTYPE(*t)) {
NDMP_LOG(LOG_DEBUG, "Lbr bk type %c", *t);
*++t = '\0';
*bpp = ++t;
return (toupper(*save));
}
while (isdigit(*t))
t++;
*t++ = '\0';
*bpp = t;
return (atoi(save));
}
static char *
get_ddate(char **bpp)
{
char *save;
if (!bpp || !*bpp)
return (NULL);
*bpp += strspn(*bpp, "\t ");
save = *bpp;
*bpp += strlen(*bpp);
return (save);
}
static void
put_ddname(FILE *fp, char *nm)
{
if (!nm)
return;
while (*nm)
switch (*nm) {
case ' ':
case '\n':
case '\t':
(void) fputc('\\', fp);
default:
(void) fputc(*nm++, fp);
}
}
static void
put_ddlevel(FILE *fp, int level)
{
if (!fp)
return;
(void) fprintf(fp, IS_LBR_BKTYPE(level) ? "%c" : "%d", level);
}
static void put_ddate(FILE *fp,
time_t t)
{
char tbuf[64];
if (!fp)
return;
NDMP_LOG(LOG_DEBUG, "[%u]", t);
(void) ctime_r(&t, tbuf, sizeof (tbuf));
(void) fprintf(fp, tbuf);
}
static void
dd_free(dumpdates_t *ddheadp)
{
dumpdates_t *save;
if (!ddheadp)
return;
ddheadp = ddheadp->dd_next;
while (ddheadp) {
save = ddheadp->dd_next;
free(ddheadp);
ddheadp = save;
}
}
static int
makedumpdate(dumpdates_t *ddp, char *tbuf)
{
char *nmp, *un_buf;
int rv;
if (!ddp || !tbuf)
rv = -1;
else if (!(nmp = get_ddname(&tbuf))) {
rv = -1;
NDMP_LOG(LOG_DEBUG, "get_ddname failed 0x%p", nmp);
} else if ((ddp->dd_level = get_ddlevel(&tbuf)) < 0) {
rv = -1;
NDMP_LOG(LOG_DEBUG, "dd_level < 0 %d", ddp->dd_level);
} else if (!(un_buf = get_ddate(&tbuf))) {
rv = -1;
NDMP_LOG(LOG_DEBUG, "get_ddate failed 0x%p", un_buf);
} else if (unctime(un_buf, &ddp->dd_ddate) < 0) {
rv = -1;
NDMP_LOG(LOG_DEBUG, "unctime failed \"%s\"", un_buf);
} else {
(void) strlcpy(ddp->dd_name, nmp, TLM_MAX_PATH_NAME);
rv = 0;
}
return (rv);
}
static int
getrecord(FILE *fp, dumpdates_t *ddatep, int *recno)
{
char tbuf[BUFSIZ];
if (!fp || !ddatep || !recno)
return (-1);
do {
if (getaline(fp, tbuf, sizeof (tbuf)) != tbuf)
return (-1);
} while (!*tbuf);
if (makedumpdate(ddatep, tbuf) < 0)
NDMP_LOG(LOG_DEBUG,
"Unknown intermediate format in %s, line %d", tbuf, *recno);
(*recno)++;
if (IS_LBR_BKTYPE(ddatep->dd_level & 0xff)) {
NDMP_LOG(LOG_DEBUG, "Lbr: [%s][%c][%u]",
ddatep->dd_name, ddatep->dd_level, ddatep->dd_ddate);
} else
NDMP_LOG(LOG_DEBUG, "[%s][%d][%u]",
ddatep->dd_name, ddatep->dd_level, ddatep->dd_ddate);
return (0);
}
static int
readdumptimes(FILE *fp, dumpdates_t *ddheadp)
{
int recno;
register struct dumpdates *ddwalk;
if (!fp || !ddheadp)
return (-1);
recno = 1;
(void) memset((void *)ddheadp, 0, sizeof (*ddheadp));
for (; ; ) {
ddwalk = ndmp_malloc(sizeof (*ddwalk));
if (!ddwalk)
return (-1);
if (getrecord(fp, ddwalk, &recno) < 0) {
free(ddwalk);
break;
}
ddwalk->dd_next = ddheadp->dd_next;
ddheadp->dd_next = ddwalk;
ddheadp = ddwalk;
}
return (0);
}
static void
dumprecout(FILE *fp, dumpdates_t *ddp)
{
if (!ddp)
return;
if (IS_LBR_BKTYPE(ddp->dd_level)) {
NDMP_LOG(LOG_DEBUG, "Lbr: [%s][%c][%u]",
ddp->dd_name, ddp->dd_level, ddp->dd_ddate);
} else
NDMP_LOG(LOG_DEBUG, "[%s][%d][%u]",
ddp->dd_name, ddp->dd_level, ddp->dd_ddate);
put_ddname(fp, ddp->dd_name);
(void) fputc('\t', fp);
put_ddlevel(fp, ddp->dd_level);
(void) fputc('\t', fp);
put_ddate(fp, ddp->dd_ddate);
}
static int
initdumptimes(dumpdates_t *ddheadp)
{
char fname[PATH_MAX];
int rv;
FILE *fp;
if (!ddheadp)
return (-1);
if (!ddates_pathname(fname))
return (-1);
fp = fopen(fname, "r");
if (!fp) {
if (errno != ENOENT) {
NDMP_LOG(LOG_ERR, "Cannot read %s: %m.", fname);
return (-1);
}
NDMP_LOG(LOG_DEBUG,
"No file `%s', making an empty one", fname);
fp = fopen(fname, "w");
if (!fp) {
NDMP_LOG(LOG_ERR, "Cannot create %s: %m.", fname);
return (-1);
}
(void) fclose(fp);
fp = fopen(fname, "r");
if (!fp) {
NDMP_LOG(LOG_ERR,
"Cannot read %s after creating it. %m.", fname);
return (-1);
}
}
rv = readdumptimes(fp, ddheadp);
(void) fclose(fp);
return (rv);
}
static int
putdumptime(char *path, int level, time_t ddate)
{
int found;
char fname[PATH_MAX], bakfname[PATH_MAX];
FILE *rfp, *wfp;
dumpdates_t ddhead, tmpdd;
register dumpdates_t *ddp;
int rv;
if (!path)
return (-1);
if (IS_LBR_BKTYPE(level)) {
NDMP_LOG(LOG_DEBUG, "Lbr: [%s][%c][%u]", path, level, ddate);
} else {
NDMP_LOG(LOG_DEBUG, "[%s][%d][%u]", path, level, ddate);
}
if (!ddates_pathname(fname)) {
NDMP_LOG(LOG_ERR, "Cannot get dumpdate file path name.");
return (-1);
}
rfp = fopen(fname, "r");
if (!rfp) {
NDMP_LOG(LOG_DEBUG, "Creating %s.", fname);
(void) memset((void *)&ddhead, 0, sizeof (ddhead));
if (initdumptimes(&ddhead) < 0) {
NDMP_LOG(LOG_ERR, "Could not initialize %s.",
NDMP_DUMPDATES);
dd_free(&ddhead);
return (-1);
}
} else {
rv = readdumptimes(rfp, &ddhead);
if (rv < 0) {
NDMP_LOG(LOG_ERR, "Error reading dumpdates file.");
(void) fclose(rfp);
dd_free(&ddhead);
return (-1);
}
(void) fclose(rfp);
}
(void) snprintf(bakfname, PATH_MAX, "%s.bak", fname);
wfp = fopen(bakfname, "w");
if (!wfp) {
NDMP_LOG(LOG_ERR, "Cannot open %s: %m.", bakfname);
dd_free(&ddhead);
return (-1);
}
NDMP_LOG(LOG_DEBUG, "[%s][%s]", fname, bakfname);
found = 0;
for (ddp = ddhead.dd_next; ddp; ddp = ddp->dd_next) {
if (ddp->dd_level != level)
continue;
if (strcmp(path, ddp->dd_name))
continue;
NDMP_LOG(LOG_DEBUG, "Found: [%s][%d][%u]",
ddp->dd_name, ddp->dd_level, ddp->dd_ddate);
found = 1;
ddp->dd_ddate = ddate;
NDMP_LOG(LOG_DEBUG,
"Updated to: [%s][%d][%u]",
ddp->dd_name, ddp->dd_level, ddp->dd_ddate);
}
for (ddp = ddhead.dd_next; ddp; ddp = ddp->dd_next)
dumprecout(wfp, ddp);
dd_free(&ddhead);
if (!found) {
(void) strlcpy(tmpdd.dd_name, path, TLM_MAX_PATH_NAME);
tmpdd.dd_level = level;
tmpdd.dd_ddate = ddate;
dumprecout(wfp, &tmpdd);
}
(void) fclose(wfp);
(void) rename(bakfname, fname);
return (0);
}
static int
append_dumptime(char *fname, char *path, int level, time_t ddate)
{
char fpath[PATH_MAX], bakfpath[PATH_MAX];
FILE *fp;
dumpdates_t tmpdd;
if (!fname || !*fname || !path || !*path)
return (-1);
if (IS_LBR_BKTYPE(level & 0xff)) {
NDMP_LOG(LOG_DEBUG,
"Lbr: [%s][%s][%c][%u]",
fname, path, level, ddate);
} else
NDMP_LOG(LOG_DEBUG, "[%s][%s][%d][%u]",
fname, path, level, ddate);
if (!ndmpd_make_bk_dir_path(fpath, fname)) {
NDMP_LOG(LOG_ERR, "Cannot get dumpdate file path name %s.",
fname);
return (-1);
}
(void) snprintf(bakfpath, PATH_MAX, "%s.bak", fpath);
fp = fopen(fpath, "r");
if (fp) {
(void) fclose(fp);
if (filecopy(bakfpath, fpath) != 0) {
NDMP_LOG(LOG_ERR, "Cannot copy %s to %s: %m.",
fpath, bakfpath);
return (-1);
}
}
fp = fopen(bakfpath, "a");
if (!fp) {
NDMP_LOG(LOG_ERR, "Cannot open %s: %m.", bakfpath);
return (-1);
}
NDMP_LOG(LOG_DEBUG, "[%s][%s]", fpath, bakfpath);
(void) strlcpy(tmpdd.dd_name, path, TLM_MAX_PATH_NAME);
tmpdd.dd_level = level;
tmpdd.dd_ddate = ddate;
dumprecout(fp, &tmpdd);
(void) fclose(fp);
(void) rename(bakfpath, fpath);
return (0);
}
static dumpdates_t *
find_date(dumpdates_t *ddp, char *path, int level, time_t t)
{
for (; ddp; ddp = ddp->dd_next)
if (ddp->dd_level == level && ddp->dd_ddate > t &&
strcmp(path, ddp->dd_name) == 0)
break;
return (ddp);
}
int
ndmpd_get_dumptime(char *path, int *level, time_t *ddate)
{
int i;
dumpdates_t ddhead, *ddp, *save;
char vol[ZFS_MAX_DATASET_NAME_LEN];
nvlist_t *userprops;
zfs_handle_t *zhp;
nvlist_t *propval = NULL;
char *strval = NULL;
if (!path || !level || !ddate)
return (-1);
NDMP_LOG(LOG_DEBUG, "[%s] level %d",
path, *level);
if (*level == 0) {
*ddate = (time_t)0;
return (0);
}
(void) mutex_lock(&zlib_mtx);
if ((zlibh != NULL) &&
(get_zfsvolname(vol, sizeof (vol), path) == 0) &&
((zhp = zfs_open(zlibh, vol, ZFS_TYPE_DATASET)) != NULL)) {
if ((userprops = zfs_get_user_props(zhp)) == NULL) {
*level = 0;
*ddate = (time_t)0;
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (0);
}
for (i = *level - 1; i >= 0; i--) {
if (nvlist_lookup_nvlist(userprops,
zfs_dumpdate_props[i], &propval) == 0) {
*level = i;
break;
}
}
if (propval == NULL ||
nvlist_lookup_string(propval, ZPROP_VALUE,
&strval) != 0) {
*level = 0;
*ddate = (time_t)0;
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (0);
}
if (unctime(strval, ddate) < 0) {
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (-1);
}
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (0);
}
(void) mutex_unlock(&zlib_mtx);
(void) memset((void *)&ddhead, 0, sizeof (ddhead));
if (initdumptimes(&ddhead) < 0) {
dd_free(&ddhead);
return (-1);
}
if ((ddp = ddhead.dd_next) == 0) {
if (!IS_LBR_BKTYPE(*level & 0xff))
*level = 0;
*ddate = 0;
return (0);
}
if (IS_LBR_BKTYPE(*level & 0xff)) {
save = find_date(ddp, path, *level, *ddate);
NDMP_LOG(LOG_DEBUG,
"LBR_BKTYPE save 0x%p", save);
*ddate = save ? save->dd_ddate : (time_t)0;
} else {
save = NULL;
for (i = *level - 1; i >= 0; i--) {
save = find_date(ddp, path, i, *ddate);
if (save) {
*level = save->dd_level;
*ddate = save->dd_ddate;
break;
}
}
if (!save) {
*level = 0;
*ddate = (time_t)0;
}
}
dd_free(&ddhead);
return (0);
}
int
ndmpd_put_dumptime(char *path, int level, time_t ddate)
{
char vol[ZFS_MAX_DATASET_NAME_LEN];
zfs_handle_t *zhp;
char tbuf[64];
int rv;
NDMP_LOG(LOG_DEBUG, "[%s][%d][%u]", path, level,
ddate);
(void) mutex_lock(&zlib_mtx);
if ((zlibh != NULL) &&
(get_zfsvolname(vol, sizeof (vol), path) == 0) &&
((zhp = zfs_open(zlibh, vol, ZFS_TYPE_DATASET)) != NULL)) {
(void) ctime_r(&ddate, tbuf, sizeof (tbuf));
rv = zfs_prop_set(zhp, zfs_dumpdate_props[level], tbuf);
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (rv);
}
(void) mutex_unlock(&zlib_mtx);
(void) mutex_lock(&ndmp_dd_lock);
rv = putdumptime(path, level, ddate);
(void) mutex_unlock(&ndmp_dd_lock);
return (rv);
}
int
ndmpd_append_dumptime(char *fname, char *path, int level, time_t ddate)
{
char vol[ZFS_MAX_DATASET_NAME_LEN];
zfs_handle_t *zhp;
char tbuf[64];
int rv;
NDMP_LOG(LOG_DEBUG, "[%s][%s][%d][%u]", fname,
path, level, ddate);
(void) mutex_lock(&zlib_mtx);
if ((zlibh != NULL) &&
(get_zfsvolname(vol, sizeof (vol), path) == 0) &&
((zhp = zfs_open(zlibh, vol, ZFS_TYPE_DATASET)) != NULL)) {
(void) ctime_r(&ddate, tbuf, sizeof (tbuf));
rv = zfs_prop_set(zhp, zfs_dumpdate_props[level], tbuf);
zfs_close(zhp);
(void) mutex_unlock(&zlib_mtx);
return (rv);
}
(void) mutex_unlock(&zlib_mtx);
(void) mutex_lock(&ndmp_dd_lock);
rv = append_dumptime(fname, path, level, ddate);
(void) mutex_unlock(&ndmp_dd_lock);
return (rv);
}