Viewing: lsnapshot.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017, Intel Corporation.
*
* lustre/utils/lsnapshot.c
*
* Author: Fan, Yong <fan.yong@intel.com>
*/
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/file.h>
#include <time.h>
#include <limits.h>
#include <ctype.h>
#include <libcfs/util/list.h>
#include <libcfs/util/ioctl.h>
#include <libcfs/util/string.h>
#include <linux/lustre/lustre_ioctl.h>
#include <linux/lustre/lustre_barrier_user.h>
#include "obdctl.h"
#define SNAPSHOT_CONF_DIR "/etc/lsnapshot"
#define LDEV_CONF "/etc/ldev.conf"
#define SNAPSHOT_LOG "/var/log/lsnapshot.log"
#define SNAPSHOT_MAGIC "0x14F711B9"
#define MAX_BUF_SIZE 4096
enum snapshot_role {
SR_MGS = 0x0001,
SR_MDT = 0x0002,
SR_OST = 0x0004,
};
struct snapshot_target {
struct list_head st_list;
/* Target node name. */
char *st_host;
char *st_fhost;
/* Where the pool is */
char *st_dir;
/* The target pool name on the target node. */
char *st_pool;
/* The backend filesystem name against the target pool. */
char *st_filesystem;
int st_role;
unsigned int st_index;
unsigned int st_gen;
int st_line;
int st_status;
pid_t st_pid;
bool st_ignored;
};
struct snapshot_instance {
struct list_head si_mdts_list;
struct list_head si_osts_list;
struct snapshot_target *si_mgs;
struct snapshot_target *si_mdt0;
FILE *si_log_fp;
char *si_rsh;
char *si_fsname;
char *si_ssname;
char *si_new_ssname;
char *si_comment;
int si_conf_fd;
int si_timeout;
bool si_barrier;
bool si_detail;
bool si_force;
};
static const char snapshot_rsh_default[] = "ssh";
static char snapshot_path[MAX_BUF_SIZE];
static char *snapshot_role2name(char *name, enum snapshot_role role,
__u32 index)
{
if (role & SR_MDT)
snprintf(name, 8, "MDT%04x", index);
else if (role & SR_MGS)
snprintf(name, 4, "MGS");
else
snprintf(name, 8, "OST%04x", index);
return name;
}
#define SNAPSHOT_ADD_LOG(si, format, ...) \
do { \
char buf[MAX_BUF_SIZE]; \
char *ptr; \
time_t tt; \
\
memset(buf, 0, sizeof(buf)); \
time(&tt); \
snprintf(buf, sizeof(buf) - 1, "%s", ctime(&tt)); \
ptr = strrchr(buf, '\n'); \
if (ptr) \
*ptr = '\0'; \
\
fprintf(si->si_log_fp, "%s (%d:%s:%d:%s:%s): "format, buf, \
getpid(), __func__, __LINE__, si->si_fsname, \
si->si_rsh, ## __VA_ARGS__); \
} while (0)
#define DRSH "%s %s"
#define DFSNAME "%s/%s"
#define DSSNAME "%s/%s@%s"
#define DZFS "%s zfs"
#define DIMPORT "%s zpool import -d %s %s > /dev/null 2>&1"
#define PRSH(si, st, foreign) (si)->si_rsh, \
foreign ? (st)->st_fhost : (st)->st_host
#define PFSNAME(st) (st)->st_pool, (st)->st_filesystem
#define PSSNAME(si, st) PFSNAME(st), (si)->si_ssname
#define PSS_NEW(si, st) PFSNAME(st), (si)->si_new_ssname
#define PZFS(st) snapshot_path
#define PIMPORT(st) snapshot_path, \
(st)->st_dir ? (st)->st_dir : "/dev -d /tmp", (st)->st_pool
static char *snapshot_fgets(FILE *fp, char *buf, int buflen)
{
char *ptr;
memset(buf, 0, buflen);
if (fgets(buf, buflen, fp) == NULL)
return NULL;
ptr = strchr(buf, '\n');
if (ptr)
*ptr = '\0';
return buf;
}
static int snapshot_exec(const char *cmd)
{
int rc;
errno = 0;
/* system() return value depends on both the system() general framework,
* such as whether fork()/exec() success or fail, and the real @cmd exec
* result. Especially, if the @cmd is remote command, we may cannot know
* the real failure. */
rc = system(cmd);
/* fork()/exec() error */
if (rc == -1)
return errno != 0 ? -errno : -1;
if (WIFEXITED(rc)) {
rc = WEXITSTATUS(rc);
if (rc > 0)
rc = -rc;
} else if (WIFSIGNALED(rc)) {
rc = -EINTR;
} else {
/* all other known or unknown cases. */
rc = -EFAULT;
}
return rc;
}
static int snapshot_load_conf_ldev(struct snapshot_instance *si, char *buf,
struct snapshot_target *st, char **role)
{
char *label = NULL;
char *device = NULL;
char *ptr;
char *ptr1;
int len;
int rc;
rc = sscanf(buf, "%ms %ms %ms %ms",
&st->st_host, &st->st_fhost, &label, &device);
if (rc < 4) {
rc = -EINVAL;
goto out;
}
if (strcmp(st->st_fhost, "-") == 0)
st->st_fhost = NULL;
/* Format of device:
* [md|zfs:][pool_dir/]<pool>/<filesystem> */
ptr = strchr(device, ':');
if (ptr) {
ptr++;
if (strncmp(device, "zfs:", strlen("zfs:")) != 0) {
rc = -EINVAL;
goto out;
}
} else {
ptr = device;
}
ptr1 = strrchr(ptr, '/');
/* "ptr1 - ptr + 1 == strlen(ptr)" means '/' is at the tail. */
if (!ptr1 || ptr1 == ptr || ptr1 - ptr + 1 == strlen(ptr)) {
rc = -EINVAL;
goto out;
}
len = strlen(ptr1);
st->st_filesystem = malloc(len);
if (!st->st_filesystem) {
rc = -ENOMEM;
goto out;
}
*ptr1 = '\0';
strncpy(st->st_filesystem, ptr1 + 1, len - 1);
st->st_filesystem[len - 1] = '\0';
if (*ptr == '/') {
ptr1 = strrchr(ptr, '/');
*ptr1 = '\0';
st->st_dir = strdup(ptr);
if (!st->st_dir) {
rc = -ENOMEM;
goto out;
}
ptr = ptr1 + 1;
}
st->st_pool = strdup(ptr);
if (!st->st_pool) {
rc = -ENOMEM;
goto out;
}
/* Format of label:
* fsname-<role><index> or <role><index> */
ptr = strrchr(label, '-');
if (ptr) {
if (strncmp(si->si_fsname, label, ptr - label) != 0) {
/* This line is NOT for current filesystem .*/
rc = -EAGAIN;
goto out;
}
ptr++;
} else {
ptr = label;
}
if (strlen(ptr) < 3 || strlen(ptr) > 7) {
rc = -EINVAL;
goto out;
}
*role = malloc(4);
if (!*role) {
rc = -ENOMEM;
goto out;
}
strncpy(*role, ptr, 3);
(*role)[3] = 0;
ptr += 3;
len = 0;
while (isxdigit(ptr[len])) {
if (isdigit(ptr[len]))
st->st_index =
st->st_index * 16 + ptr[len] - '0';
else if (isupper(ptr[len]))
st->st_index =
st->st_index * 16 + ptr[len] - 'A' + 10;
else
st->st_index =
st->st_index * 16 + ptr[len] - 'a' + 10;
len++;
}
if (len == 0) {
if (strncasecmp(*role, "MGS", 3) != 0)
rc = -EINVAL;
else
rc = 0;
goto out;
}
if (!isxdigit(ptr[len]) && ptr[len] != '\0') {
rc = -EINVAL;
goto out;
}
out:
if (label)
free(label);
if (device)
free(device);
return rc;
}
/**
* snapshot_load_conf_one() - Read one line from snapshot conf file & load
* @si: Pointer to snapshot_instance after loading [out]
* @buf: buffer holding snapshot conf data
* @line_num: Position within @buf
* @is_ldev: If %false use old format /etc/lsnapshot/${fsname}.conf
* If %true use new format /etc/ldev.conf
*
* For old snasphot tools, the configration is in /etc/lsnapshot/${fsname}.conf,
* the format is:
* <host> <pool_dir> <pool> <local_fsname> <role(,s)> <index>
*
* For example:
* host-mdt1 /tmp myfs-mdt1 mdt1 MGS,MDT 0
* host-mdt2 /tmp myfs-mdt2 mdt2 MDT 1
* host-ost1 /tmp myfs-ost1 ost1 OST 0
* host-ost2 /tmp myfs-ost2 ost2 OST 1
*
* For new snasphot tools, the configration is in /etc/ldev.conf, which is not
* only for snapshot, but also for other purpose. The format is:
* <host> foreign/- <label> <device> [journal-path]/- [raidtab]
*
* The format of <label> is:
* fsname-<role><index> or <role><index>
*
* The format of <device> is:
* [md|zfs:][pool_dir/]<pool>/<filesystem>
*
* Snapshot only uses the fields <host>, <label> and <device>.
*
* For example:
* host-mdt1 - myfs-MDT0000 zfs:/tmp/myfs-mdt1/mdt1
*
* Return:
* * %0 for success
* * %positive the line# with which the current line is conflict
* * %-EAGAIN skip current line
* * %negative other failures
*/
static int snapshot_load_conf_one(struct snapshot_instance *si,
char *buf, int line_num, bool is_ldev)
{
struct snapshot_target *st;
char *role = NULL;
char *path;
int rc = 0;
path = getenv("PATH");
if (!path)
return -EINVAL;
memset(snapshot_path, 0, sizeof(snapshot_path));
snprintf(snapshot_path, sizeof(snapshot_path) - 1, "PATH='%s'", path);
/* filter out space */
while (isspace(*buf))
buf++;
/* skip empty line */
if (*buf == '\0')
return 0;
/* skip comment line */
if (*buf == '#')
return 0;
st = malloc(sizeof(*st));
if (!st)
return -ENOMEM;
memset(st, 0, sizeof(*st));
INIT_LIST_HEAD(&st->st_list);
if (is_ldev) {
rc = snapshot_load_conf_ldev(si, buf, st, &role);
} else {
rc = sscanf(buf, "%ms %ms %ms %ms %ms %ms %d",
&st->st_host, &st->st_fhost, &st->st_dir,
&st->st_pool, &st->st_filesystem, &role,
&st->st_index);
if (rc < 7)
rc = -EINVAL;
if (strcmp(st->st_fhost, "-") == 0)
st->st_fhost = NULL;
}
if (rc < 0)
goto out;
rc = 0;
if (strncasecmp(role, "MGS", 3) == 0) {
st->st_role = SR_MGS;
if (role[3] == ',') {
/* MGS,MDT */
if (strncasecmp(&role[4], "MDT", 3) != 0) {
rc = -EINVAL;
goto out;
}
st->st_role |= SR_MDT;
}
} else if (strncasecmp(role, "MDT", 3) == 0) {
st->st_role = SR_MDT;
if (role[3] == ',') {
/* MDT,MGS */
if (strncasecmp(&role[4], "MGS", 3) != 0) {
rc = -EINVAL;
goto out;
}
st->st_role |= SR_MGS;
}
} else if (strncasecmp(role, "OST", 3) == 0) {
st->st_role = SR_OST;
} else {
rc = -EINVAL;
goto out;
}
st->st_line = line_num;
if (st->st_role & SR_MDT) {
/* MGS is the first, MDT0 is just after the MGS
* if they are not combined together. */
if (st->st_role & SR_MGS) {
if (si->si_mgs) {
rc = si->si_mgs->st_line;
goto out;
}
si->si_mgs = st;
list_add(&st->st_list, &si->si_mdts_list);
}
if (st->st_index == 0) {
if (si->si_mdt0) {
rc = si->si_mdt0->st_line;
goto out;
}
si->si_mdt0 = st;
if (list_empty(&st->st_list)) {
if (list_empty(&si->si_mdts_list) ||
!si->si_mgs)
list_add(&st->st_list,
&si->si_mdts_list);
else
list_add(&st->st_list,
&si->si_mgs->st_list);
}
} else if (list_empty(&st->st_list)) {
list_add_tail(&st->st_list, &si->si_mdts_list);
}
} else if (st->st_role & SR_MGS) {
if (si->si_mgs) {
rc = si->si_mgs->st_line;
goto out;
}
si->si_mgs = st;
list_add(&st->st_list, &si->si_mdts_list);
} else {
list_add_tail(&st->st_list, &si->si_osts_list);
}
out:
if (role)
free(role);
if (rc) {
if (st->st_host)
free(st->st_host);
if (st->st_fhost)
free(st->st_fhost);
if (st->st_dir)
free(st->st_dir);
if (st->st_pool)
free(st->st_pool);
if (st->st_filesystem)
free(st->st_filesystem);
free(st);
}
return rc;
}
static int snapshot_load_conf(struct snapshot_instance *si, int lock_mode)
{
FILE *fp;
char buf[MAX_BUF_SIZE];
char conf_name[32];
int line_num = 1;
int fd = -1;
int rc = 0;
bool is_ldev = true;
memset(conf_name, 0, sizeof(conf_name));
strncpy(conf_name, LDEV_CONF, sizeof(conf_name) - 1);
fd = open(conf_name, O_RDONLY);
if (fd < 0) {
if (errno != ENOENT) {
fprintf(stderr,
"Can't open the snapshot config file %s: %s\n",
conf_name, strerror(errno));
return fd;
}
snprintf(conf_name, sizeof(conf_name) - 1, "%s/%s.conf",
SNAPSHOT_CONF_DIR, si->si_fsname);
fd = open(conf_name, O_RDONLY);
if (fd < 0) {
fprintf(stderr,
"Can't open the snapshot config file %s: %s\n",
conf_name, strerror(errno));
return fd;
}
is_ldev = false;
}
rc = flock(fd, lock_mode | LOCK_NB);
if (rc < 0) {
fprintf(stderr,
"Can't lock the snapshot config file %s (%d): %s\n",
conf_name, lock_mode, strerror(errno));
close(fd);
return rc;
}
fp = fdopen(fd, "r");
if (!fp) {
fprintf(stderr,
"Can't fdopen the snapshot config file %s: %s\n",
conf_name, strerror(errno));
rc = -1;
goto out_fd;
}
while (snapshot_fgets(fp, buf, MAX_BUF_SIZE) != NULL) {
rc = snapshot_load_conf_one(si, buf, line_num, is_ldev);
if (rc == -EINVAL) {
fprintf(stderr,
"Invalid snapshot config file %s at the line "
"%d '%s'\n", conf_name, line_num, buf);
} else if (rc == -EAGAIN) {
rc = 0;
} else if (rc > 0) {
fprintf(stderr,
"The config role has been specified repeatedly "
"at the lines %d/%d in %s\n",
rc, line_num, conf_name);
rc = -EINVAL;
}
if (rc)
goto out;
line_num++;
}
if (!si->si_mdt0) {
fprintf(stderr,
"Miss MDT0 in the config file %s\n",
conf_name);
rc = -1;
goto out;
}
/* By default, the MGS is on the MDT0 if it is not specified. */
if (!si->si_mgs) {
si->si_mgs = si->si_mdt0;
si->si_mgs->st_role |= SR_MGS;
}
if (list_empty(&si->si_osts_list)) {
fprintf(stderr,
"Miss OST(s) in the config file %s\n",
conf_name);
rc = -1;
goto out;
}
out:
if (fd >= 0) {
if (rc < 0)
flock(fd, LOCK_UN);
else
si->si_conf_fd = fd;
}
/* fclose() closes any associated underlying file descriptor.
* explicit close(fd) not required
*/
fclose(fp);
return rc;
out_fd:
close(fd);
return rc;
}
static void snapshot_unload_conf(struct snapshot_instance *si)
{
struct snapshot_target *st;
while (!list_empty(&si->si_mdts_list)) {
st = list_first_entry(&si->si_mdts_list,
struct snapshot_target, st_list);
list_del(&st->st_list);
free(st->st_host);
free(st->st_fhost);
free(st->st_dir);
free(st->st_pool);
free(st->st_filesystem);
free(st);
}
while (!list_empty(&si->si_osts_list)) {
st = list_first_entry(&si->si_osts_list,
struct snapshot_target, st_list);
list_del(&st->st_list);
free(st->st_host);
free(st->st_fhost);
free(st->st_dir);
free(st->st_pool);
free(st->st_filesystem);
free(st);
}
si->si_mgs = NULL;
si->si_mdt0 = NULL;
if (si->si_conf_fd >= 0) {
flock(si->si_conf_fd, LOCK_UN);
close(si->si_conf_fd);
si->si_conf_fd = -1;
}
}
static int snapshot_handle_string_option(char **dst, const char *option,
const char *opt_name)
{
if (*dst && *dst != snapshot_rsh_default) {
fprintf(stderr,
"%s option has been specified repeatedly.\n", opt_name);
return -EINVAL;
}
*dst = strdup(option);
if (!*dst)
return -ENOMEM;
return 0;
}
static void snapshot_fini(struct snapshot_instance *si)
{
snapshot_unload_conf(si);
if (si->si_log_fp)
fclose(si->si_log_fp);
if (si->si_rsh && si->si_rsh != snapshot_rsh_default)
free(si->si_rsh);
if (si->si_fsname)
free(si->si_fsname);
if (si->si_ssname)
free(si->si_ssname);
if (si->si_new_ssname)
free(si->si_new_ssname);
if (si->si_comment)
free(si->si_comment);
free(si);
}
static struct snapshot_instance *
snapshot_init(int argc, char * const argv[], const struct option *longopts,
const char *optstring, void (*usage)(void),
int lock_mode, int *err)
{
struct snapshot_instance *si;
int idx;
int opt;
*err = 0;
si = malloc(sizeof(*si));
if (!si) {
fprintf(stderr,
"No enough memory to initialize snapshot instance.\n");
*err = -ENOMEM;
return NULL;
}
memset(si, 0, sizeof(*si));
INIT_LIST_HEAD(&si->si_mdts_list);
INIT_LIST_HEAD(&si->si_osts_list);
si->si_rsh = (char *)snapshot_rsh_default;
si->si_conf_fd = -1;
si->si_timeout = BARRIER_TIMEOUT_DEFAULT;
si->si_barrier = true;
si->si_detail = false;
si->si_force = false;
while ((opt = getopt_long(argc, argv, optstring,
longopts, &idx)) != EOF) {
switch (opt) {
case 'b':
if (!optarg || strcmp(optarg, "on") == 0) {
si->si_barrier = true;
} else if (strcmp(optarg, "off") == 0) {
si->si_barrier = false;
} else {
usage();
*err = -EINVAL;
goto out;
}
break;
case 'c':
*err = snapshot_handle_string_option(&si->si_comment,
optarg, "comment");
if (*err != 0)
goto out;
break;
case 'd':
si->si_detail = true;
break;
case 'f':
si->si_force = true;
break;
case 'F':
*err = snapshot_handle_string_option(&si->si_fsname,
optarg, "fsname");
if (*err != 0)
goto out;
break;
case 'n':
*err = snapshot_handle_string_option(&si->si_ssname,
optarg, "ssname");
if (*err != 0)
goto out;
break;
case 'N':
*err = snapshot_handle_string_option(&si->si_new_ssname,
optarg,
"new ssname");
if (*err != 0)
goto out;
break;
case 'r':
*err = snapshot_handle_string_option(&si->si_rsh,
optarg,
"remote shell");
if (*err != 0)
goto out;
break;
case 't':
si->si_timeout = atoi(optarg);
break;
default:
*err = -EINVAL;
usage();
goto out;
case 'h':
usage();
snapshot_fini(si);
*err = 0;
return NULL;
}
}
if (!si->si_fsname) {
fprintf(stderr, "The fsname must be specified\n");
usage();
*err = -EINVAL;
goto out;
}
if (strlen(si->si_fsname) > 8) {
fprintf(stderr, "Invalid fsname %s\n", si->si_fsname);
*err = -EINVAL;
goto out;
}
si->si_log_fp = fopen(SNAPSHOT_LOG, "a");
if (!si->si_log_fp) {
*err = -errno;
fprintf(stderr,
"Can't open the snapshot log file %s: %s\n",
SNAPSHOT_LOG, strerror(errno));
goto out;
}
*err = snapshot_load_conf(si, lock_mode);
out:
if (*err != 0 && si) {
snapshot_fini(si);
si = NULL;
}
return si;
}
static int __snapshot_wait(struct snapshot_instance *si,
struct list_head *head, int *err)
{
struct snapshot_target *st;
int count = 0;
int rc = 0;
list_for_each_entry(st, head, st_list) {
if (st->st_pid == 0)
continue;
rc = waitpid(st->st_pid, &st->st_status, 0);
if (rc < 0) {
SNAPSHOT_ADD_LOG(si, "Can't wait child (%d) operation "
"on the target <%s(%s):%x:%d>: %s\n",
st->st_pid, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
strerror(errno));
count++;
if (*err == 0)
*err = rc;
st->st_pid = 0;
/* continue to wait for next */
continue;
}
if (WIFEXITED(st->st_status)) {
rc = WEXITSTATUS(st->st_status);
if (rc > 0)
rc -= 256;
if (rc == -ESRCH) {
st->st_ignored = true;
} else if (rc) {
count++;
if (*err == 0)
*err = rc;
}
} else if (WIFSIGNALED(st->st_status)) {
SNAPSHOT_ADD_LOG(si, "The child (%d) operation on the "
"target <%s(%s):%x:%d> was killed by "
"signal %d\n",
st->st_pid, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
WTERMSIG(st->st_status));
count++;
if (*err == 0)
*err = -EINTR;
} else {
SNAPSHOT_ADD_LOG(si, "The child (%d) operation on the "
"target <%s(%s):%x:%d> failed for "
"unknown reason\n",
st->st_pid, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index);
count++;
if (*err == 0)
*err = -EFAULT;
}
st->st_pid = 0;
}
return count;
}
static int snapshot_wait(struct snapshot_instance *si, int *err)
{
int count;
count = __snapshot_wait(si, &si->si_mdts_list, err);
count += __snapshot_wait(si, &si->si_osts_list, err);
return count;
}
static char *snapshot_first_skip_blank(char *buf)
{
char *ptr;
ptr = strchr(buf, ' ');
if (!ptr) {
ptr = strchr(buf, '\t');
if (!ptr)
return NULL;
}
while (*ptr == ' ' || *ptr == '\t')
ptr++;
if (*ptr == '\0')
ptr = NULL;
return ptr;
}
static int mdt0_is_lustre_snapshot(struct snapshot_instance *si)
{
struct snapshot_target *st = si->si_mdt0;
char buf[MAX_BUF_SIZE * 3];
FILE *fp;
int rc;
int foreign = 0;
again:
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" '"DIMPORT"; "DZFS
" get -H -o value lustre:magic "DSSNAME"' 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st), PZFS(st), PSSNAME(si, st));
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to check snapshot "
"on mdt0: %s\n", strerror(errno));
return -errno;
}
if (snapshot_fgets(fp, buf, strlen(SNAPSHOT_MAGIC) + 1) == NULL) {
if (foreign || !st->st_fhost) {
rc = -EINVAL;
} else {
foreign = 1;
pclose(fp);
goto again;
}
} else if (strcmp(buf, SNAPSHOT_MAGIC) == 0) {
rc = 0;
} else {
fprintf(stderr,
"The target %s is not Lustre snapshot "
"or it does not exists\n", si->si_ssname);
rc = -EPERM;
}
pclose(fp);
return rc;
}
static int target_is_mounted(struct snapshot_instance *si,
struct snapshot_target *st, const char *ssname)
{
char buf[MAX_BUF_SIZE];
char fullname[MAX_BUF_SIZE];
FILE *fp;
char *ptr;
int rc = 0;
int foreign = 0;
again:
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" 'mount' 2>/dev/null",
PRSH(si, st, foreign));
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to check target mount: %s\n",
strerror(errno));
return -errno;
}
memset(fullname, 0, sizeof(fullname));
if (ssname)
snprintf(fullname, sizeof(fullname) - 1,
DFSNAME"@%s on ",
PFSNAME(st), ssname);
else
snprintf(fullname, sizeof(fullname) - 1,
DFSNAME" on ",
PFSNAME(st));
while (snapshot_fgets(fp, buf, sizeof(buf)) != NULL) {
ptr = strstr(buf, fullname);
if (!ptr)
continue;
ptr += strlen(fullname) + 1; /* mount point */
if (ptr >= buf + strlen(buf))
continue;
ptr = strstr(ptr, "type lustre");
if (ptr) {
rc = 1;
break;
}
}
pclose(fp);
if (!rc && !foreign && st->st_fhost) {
foreign = 1;
goto again;
}
return rc;
}
static int snapshot_get_fsname(struct snapshot_instance *si,
char *fsname, int fslen)
{
struct snapshot_target *st = si->si_mdt0;
char buf[MAX_BUF_SIZE * 3];
FILE *fp;
int rc;
int foreign = 0;
again:
rc = 0;
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" '"DIMPORT"; "DZFS
" get -H -o value lustre:fsname "DSSNAME"' 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st), PZFS(st),
PSSNAME(si, st));
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to get fsname: %s\n",
strerror(errno));
return -errno;
}
if (snapshot_fgets(fp, fsname, fslen) == NULL)
rc = -EINVAL;
pclose(fp);
if (rc && !foreign && st->st_fhost) {
foreign = 1;
goto again;
}
return rc;
}
static int snapshot_get_mgsnode(struct snapshot_instance *si,
char *node, int size)
{
char buf[MAX_BUF_SIZE * 2];
struct snapshot_target *st;
FILE *fp;
int rc;
int foreign = 0;
st = list_first_entry(&si->si_osts_list, struct snapshot_target,
st_list);
again:
rc = 0;
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" '"DZFS" get -H -o value lustre:mgsnode "DFSNAME"'"
" 2>/dev/null", PRSH(si, st, foreign), PZFS(st),
PFSNAME(st));
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to get mgsnode: %s\n",
strerror(errno));
return -errno;
}
if (snapshot_fgets(fp, node, size) == NULL)
rc = -EINVAL;
pclose(fp);
if (rc && !foreign && st->st_fhost) {
foreign = 1;
goto again;
}
return rc;
}
static int snapshot_exists_check(struct snapshot_instance *si,
struct snapshot_target *st)
{
char buf[MAX_BUF_SIZE * 2];
FILE *fp;
int rc = 0;
int foreign = 0;
again:
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" '"DZFS" list "DSSNAME"' 2>/dev/null",
PRSH(si, st, foreign), PZFS(st), PSSNAME(si, st));
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to create check: %s\n",
strerror(errno));
return -errno;
}
if (snapshot_fgets(fp, buf, sizeof(buf)) != NULL)
rc = -EEXIST;
pclose(fp);
if (rc != -EEXIST && !foreign && st->st_fhost) {
foreign = 1;
goto again;
}
return rc;
}
static int snapshot_general_check(struct snapshot_instance *si)
{
return mdt0_is_lustre_snapshot(si);
}
static void snapshot_create_usage(void)
{
fprintf(stderr,
"Create snapshot for the given filesystem.\n"
"Usage:\n"
"snapshot_create [-b | --barrier [on | off]] "
"[-c | --comment comment] "
"<-F | --fsname fsname> "
"[-h | --help] <-n | --name ssname> "
"[-r | --rsh remote_shell]"
"[-t | --timeout timeout]\n"
"Options:\n"
"-b: set write barrier before creating snapshot, "
"the default value is 'on'.\n"
"-c: describe what the snapshot is for, and so on.\n"
"-F: the filesystem name.\n"
"-h: for help information.\n"
"-n: the snapshot's name.\n"
"-r: the remote shell used for communication with remote "
"target, the default value is 'ssh'.\n"
"-t: the life cycle (seconds) for write barrier, "
"the default value is %d seconds.\n",
BARRIER_TIMEOUT_DEFAULT);
}
static int snapshot_create_check(struct snapshot_instance *si)
{
int rc;
rc = snapshot_exists_check(si, si->si_mdt0);
if (rc == -EEXIST)
fprintf(stderr, "The snapshot %s exists\n", si->si_ssname);
return rc;
}
static int snapshot_inherit_prop(struct snapshot_instance *si,
struct snapshot_target *st,
char *cmd, int size)
{
char buf[MAX_BUF_SIZE * 3];
FILE *fp;
int len = 0;
int rc = 0;
int foreign = 0;
again:
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" \""DIMPORT"; "DZFS
" get all "DFSNAME" | grep lustre: | grep local$ | "
"awk '{ \\$1=\\\"\\\"; \\$NF=\\\"\\\"; print \\$0 }' | "
"sed -e 's/^ //'\" 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st), PZFS(st), PFSNAME(st));
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to list one: %s\n",
strerror(errno));
return -errno;
}
while (snapshot_fgets(fp, buf, MAX_BUF_SIZE) != NULL) {
char *ptr;
char *end;
foreign = 1;
if (strncmp(buf, "lustre:fsname",
strlen("lustre:fsname")) == 0)
continue;
if (strncmp(buf, "lustre:magic",
strlen("lustre:magic")) == 0)
continue;
if (strncmp(buf, "lustre:ctime",
strlen("lustre:ctime")) == 0)
continue;
if (strncmp(buf, "lustre:mtime",
strlen("lustre:mtime")) == 0)
continue;
if (strncmp(buf, "lustre:comment",
strlen("lustre:comment")) == 0)
continue;
if (strncmp(buf, "lustre:svname",
strlen("lustre:svname")) == 0)
continue;
if (strncmp(buf, "lustre:mgsnode",
strlen("lustre:mgsnode")) == 0)
continue;
ptr = strchr(buf, ' ');
if (!ptr) {
ptr = strchr(buf, '\t');
if (!ptr)
continue;
}
*ptr = '\0';
ptr++;
while (*ptr == ' ' || *ptr == '\t')
ptr++;
if (*ptr == '\0')
continue;
end = strchr(ptr, ' ');
if (!end)
end = strchr(buf, '\t');
if (end)
*end = '\0';
rc = scnprintf(cmd + len, size - len - 1,
"-o %s=\"%s\" ", buf, ptr);
if (rc <= 0) {
pclose(fp);
return -EOVERFLOW;
}
len += rc;
}
pclose(fp);
if (!foreign && st->st_fhost) {
foreign = 1;
goto again;
}
return len;
}
static int __snapshot_create(struct snapshot_instance *si,
struct list_head *head, const char *fsname,
const char *mgsnode, __u64 xtime)
{
struct snapshot_target *st;
pid_t pid;
int rc;
list_for_each_entry(st, head, st_list) {
st->st_status = 0;
st->st_ignored = 0;
st->st_pid = 0;
pid = fork();
if (pid < 0) {
SNAPSHOT_ADD_LOG(si, "Can't fork for create snapshot "
"(%s@%s <%s>) on the target "
"(%s(%s):%x:%d): %s\n",
fsname, si->si_ssname,
si->si_comment, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
strerror(errno));
return pid;
}
/* child */
if (pid == 0) {
char cmd[MAX_BUF_SIZE];
int len;
int foreign = 0;
again:
memset(cmd, 0, sizeof(cmd));
len = scnprintf(cmd, sizeof(cmd) - 1,
DRSH" '"DZFS" snapshot "
"-o lustre:fsname=%s "
"-o lustre:magic=%s "
"-o lustre:ctime=%llu "
"-o lustre:mtime=%llu ",
PRSH(si, st, foreign), PZFS(st),
fsname, SNAPSHOT_MAGIC,
(unsigned long long)xtime,
(unsigned long long)xtime);
if (len <= 0)
exit(-EOVERFLOW);
if (si->si_comment) {
rc = scnprintf(cmd + len, sizeof(cmd) - len - 1,
"-o lustre:comment=\"%s\" ",
si->si_comment);
if (rc <= 0)
exit(-EOVERFLOW);
len += rc;
}
/* Make the inherited properties as local ones,
* then even if others changed (or removed) the
* property of the parent dataset, the snapshot
* will not be affected. */
rc = snapshot_inherit_prop(si, st, cmd + len,
MAX_BUF_SIZE - len - 1);
if (rc < 0) {
SNAPSHOT_ADD_LOG(si, "Can't filter property on "
"target (%s:%x:%d): rc = %d\n",
foreign ? st->st_fhost :
st->st_host, st->st_role,
st->st_index, rc);
exit(rc);
}
len += rc;
if (st->st_role & SR_OST)
rc = snprintf(cmd + len, sizeof(cmd) - len - 1,
"-o lustre:svname=%s-OST%04x "
"-o lustre:mgsnode=%s "DSSNAME"'"
" 2>/dev/null",
fsname, st->st_index, mgsnode,
PSSNAME(si, st));
else if (!(st->st_role & SR_MGS) ||
/* MGS is on MDT0 */
si->si_mdt0 == si->si_mgs)
rc = snprintf(cmd + len, sizeof(cmd) - len - 1,
"-o lustre:svname=%s-MDT%04x "
"-o lustre:mgsnode=%s "DSSNAME"'"
" 2>/dev/null",
fsname, st->st_index, mgsnode,
PSSNAME(si, st));
else
/* separated MGS */
rc = snprintf(cmd + len, sizeof(cmd) - len - 1,
DSSNAME"' 2>/dev/null",
PSSNAME(si, st));
if (rc <= 0)
exit(-EOVERFLOW);
rc = snapshot_exec(cmd);
if (rc) {
SNAPSHOT_ADD_LOG(si, "Can't execute \"%s\" on "
"target (%s:%x:%d): rc = %d\n",
cmd, foreign ? st->st_fhost :
st->st_host, st->st_role,
st->st_index, rc);
if (!foreign && st->st_fhost) {
foreign = 1;
goto again;
}
}
exit(rc);
} /* end of child */
/* parent continue to run more snapshot commands in parallel. */
st->st_pid = pid;
}
return 0;
}
static int __snapshot_destroy(struct snapshot_instance *si,
struct list_head *head);
static int snapshot_create(struct snapshot_instance *si)
{
char *__argv[4] = { NULL };
char buf[MAX_BUF_SIZE];
struct timeval tv;
char new_fsname[9];
int rc = 0;
int rc1 = 0;
int rc2 = 0;
rc = snapshot_create_check(si);
if (rc)
return rc;
rc = gettimeofday(&tv, NULL);
if (rc)
return rc;
srandom(tv.tv_usec);
snprintf(new_fsname, sizeof(new_fsname), "%08x", (__u32)random());
rc = snapshot_get_mgsnode(si, buf, sizeof(buf));
if (rc)
return rc;
__argv[1] = si->si_fsname;
/* 1. Get barrier */
if (si->si_barrier) {
char tbuf[8];
memset(tbuf, 0, sizeof(tbuf));
snprintf(tbuf, 7, "%u", si->si_timeout);
__argv[0] = "barrier_freeze";
__argv[2] = tbuf;
rc = jt_barrier_freeze(3, __argv);
if (rc) {
SNAPSHOT_ADD_LOG(si, "Can't set barrier within %u "
"seconds on %s: rc = %d\n",
si->si_timeout, si->si_fsname, rc);
return rc;
}
}
/* 2. Fork config llog on MGS */
__argv[0] = "lcfg_fork";
__argv[2] = new_fsname;
rc = jt_lcfg_fork(3, __argv);
if (rc) {
SNAPSHOT_ADD_LOG(si, "Can't fork config log for create "
"snapshot %s from %s to %s: rc = %d\n",
si->si_ssname, si->si_fsname, new_fsname, rc);
goto out;
}
/* 3.1 Create snapshot on every MDT */
rc = __snapshot_create(si, &si->si_mdts_list, new_fsname, buf,
tv.tv_sec);
if (!rc)
/* 3.2 Create snapshot on every OST */
rc = __snapshot_create(si, &si->si_osts_list, new_fsname, buf,
tv.tv_sec);
/* 4. Wait for all children, even though part of them maybe failed */
snapshot_wait(si, &rc1);
out:
/* 5. Put barrier */
if (si->si_barrier) {
if (!rc && !rc1) {
struct barrier_ctl bc;
rc = __jt_barrier_stat(__argv[1], &bc);
if (rc) {
SNAPSHOT_ADD_LOG(si, "Can't get barrier status "
"on %s: rc = %d\n",
si->si_fsname, rc);
} else if (bc.bc_status != BS_FROZEN ||
bc.bc_timeout <= 0) {
SNAPSHOT_ADD_LOG(si, "The barrier expired "
"on %s\n", si->si_fsname);
rc = -ETIMEDOUT;
}
}
__argv[0] = "barrier_thaw";
rc2 = jt_barrier_thaw(2, __argv);
if (rc2)
SNAPSHOT_ADD_LOG(si, "Can't release barrier on %s: "
"rc = %d\n", si->si_fsname, rc2);
}
/* cleanup */
if (rc || rc1) {
si->si_force = true;
__snapshot_destroy(si, &si->si_osts_list);
__snapshot_destroy(si, &si->si_mdts_list);
snapshot_wait(si, &rc2);
__argv[0] = "lcfg_erase";
__argv[1] = new_fsname;
__argv[2] = "-q";
jt_lcfg_erase(3, __argv);
}
return rc ? rc : (rc1 ? rc1 : rc2);
}
int jt_snapshot_create(int argc, char **argv)
{
struct snapshot_instance *si;
struct option long_opts[] = {
{ .val = 'b', .name = "barrier", .has_arg = optional_argument },
{ .val = 'c', .name = "comment", .has_arg = required_argument },
{ .val = 'F', .name = "fsname", .has_arg = required_argument },
{ .val = 'h', .name = "help", .has_arg = no_argument },
{ .val = 'n', .name = "name", .has_arg = required_argument },
{ .val = 'r', .name = "rsh", .has_arg = required_argument },
{ .val = 't', .name = "timeout", .has_arg = required_argument },
{ .name = NULL } };
int rc = 0;
si = snapshot_init(argc, argv, long_opts, "b::c:F:hn:r:t:",
snapshot_create_usage, LOCK_EX, &rc);
if (!si)
return rc;
if (!si->si_ssname) {
fprintf(stderr,
"Miss the snapshot name to be created\n");
snapshot_create_usage();
snapshot_fini(si);
return -EINVAL;
}
rc = snapshot_create(si);
if (rc) {
fprintf(stderr,
"Can't create the snapshot %s\n", si->si_ssname);
SNAPSHOT_ADD_LOG(si, "Can't create snapshot %s with "
"comment <%s> barrier <%s>, timeout "
"<%d>: %d\n",
si->si_ssname, si->si_comment,
si->si_barrier ? "enable" : "disable",
si->si_barrier ? si->si_timeout : -1, rc);
} else {
SNAPSHOT_ADD_LOG(si, "Create snapshot %s successfully "
"with comment <%s>, barrier <%s>, "
"timeout <%d>\n",
si->si_ssname, si->si_comment,
si->si_barrier ? "enable" : "disable",
si->si_barrier ? si->si_timeout : -1);
}
snapshot_fini(si);
return rc;
}
static void snapshot_destroy_usage(void)
{
fprintf(stderr,
"Destroy the specified snapshot.\n"
"Usage:\n"
"snapshot_destroy [-f | --force] "
"<-F | --fsname fsname> [-h | --help] "
"<-n | --name ssname> "
"[-r | --rsh remote_shell]\n"
"Options:\n"
"-f: destroy the snapshot by force.\n"
"-F: the filesystem name.\n"
"-h: for help information.\n"
"-n: the snapshot's name.\n"
"-r: the remote shell used for communication with remote "
"target, the default value is 'ssh'.\n");
}
static int snapshot_destroy_check(struct snapshot_instance *si)
{
struct list_head *head = &si->si_osts_list;
struct snapshot_target *st;
pid_t pid;
int rc = 0;
again1:
list_for_each_entry(st, head, st_list) {
st->st_status = 0;
st->st_ignored = 0;
st->st_pid = 0;
pid = fork();
if (pid < 0) {
SNAPSHOT_ADD_LOG(si, "Can't fork for check snapshot "
"%s on the target (%s(%s):%x:%d): "
"%s\n",
si->si_ssname, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
strerror(errno));
return pid;
}
/* child */
if (pid == 0) {
rc = snapshot_exists_check(si, st);
if (!rc)
/* The snapshot piece does not exist */
exit(-ESRCH);
exit(rc == -EEXIST ? 0: rc);
} /* end of child */
/* parent continue to run more snapshot commands in parallel. */
st->st_pid = pid;
}
if (head == &si->si_osts_list) {
head = &si->si_mdts_list;
goto again1;
}
snapshot_wait(si, &rc);
if (rc)
return rc;
head = &si->si_osts_list;
again2:
list_for_each_entry(st, head, st_list) {
if (st->st_ignored && !si->si_force) {
char name[8];
snapshot_role2name(name, st->st_role, st->st_index);
fprintf(stderr,
"Miss snapshot piece on the %s. Use '-f' "
"option if want to destroy it by force.\n",
name);
return -ENOENT;
}
}
if (head == &si->si_osts_list) {
head = &si->si_mdts_list;
goto again2;
}
if (!si->si_force)
rc = snapshot_general_check(si);
return rc;
}
static int __snapshot_destroy(struct snapshot_instance *si,
struct list_head *head)
{
struct snapshot_target *st;
pid_t pid;
int rc;
list_for_each_entry(st, head, st_list) {
if (st->st_ignored)
continue;
st->st_status = 0;
st->st_pid = 0;
pid = fork();
if (pid < 0) {
SNAPSHOT_ADD_LOG(si, "Can't fork for destroy snapshot "
"%s on the target (%s(%s):%x:%d): "
"%s\n",
si->si_ssname, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
strerror(errno));
return pid;
}
/* child */
if (pid == 0) {
char cmd[MAX_BUF_SIZE * 2];
int foreign = 0;
again:
memset(cmd, 0, sizeof(cmd));
if (si->si_force)
snprintf(cmd, sizeof(cmd) - 1,
DRSH" 'umount -f "DSSNAME
" > /dev/null 2>&1; "DZFS
" destroy -f "DSSNAME"'"
" 2>/dev/null",
PRSH(si, st, foreign),
PSSNAME(si, st), PZFS(st),
PSSNAME(si, st));
else
snprintf(cmd, sizeof(cmd) - 1,
DRSH" '"DZFS" destroy "DSSNAME"'"
" 2>/dev/null",
PRSH(si, st, foreign), PZFS(st),
PSSNAME(si, st));
rc = snapshot_exec(cmd);
if (rc) {
SNAPSHOT_ADD_LOG(si, "Can't execute \"%s\" on "
"target (%s:%x:%d): rc = %d\n",
cmd, foreign ? st->st_fhost :
st->st_host, st->st_role,
st->st_index, rc);
if (!foreign && st->st_fhost) {
foreign = 1;
goto again;
}
}
exit(rc);
} /* end of child */
/* parent continue to run more snapshot commands in parallel. */
st->st_pid = pid;
}
return 0;
}
static int snapshot_destroy(struct snapshot_instance *si)
{
char fsname[9];
int rc = 0;
int rc1 = 0;
int rc2 = 0;
int rc3 = 0;
rc = snapshot_destroy_check(si);
if (rc)
return rc;
rc = snapshot_get_fsname(si, fsname, sizeof(fsname));
if (rc)
return rc;
/* 1.1 Destroy snapshot on every OST */
rc = __snapshot_destroy(si, &si->si_osts_list);
if (!si->si_force) {
if (rc)
return rc;
__snapshot_wait(si, &si->si_osts_list, &rc);
if (rc)
return rc;
}
/* 1.2 Destroy snapshot on every MDT */
rc1 = __snapshot_destroy(si, &si->si_mdts_list);
/* 2 Wait for all children, even though part of them maybe failed */
snapshot_wait(si, &rc2);
if (rc2 == -ENOENT && si->si_force)
rc2 = 0;
/* 3. Erase config llog from MGS */
if ((!rc && !rc1 && !rc2) || si->si_force) {
char *__argv[4] = { NULL };
__argv[0] = "lcfg_erase";
__argv[1] = fsname;
__argv[2] = "-q";
rc3 = jt_lcfg_erase(3, __argv);
if (rc3 && errno == ENOENT)
rc3 = 0;
if (rc3)
SNAPSHOT_ADD_LOG(si,
"%s: cannot erase config for snapshot '%s' destroy: rc = %d\n",
fsname, si->si_ssname, rc3);
}
return rc ? rc : (rc1 ? rc1 : (rc2 ? rc2 : rc3));
}
int jt_snapshot_destroy(int argc, char **argv)
{
struct snapshot_instance *si;
struct option long_opts[] = {
{ .val = 'f', .name = "force", .has_arg = no_argument },
{ .val = 'F', .name = "fsname", .has_arg = required_argument },
{ .val = 'h', .name = "help", .has_arg = no_argument },
{ .val = 'n', .name = "name", .has_arg = required_argument },
{ .val = 'r', .name = "rsh", .has_arg = required_argument },
{ .name = NULL } };
int rc = 0;
si = snapshot_init(argc, argv, long_opts, "fF:hn:r:",
snapshot_destroy_usage, LOCK_EX, &rc);
if (!si)
return rc;
if (!si->si_ssname) {
fprintf(stderr,
"Miss the snapshot name to be destroyed\n");
snapshot_destroy_usage();
snapshot_fini(si);
return -EINVAL;
}
rc = snapshot_destroy(si);
if (rc) {
fprintf(stderr,
"Can't destroy the snapshot %s\n", si->si_ssname);
SNAPSHOT_ADD_LOG(si, "Can't destroy snapshot %s with "
"force <%s>: %d\n", si->si_ssname,
si->si_force ? "enable" : "disable", rc);
} else {
SNAPSHOT_ADD_LOG(si, "Destroy snapshot %s successfully "
"with force <%s>\n", si->si_ssname,
si->si_force ? "enable" : "disable");
}
snapshot_fini(si);
return rc;
}
static void snapshot_modify_usage(void)
{
fprintf(stderr,
"Change the specified snapshot's name and/or comment.\n"
"Usage:\n"
"snapshot_modify [-c | --comment comment] "
"<-F | --fsname fsname> [-h | --help] "
"<-n | --name ssname> [-N | --new new_ssname] "
"[-r | --rsh remote_shell]\n"
"Options:\n"
"-c: update the snapshot's comment.\n"
"-F: the filesystem name.\n"
"-h: for help information.\n"
"-n: the snapshot's name.\n"
"-N: rename the snapshot's name as the new_ssname.\n"
"-r: the remote shell used for communication with remote "
"target, the default value is 'ssh'.\n");
}
static int snapshot_modify_check(struct snapshot_instance *si)
{
int rc;
if (si->si_new_ssname &&
strcmp(si->si_ssname, si->si_new_ssname) == 0) {
fprintf(stderr, "The new snapshot's name is the same as "
"the old one %s %s.\n",
si->si_ssname, si->si_new_ssname);
return -EPERM;
}
if (!si->si_new_ssname && !si->si_comment) {
fprintf(stderr, "Miss options, nothing to be changed.\n");
return -EINVAL;
}
rc = mdt0_is_lustre_snapshot(si);
if (!rc && si->si_new_ssname) {
rc = target_is_mounted(si, si->si_mdt0, si->si_ssname);
if (rc > 0) {
fprintf(stderr,
"snapshot %s is mounted, can't be renamed.\n",
si->si_ssname);
rc = -EBUSY;
}
}
return rc;
}
static int __snapshot_modify(struct snapshot_instance *si,
struct list_head *head, __u64 xtime)
{
struct snapshot_target *st;
pid_t pid;
int rc;
list_for_each_entry(st, head, st_list) {
st->st_status = 0;
st->st_ignored = 0;
st->st_pid = 0;
pid = fork();
if (pid < 0) {
SNAPSHOT_ADD_LOG(si, "Can't fork for modify snapshot "
"(%s|%s, <%s>) on the target "
"(%s(%s):%x:%d): %s\n",
si->si_ssname, si->si_new_ssname,
si->si_comment, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
strerror(errno));
return pid;
}
/* child */
if (pid == 0) {
char cmd[MAX_BUF_SIZE * 5];
int foreign = 0;
again:
memset(cmd, 0, sizeof(cmd));
if (si->si_new_ssname && si->si_comment)
snprintf(cmd, sizeof(cmd) - 1,
DRSH" '"DIMPORT"; "DZFS" rename "
DSSNAME" "DSSNAME" && "DZFS
" set lustre:comment=\"%s\" "DSSNAME
" && "DZFS
" set lustre:mtime=%llu "DSSNAME"'"
" 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st),
PZFS(st), PSSNAME(si, st),
PSS_NEW(si, st), PZFS(st),
si->si_comment, PSS_NEW(si, st),
PZFS(st), (unsigned long long)xtime,
PSS_NEW(si, st));
else if (si->si_new_ssname)
snprintf(cmd, sizeof(cmd) - 1,
DRSH" '"DIMPORT"; "DZFS
" rename "DSSNAME" "DSSNAME" && "DZFS
" set lustre:mtime=%llu "DSSNAME"'"
" 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st),
PZFS(st), PSSNAME(si, st),
PSS_NEW(si, st), PZFS(st),
(unsigned long long)xtime,
PSS_NEW(si, st));
else if (si->si_comment)
snprintf(cmd, sizeof(cmd) - 1,
DRSH" '"DIMPORT"; "DZFS
" set lustre:comment=\"%s\" "DSSNAME
" && "DZFS " set lustre:mtime=%llu "
DSSNAME"' 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st),
PZFS(st), si->si_comment,
PSSNAME(si, st), PZFS(st),
(unsigned long long)xtime,
PSSNAME(si, st));
else
exit(-EINVAL);
rc = snapshot_exec(cmd);
if (rc) {
SNAPSHOT_ADD_LOG(si, "Can't execute \"%s\" on "
"target (%s:%x:%d): rc = %d\n",
cmd, foreign ? st->st_fhost :
st->st_host, st->st_role,
st->st_index, rc);
if (!foreign && st->st_fhost) {
foreign = 1;
goto again;
}
}
exit(rc);
} /* end of child */
/* parent continue to run more snapshot commands in parallel. */
st->st_pid = pid;
}
return 0;
}
static int snapshot_modify(struct snapshot_instance *si)
{
time_t tt;
int rc = 0;
int rc1 = 0;
rc = snapshot_modify_check(si);
if (rc)
return rc;
time(&tt);
/* Modify snapshot on every MDT */
rc = __snapshot_modify(si, &si->si_mdts_list, (__u64)tt);
if (!rc)
/* Modify snapshot on every OST */
rc = __snapshot_modify(si, &si->si_osts_list, (__u64)tt);
/* Wait for all children, even though part of them maybe failed */
snapshot_wait(si, &rc1);
return rc ? rc : rc1;
}
int jt_snapshot_modify(int argc, char **argv)
{
struct snapshot_instance *si;
struct option long_opts[] = {
{ .val = 'c', .name = "comment", .has_arg = required_argument },
{ .val = 'F', .name = "fsname", .has_arg = required_argument },
{ .val = 'h', .name = "help", .has_arg = no_argument },
{ .val = 'n', .name = "name", .has_arg = required_argument },
{ .val = 'N', .name = "new", .has_arg = required_argument },
{ .val = 'r', .name = "rsh", .has_arg = required_argument },
{ .name = NULL } };
int rc = 0;
si = snapshot_init(argc, argv, long_opts, "c:F:hn:N:r:",
snapshot_modify_usage, LOCK_EX, &rc);
if (!si)
return rc;
if (!si->si_ssname) {
fprintf(stderr,
"Miss the snapshot name to be modified\n");
snapshot_modify_usage();
snapshot_fini(si);
return -EINVAL;
}
rc = snapshot_modify(si);
if (rc) {
fprintf(stderr,
"Can't modify the snapshot %s\n", si->si_ssname);
SNAPSHOT_ADD_LOG(si, "Can't modify snapshot %s with "
"name <%s>, comment <%s>: %d\n",
si->si_ssname, si->si_new_ssname,
si->si_comment, rc);
} else {
SNAPSHOT_ADD_LOG(si, "Modify snapshot %s successfully "
"with name <%s>, comment <%s>\n",
si->si_ssname, si->si_new_ssname,
si->si_comment);
}
snapshot_fini(si);
return rc;
}
static void snapshot_list_usage(void)
{
fprintf(stderr,
"List the specified snapshot or all snapshots.\n"
"Usage:\n"
"snapshot_list [-d | --detail] "
"<-F | --fsname fsname> [-h | --help] "
"[-n | --name ssname] [-r | --rsh remote_shell]\n"
"Options:\n"
"-d: list every piece for the specified snapshot.\n"
"-F: the filesystem name.\n"
"-h: for help information.\n"
"-n: the snapshot's name.\n"
"-r: the remote shell used for communication with remote "
"target, the default value is 'ssh'.\n");
}
static int snapshot_list_one(struct snapshot_instance *si,
struct snapshot_target *st)
{
char buf[MAX_BUF_SIZE * 3];
FILE *fp;
int rc;
int foreign = 0;
again:
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" \""DIMPORT"; "DZFS
" get all "DSSNAME" | grep lustre: | grep local$ | "
"awk '{ \\$1=\\\"\\\"; \\$NF=\\\"\\\"; print \\$0 }' | "
"sed -e 's/^ //'\" 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st), PZFS(st),
PSSNAME(si, st));
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to list one: %s\n",
strerror(errno));
return -errno;
}
if (si->si_detail && !foreign) {
char name[8];
snapshot_role2name(name, st->st_role, st->st_index);
printf("\nsnapshot_role: %s\n", name);
}
while (snapshot_fgets(fp, buf, MAX_BUF_SIZE) != NULL) {
__u64 xtime;
char *ptr;
foreign = 1;
if (strncmp(buf, "lustre:fsname",
strlen("lustre:fsname")) == 0) {
ptr = snapshot_first_skip_blank(buf);
if (ptr)
printf("snapshot_fsname: %s\n", ptr);
continue;
}
if (strncmp(buf, "lustre:comment",
strlen("lustre:comment")) == 0) {
ptr = snapshot_first_skip_blank(buf);
if (ptr)
printf("comment: %s\n", ptr);
continue;
}
if (strncmp(buf, "lustre:ctime",
strlen("lustre:ctime")) == 0) {
ptr = snapshot_first_skip_blank(buf);
if (ptr) {
xtime = (__u64)strtoull(ptr, NULL, 10);
printf("create_time: %s",
ctime((time_t *)&xtime));
}
continue;
}
if (strncmp(buf, "lustre:mtime",
strlen("lustre:mtime")) == 0) {
ptr = snapshot_first_skip_blank(buf);
if (ptr) {
xtime = (__u64)strtoull(ptr, NULL, 10);
printf("modify_time: %s",
ctime((time_t *)&xtime));
}
continue;
}
}
pclose(fp);
if (!foreign && st->st_fhost) {
foreign = 1;
goto again;
}
rc = target_is_mounted(si, st, si->si_ssname);
if (rc < 0)
printf("status: unknown\n");
else if (!rc)
printf("status: not mount\n");
else
printf("status: mounted\n");
return rc;
}
static int __snapshot_list(struct snapshot_instance *si,
struct list_head *head)
{
struct snapshot_target *st;
int rc = 0;
list_for_each_entry(st, head, st_list) {
int rc1;
rc1 = snapshot_list_one(si, st);
if (rc1 < 0 || rc >= 0)
rc = rc1;
}
return rc;
}
static int snapshot_list(struct snapshot_instance *si)
{
int rc = 0;
rc = snapshot_general_check(si);
if (rc)
return rc;
printf("\nfilesystem_name: %s\nsnapshot_name: %s\n",
si->si_fsname, si->si_ssname);
if (!si->si_detail) {
rc = snapshot_list_one(si, si->si_mdt0);
} else {
int rc1;
rc = __snapshot_list(si, &si->si_mdts_list);
rc1 = __snapshot_list(si, &si->si_osts_list);
if (rc >= 0)
rc = rc1;
}
return rc < 0 ? rc : 0;
}
static int snapshot_list_all(struct snapshot_instance *si)
{
struct snapshot_target *st = si->si_mdt0;
struct list_sub_item {
struct list_head lsi_list;
char lsi_ssname[];
};
struct list_head list_sub_items;
struct list_sub_item *lsi;
char buf[MAX_BUF_SIZE * 2];
FILE *fp;
int rc = 0;
int foreign = 0;
INIT_LIST_HEAD(&list_sub_items);
again:
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf) - 1,
DRSH" \""DZFS" get -H -r lustre:magic "DFSNAME
" | grep %s | awk '{ print \\$1 }' | cut -d@ -f2\" "
" 2>/dev/null",
PRSH(si, st, foreign), PZFS(st), PFSNAME(st),
SNAPSHOT_MAGIC);
fp = popen(buf, "r");
if (!fp) {
SNAPSHOT_ADD_LOG(si, "Popen fail to list ssnames: %s\n",
strerror(errno));
return -errno;
}
while (snapshot_fgets(fp, buf, MAX_BUF_SIZE) != NULL) {
int len = strlen(buf);
foreign = 1;
lsi = malloc(len + 1 + sizeof(struct list_sub_item));
if (!lsi) {
SNAPSHOT_ADD_LOG(si, "NOT enough memory\n");
rc = -ENOMEM;
break;
}
memcpy(lsi->lsi_ssname, buf, len + 1);
list_add(&lsi->lsi_list, &list_sub_items);
}
pclose(fp);
if (!foreign && st->st_fhost) {
foreign = 1;
goto again;
}
while (!list_empty(&list_sub_items)) {
lsi = list_first_entry(&list_sub_items,
struct list_sub_item, lsi_list);
list_del(&lsi->lsi_list);
if (!rc) {
si->si_ssname = lsi->lsi_ssname;
rc = snapshot_list(si);
si->si_ssname = NULL;
}
free(lsi);
}
return rc;
}
int jt_snapshot_list(int argc, char **argv)
{
struct snapshot_instance *si;
struct option long_opts[] = {
{ .val = 'd', .name = "detail", .has_arg = no_argument },
{ .val = 'F', .name = "fsname", .has_arg = required_argument },
{ .val = 'h', .name = "help", .has_arg = no_argument },
{ .val = 'n', .name = "name", .has_arg = required_argument },
{ .val = 'r', .name = "rsh", .has_arg = required_argument },
{ .name = NULL } };
int rc = 0;
si = snapshot_init(argc, argv, long_opts, "dF:hn:r:",
snapshot_list_usage, LOCK_SH, &rc);
if (!si)
return rc;
if (si->si_ssname)
rc = snapshot_list(si);
else
rc = snapshot_list_all(si);
if (rc) {
fprintf(stderr,
"Can't list the snapshot %s\n", si->si_ssname);
SNAPSHOT_ADD_LOG(si, "Can't list snapshot %s with detail "
"<%s>: %d\n", si->si_ssname,
si->si_detail ? "yes" : "no", rc);
}
snapshot_fini(si);
return rc;
}
static void snapshot_mount_usage(void)
{
fprintf(stderr,
"Mount the specified snapshot.\n"
"Usage:\n"
"snapshot_mount <-F | --fsname fsname> [-h | --help] "
"<-n | --name ssname> "
"[-r | --rsh remote_shell]\n"
"Options:\n"
"-F: the filesystem name.\n"
"-h: for help information.\n"
"-n: the snapshot's name.\n"
"-r: the remote shell used for communication with remote "
"target, the default value is 'ssh'.\n");
}
static int snapshot_mount_check(struct snapshot_instance *si, char *fsname,
int fslen, bool *mgs_running)
{
int rc;
rc = mdt0_is_lustre_snapshot(si);
if (rc)
return rc;
rc = snapshot_get_fsname(si, fsname, fslen);
if (rc < 0)
return rc;
rc = target_is_mounted(si, si->si_mgs, NULL);
if (rc > 0) {
*mgs_running = true;
rc = 0;
}
return rc;
}
static int snapshot_mount_target(struct snapshot_instance *si,
struct snapshot_target *st, const char *optstr)
{
char cmd[MAX_BUF_SIZE * 2];
char name[8];
int rc;
int foreign = 0;
rc = target_is_mounted(si, st, si->si_ssname);
if (rc < 0)
return rc;
if (rc > 0)
return -ESRCH;
memset(name, 0, sizeof(name));
snapshot_role2name(name, st->st_role, st->st_index);
again:
memset(cmd, 0, sizeof(cmd));
snprintf(cmd, sizeof(cmd) - 1,
DRSH" '"DIMPORT"; mkdir -p /mnt/%s_%s && mount -t lustre "
"-o rdonly_dev%s "DSSNAME" /mnt/%s_%s' 2>/dev/null",
PRSH(si, st, foreign), PIMPORT(st), si->si_ssname, name,
st != si->si_mdt0 ? "" : optstr, PSSNAME(si, st),
si->si_ssname, name);
rc = snapshot_exec(cmd);
if (rc) {
SNAPSHOT_ADD_LOG(si, "Can't execute \"%s\" on the target "
"(%s:%x:%d): rc = %d\n",
cmd, foreign ? st->st_fhost : st->st_host,
st->st_role, st->st_index, rc);
if (!foreign && st->st_fhost) {
foreign = 1;
goto again;
}
}
return rc;
}
static int __snapshot_mount(struct snapshot_instance *si,
struct list_head *head)
{
struct snapshot_target *st;
pid_t pid;
int rc;
list_for_each_entry(st, head, st_list) {
if (st == si->si_mgs || st == si->si_mdt0)
continue;
st->st_status = 0;
st->st_ignored = 0;
st->st_pid = 0;
pid = fork();
if (pid < 0) {
SNAPSHOT_ADD_LOG(si, "Can't fork for mount snapshot "
"%s on target (%s(%s):%x:%d): %s\n",
si->si_ssname, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
strerror(errno));
return pid;
}
/* child */
if (pid == 0) {
rc = snapshot_mount_target(si, st, "");
exit(rc);
}
/* parent continue to run more snapshot commands in parallel. */
st->st_pid = pid;
}
return 0;
}
static int __snapshot_umount(struct snapshot_instance *si,
struct list_head *head);
static int snapshot_mount(struct snapshot_instance *si)
{
struct snapshot_target *st;
int needed = 0;
int failed = 0;
int rc = 0;
int rc1 = 0;
char fsname[9];
bool mdt0_mounted = false;
bool mgs_running = false;
rc = snapshot_mount_check(si, fsname, sizeof(fsname), &mgs_running);
if (rc < 0) {
fprintf(stderr,
"Can't mount the snapshot %s: %s\n",
si->si_ssname, strerror(-rc));
return rc;
}
/* 1. MGS is not mounted yet, mount the MGS firstly */
si->si_mgs->st_ignored = 0;
si->si_mgs->st_pid = 0;
if (!mgs_running) {
rc = snapshot_mount_target(si, si->si_mgs, "");
if (rc == -ESRCH) {
si->si_mgs->st_ignored = 1;
rc = 0;
}
if (rc < 0) {
fprintf(stderr,
"Can't mount the snapshot %s: %s\n",
si->si_ssname, strerror(-rc));
return rc;
}
if (si->si_mgs == si->si_mdt0)
mdt0_mounted = true;
} else {
si->si_mgs->st_ignored = 1;
}
/* 2. Mount MDT0 if it is not combined with the MGS. */
if (!mdt0_mounted) {
si->si_mdt0->st_ignored = 0;
si->si_mdt0->st_pid = 0;
rc = snapshot_mount_target(si, si->si_mdt0, ",nomgs");
if (rc == -ESRCH) {
si->si_mdt0->st_ignored = 1;
rc = 0;
}
if (rc)
goto cleanup;
}
/* 3.1 Mount other MDTs in parallel */
rc = __snapshot_mount(si, &si->si_mdts_list);
if (!rc)
/* 3.2 Mount OSTs in parallel */
rc = __snapshot_mount(si, &si->si_osts_list);
/* Wait for all children, even though part of them maybe failed */
failed = snapshot_wait(si, &rc1);
list_for_each_entry(st, &si->si_mdts_list, st_list) {
if (!st->st_ignored)
needed++;
}
list_for_each_entry(st, &si->si_osts_list, st_list) {
if (!st->st_ignored)
needed++;
}
cleanup:
if (rc || rc1) {
int rc2 = 0;
__snapshot_umount(si, &si->si_mdts_list);
__snapshot_umount(si, &si->si_osts_list);
snapshot_wait(si, &rc2);
if (rc)
fprintf(stderr,
"Can't mount the snapshot %s: %s\n",
si->si_ssname, strerror(-rc));
else
fprintf(stderr,
"%d of %d pieces of the snapshot %s "
"can't be mounted: %s\n",
failed, needed, si->si_ssname, strerror(-rc1));
return rc ? rc : rc1;
}
if (needed == 0) {
fprintf(stderr,
"The snapshot %s has already been mounted by other\n",
si->si_ssname);
return -EALREADY;
}
fprintf(stdout, "mounted the snapshot %s with fsname %s\n",
si->si_ssname, fsname);
return 0;
}
int jt_snapshot_mount(int argc, char **argv)
{
struct snapshot_instance *si;
struct option long_opts[] = {
{ .val = 'F', .name = "fsname", .has_arg = required_argument },
{ .val = 'h', .name = "help", .has_arg = no_argument },
{ .val = 'n', .name = "name", .has_arg = required_argument },
{ .val = 'r', .name = "rsh", .has_arg = required_argument },
{ .name = NULL } };
int rc = 0;
si = snapshot_init(argc, argv, long_opts, "F:hn:r:",
snapshot_mount_usage, LOCK_EX, &rc);
if (!si)
return rc;
if (!si->si_ssname) {
fprintf(stderr,
"Miss the snapshot name to be mounted\n");
snapshot_mount_usage();
snapshot_fini(si);
return -EINVAL;
}
rc = snapshot_mount(si);
if (rc)
SNAPSHOT_ADD_LOG(si, "Can't mount snapshot %s: %d\n",
si->si_ssname, rc);
else
SNAPSHOT_ADD_LOG(si, "The snapshot %s is mounted\n",
si->si_ssname);
snapshot_fini(si);
return rc;
}
static void snapshot_umount_usage(void)
{
fprintf(stderr,
"Umount the specified snapshot.\n"
"Usage:\n"
"snapshot_umount <-F | --fsname fsname> [-h | --help] "
"<-n | --name ssname> "
"[-r | --rsh remote_shell]\n"
"Options:\n"
"-F: the filesystem name.\n"
"-h: for help information.\n"
"-n: the snapshot's name.\n"
"-r: the remote shell used for communication with remote "
"target, the default value is 'ssh'.\n");
}
static int __snapshot_umount(struct snapshot_instance *si,
struct list_head *head)
{
struct snapshot_target *st;
pid_t pid;
int rc;
list_for_each_entry(st, head, st_list) {
st->st_status = 0;
st->st_ignored = 0;
st->st_pid = 0;
pid = fork();
if (pid < 0) {
SNAPSHOT_ADD_LOG(si, "Can't fork for umount snapshot "
"%s on target (%s(%s):%x:%d): %s\n",
si->si_ssname, st->st_host,
st->st_fhost ? st->st_fhost : "none",
st->st_role, st->st_index,
strerror(errno));
return pid;
}
/* child */
if (pid == 0) {
char cmd[MAX_BUF_SIZE];
int foreign = 0;
rc = target_is_mounted(si, st, si->si_ssname);
if (rc < 0)
exit(rc);
if (!rc)
exit(-ESRCH);
again:
memset(cmd, 0, sizeof(cmd));
snprintf(cmd, sizeof(cmd) - 1,
DRSH" 'umount "DSSNAME"' 2>/dev/null",
PRSH(si, st, foreign), PSSNAME(si, st));
rc = snapshot_exec(cmd);
if (rc && !foreign && st->st_fhost) {
foreign = 1;
goto again;
}
exit(rc);
}
/* parent continue to run more snapshot commands in parallel. */
st->st_pid = pid;
}
return 0;
}
static int snapshot_umount(struct snapshot_instance *si)
{
struct snapshot_target *st;
int needed = 0;
int failed;
int rc = 0;
int rc1 = 0;
int rc2 = 0;
rc = snapshot_general_check(si);
if (rc < 0) {
fprintf(stderr,
"Can't umount the snapshot %s: %s\n",
si->si_ssname, strerror(-rc));
return rc;
}
rc = __snapshot_umount(si, &si->si_mdts_list);
rc1 = __snapshot_umount(si, &si->si_osts_list);
failed = snapshot_wait(si, &rc2);
list_for_each_entry(st, &si->si_mdts_list, st_list) {
if (!st->st_ignored)
needed++;
}
list_for_each_entry(st, &si->si_osts_list, st_list) {
if (!st->st_ignored)
needed++;
}
if (needed == 0) {
fprintf(stderr,
"The snapshot %s has not been mounted\n",
si->si_ssname);
return -EALREADY;
}
if (failed != 0)
fprintf(stderr,
"%d of %d pieces of the snapshot %s "
"can't be umounted: %s\n",
failed, needed, si->si_ssname, strerror(-rc2));
return rc ? rc : (rc1 ? rc1 : rc2);
}
int jt_snapshot_umount(int argc, char **argv)
{
struct snapshot_instance *si;
struct option long_opts[] = {
{ .val = 'F', .name = "fsname", .has_arg = required_argument },
{ .val = 'h', .name = "help", .has_arg = no_argument },
{ .val = 'n', .name = "name", .has_arg = required_argument },
{ .val = 'r', .name = "rsh", .has_arg = required_argument },
{ .name = NULL } };
int rc = 0;
si = snapshot_init(argc, argv, long_opts, "F:hn:r:",
snapshot_umount_usage, LOCK_EX, &rc);
if (!si)
return rc;
if (!si->si_ssname) {
fprintf(stderr,
"Miss the snapshot name to be umounted\n");
snapshot_umount_usage();
snapshot_fini(si);
return -EINVAL;
}
rc = snapshot_umount(si);
if (rc < 0)
SNAPSHOT_ADD_LOG(si, "Can't umount snapshot %s: %d\n",
si->si_ssname, rc);
else
SNAPSHOT_ADD_LOG(si, "the snapshot %s have been umounted\n",
si->si_ssname);
snapshot_fini(si);
return rc;
}