143aa3132SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
296dd86faSK. Y. Srinivasan /*
396dd86faSK. Y. Srinivasan * An implementation of the host initiated guest snapshot for Hyper-V.
496dd86faSK. Y. Srinivasan *
596dd86faSK. Y. Srinivasan * Copyright (C) 2013, Microsoft, Inc.
696dd86faSK. Y. Srinivasan * Author : K. Y. Srinivasan <kys@microsoft.com>
796dd86faSK. Y. Srinivasan */
896dd86faSK. Y. Srinivasan
996dd86faSK. Y. Srinivasan
1096dd86faSK. Y. Srinivasan #include <sys/types.h>
1196dd86faSK. Y. Srinivasan #include <sys/poll.h>
127b413b65SOlaf Hering #include <sys/ioctl.h>
13ea81fdf0SAlex Ng #include <sys/stat.h>
141330fc35SDexuan Cui #include <sys/sysmacros.h>
157b413b65SOlaf Hering #include <fcntl.h>
1696dd86faSK. Y. Srinivasan #include <stdio.h>
17d3d1ee3aSOlaf Hering #include <mntent.h>
1896dd86faSK. Y. Srinivasan #include <stdlib.h>
1996dd86faSK. Y. Srinivasan #include <unistd.h>
2096dd86faSK. Y. Srinivasan #include <string.h>
2196dd86faSK. Y. Srinivasan #include <ctype.h>
2296dd86faSK. Y. Srinivasan #include <errno.h>
237b413b65SOlaf Hering #include <linux/fs.h>
24ea81fdf0SAlex Ng #include <linux/major.h>
2596dd86faSK. Y. Srinivasan #include <linux/hyperv.h>
2696dd86faSK. Y. Srinivasan #include <syslog.h>
27170f4beaSVitaly Kuznetsov #include <getopt.h>
2807136793SVitaly Kuznetsov #include <stdbool.h>
2907136793SVitaly Kuznetsov #include <dirent.h>
3096dd86faSK. Y. Srinivasan
31*9fc3c01aSDexuan Cui static bool fs_frozen;
32*9fc3c01aSDexuan Cui
334f689190SDexuan Cui /* Don't use syslog() in the function since that can cause write to disk */
vss_do_freeze(char * dir,unsigned int cmd)344f689190SDexuan Cui static int vss_do_freeze(char *dir, unsigned int cmd)
357b413b65SOlaf Hering {
367b413b65SOlaf Hering int ret, fd = open(dir, O_RDONLY);
377b413b65SOlaf Hering
387b413b65SOlaf Hering if (fd < 0)
397b413b65SOlaf Hering return 1;
404f689190SDexuan Cui
417b413b65SOlaf Hering ret = ioctl(fd, cmd, 0);
424f689190SDexuan Cui
434f689190SDexuan Cui /*
444f689190SDexuan Cui * If a partition is mounted more than once, only the first
454f689190SDexuan Cui * FREEZE/THAW can succeed and the later ones will get
464f689190SDexuan Cui * EBUSY/EINVAL respectively: there could be 2 cases:
472d35c660SAdrian Vladu * 1) a user may mount the same partition to different directories
484f689190SDexuan Cui * by mistake or on purpose;
494f689190SDexuan Cui * 2) The subvolume of btrfs appears to have the same partition
504f689190SDexuan Cui * mounted more than once.
514f689190SDexuan Cui */
524f689190SDexuan Cui if (ret) {
534f689190SDexuan Cui if ((cmd == FIFREEZE && errno == EBUSY) ||
544f689190SDexuan Cui (cmd == FITHAW && errno == EINVAL)) {
554f689190SDexuan Cui close(fd);
564f689190SDexuan Cui return 0;
574f689190SDexuan Cui }
584f689190SDexuan Cui }
594f689190SDexuan Cui
607b413b65SOlaf Hering close(fd);
617b413b65SOlaf Hering return !!ret;
627b413b65SOlaf Hering }
637b413b65SOlaf Hering
is_dev_loop(const char * blkname)6407136793SVitaly Kuznetsov static bool is_dev_loop(const char *blkname)
6507136793SVitaly Kuznetsov {
6607136793SVitaly Kuznetsov char *buffer;
6707136793SVitaly Kuznetsov DIR *dir;
6807136793SVitaly Kuznetsov struct dirent *entry;
6907136793SVitaly Kuznetsov bool ret = false;
7007136793SVitaly Kuznetsov
7107136793SVitaly Kuznetsov buffer = malloc(PATH_MAX);
7207136793SVitaly Kuznetsov if (!buffer) {
7307136793SVitaly Kuznetsov syslog(LOG_ERR, "Can't allocate memory!");
7407136793SVitaly Kuznetsov exit(1);
7507136793SVitaly Kuznetsov }
7607136793SVitaly Kuznetsov
7707136793SVitaly Kuznetsov snprintf(buffer, PATH_MAX, "%s/loop", blkname);
7807136793SVitaly Kuznetsov if (!access(buffer, R_OK | X_OK)) {
7907136793SVitaly Kuznetsov ret = true;
8007136793SVitaly Kuznetsov goto free_buffer;
8107136793SVitaly Kuznetsov } else if (errno != ENOENT) {
8207136793SVitaly Kuznetsov syslog(LOG_ERR, "Can't access: %s; error:%d %s!",
8307136793SVitaly Kuznetsov buffer, errno, strerror(errno));
8407136793SVitaly Kuznetsov }
8507136793SVitaly Kuznetsov
8607136793SVitaly Kuznetsov snprintf(buffer, PATH_MAX, "%s/slaves", blkname);
8707136793SVitaly Kuznetsov dir = opendir(buffer);
8807136793SVitaly Kuznetsov if (!dir) {
8907136793SVitaly Kuznetsov if (errno != ENOENT)
9007136793SVitaly Kuznetsov syslog(LOG_ERR, "Can't opendir: %s; error:%d %s!",
9107136793SVitaly Kuznetsov buffer, errno, strerror(errno));
9207136793SVitaly Kuznetsov goto free_buffer;
9307136793SVitaly Kuznetsov }
9407136793SVitaly Kuznetsov
9507136793SVitaly Kuznetsov while ((entry = readdir(dir)) != NULL) {
9607136793SVitaly Kuznetsov if (strcmp(entry->d_name, ".") == 0 ||
9707136793SVitaly Kuznetsov strcmp(entry->d_name, "..") == 0)
9807136793SVitaly Kuznetsov continue;
9907136793SVitaly Kuznetsov
10007136793SVitaly Kuznetsov snprintf(buffer, PATH_MAX, "%s/slaves/%s", blkname,
10107136793SVitaly Kuznetsov entry->d_name);
10207136793SVitaly Kuznetsov if (is_dev_loop(buffer)) {
10307136793SVitaly Kuznetsov ret = true;
10407136793SVitaly Kuznetsov break;
10507136793SVitaly Kuznetsov }
10607136793SVitaly Kuznetsov }
10707136793SVitaly Kuznetsov closedir(dir);
10807136793SVitaly Kuznetsov free_buffer:
10907136793SVitaly Kuznetsov free(buffer);
11007136793SVitaly Kuznetsov return ret;
11107136793SVitaly Kuznetsov }
11207136793SVitaly Kuznetsov
vss_operate(int operation)11396dd86faSK. Y. Srinivasan static int vss_operate(int operation)
11496dd86faSK. Y. Srinivasan {
115d3d1ee3aSOlaf Hering char match[] = "/dev/";
116d3d1ee3aSOlaf Hering FILE *mounts;
117d3d1ee3aSOlaf Hering struct mntent *ent;
118ea81fdf0SAlex Ng struct stat sb;
1194ce50e94SVaughan Cao char errdir[1024] = {0};
12007136793SVitaly Kuznetsov char blkdir[23]; /* /sys/dev/block/XXX:XXX */
1217b413b65SOlaf Hering unsigned int cmd;
1227a401744SVitaly Kuznetsov int error = 0, root_seen = 0, save_errno = 0;
12396dd86faSK. Y. Srinivasan
12496dd86faSK. Y. Srinivasan switch (operation) {
12596dd86faSK. Y. Srinivasan case VSS_OP_FREEZE:
1267b413b65SOlaf Hering cmd = FIFREEZE;
12796dd86faSK. Y. Srinivasan break;
12896dd86faSK. Y. Srinivasan case VSS_OP_THAW:
1297b413b65SOlaf Hering cmd = FITHAW;
13096dd86faSK. Y. Srinivasan break;
131eb8905b8SOlaf Hering default:
132eb8905b8SOlaf Hering return -1;
13396dd86faSK. Y. Srinivasan }
13496dd86faSK. Y. Srinivasan
135d3d1ee3aSOlaf Hering mounts = setmntent("/proc/mounts", "r");
136d3d1ee3aSOlaf Hering if (mounts == NULL)
137eb8905b8SOlaf Hering return -1;
13896dd86faSK. Y. Srinivasan
139d3d1ee3aSOlaf Hering while ((ent = getmntent(mounts))) {
140d3d1ee3aSOlaf Hering if (strncmp(ent->mnt_fsname, match, strlen(match)))
14196dd86faSK. Y. Srinivasan continue;
14207136793SVitaly Kuznetsov if (stat(ent->mnt_fsname, &sb)) {
14307136793SVitaly Kuznetsov syslog(LOG_ERR, "Can't stat: %s; error:%d %s!",
14407136793SVitaly Kuznetsov ent->mnt_fsname, errno, strerror(errno));
14507136793SVitaly Kuznetsov } else {
14607136793SVitaly Kuznetsov sprintf(blkdir, "/sys/dev/block/%d:%d",
14707136793SVitaly Kuznetsov major(sb.st_rdev), minor(sb.st_rdev));
14807136793SVitaly Kuznetsov if (is_dev_loop(blkdir))
149ea81fdf0SAlex Ng continue;
15007136793SVitaly Kuznetsov }
1519e5db05aSVitaly Kuznetsov if (hasmntopt(ent, MNTOPT_RO) != NULL)
15210b637b4SOlaf Hering continue;
153f33b2155SK. Y. Srinivasan if (strcmp(ent->mnt_type, "vfat") == 0)
154f33b2155SK. Y. Srinivasan continue;
155d3d1ee3aSOlaf Hering if (strcmp(ent->mnt_dir, "/") == 0) {
156d3d1ee3aSOlaf Hering root_seen = 1;
157d3d1ee3aSOlaf Hering continue;
15896dd86faSK. Y. Srinivasan }
1594f689190SDexuan Cui error |= vss_do_freeze(ent->mnt_dir, cmd);
160*9fc3c01aSDexuan Cui if (operation == VSS_OP_FREEZE) {
161*9fc3c01aSDexuan Cui if (error)
1624f689190SDexuan Cui goto err;
163*9fc3c01aSDexuan Cui fs_frozen = true;
164*9fc3c01aSDexuan Cui }
165d3d1ee3aSOlaf Hering }
166d3d1ee3aSOlaf Hering
1674ce50e94SVaughan Cao endmntent(mounts);
1684ce50e94SVaughan Cao
169d3d1ee3aSOlaf Hering if (root_seen) {
1704f689190SDexuan Cui error |= vss_do_freeze("/", cmd);
171*9fc3c01aSDexuan Cui if (operation == VSS_OP_FREEZE) {
172*9fc3c01aSDexuan Cui if (error)
1734f689190SDexuan Cui goto err;
174*9fc3c01aSDexuan Cui fs_frozen = true;
175d3d1ee3aSOlaf Hering }
176*9fc3c01aSDexuan Cui }
177*9fc3c01aSDexuan Cui
178*9fc3c01aSDexuan Cui if (operation == VSS_OP_THAW && !error)
179*9fc3c01aSDexuan Cui fs_frozen = false;
18096dd86faSK. Y. Srinivasan
1817a401744SVitaly Kuznetsov goto out;
1824f689190SDexuan Cui err:
1837a401744SVitaly Kuznetsov save_errno = errno;
1844ce50e94SVaughan Cao if (ent) {
1854ce50e94SVaughan Cao strncpy(errdir, ent->mnt_dir, sizeof(errdir)-1);
1864ce50e94SVaughan Cao endmntent(mounts);
1874ce50e94SVaughan Cao }
1884f689190SDexuan Cui vss_operate(VSS_OP_THAW);
189*9fc3c01aSDexuan Cui fs_frozen = false;
1907a401744SVitaly Kuznetsov /* Call syslog after we thaw all filesystems */
1917a401744SVitaly Kuznetsov if (ent)
1927a401744SVitaly Kuznetsov syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s",
1934ce50e94SVaughan Cao errdir, save_errno, strerror(save_errno));
1947a401744SVitaly Kuznetsov else
1957a401744SVitaly Kuznetsov syslog(LOG_ERR, "FREEZE of / failed; error:%d %s", save_errno,
1967a401744SVitaly Kuznetsov strerror(save_errno));
1977a401744SVitaly Kuznetsov out:
1984f689190SDexuan Cui return error;
19996dd86faSK. Y. Srinivasan }
20096dd86faSK. Y. Srinivasan
print_usage(char * argv[])201170f4beaSVitaly Kuznetsov void print_usage(char *argv[])
202170f4beaSVitaly Kuznetsov {
203170f4beaSVitaly Kuznetsov fprintf(stderr, "Usage: %s [options]\n"
204170f4beaSVitaly Kuznetsov "Options are:\n"
205170f4beaSVitaly Kuznetsov " -n, --no-daemon stay in foreground, don't daemonize\n"
206170f4beaSVitaly Kuznetsov " -h, --help print this help\n", argv[0]);
207170f4beaSVitaly Kuznetsov }
208170f4beaSVitaly Kuznetsov
main(int argc,char * argv[])209170f4beaSVitaly Kuznetsov int main(int argc, char *argv[])
21096dd86faSK. Y. Srinivasan {
211*9fc3c01aSDexuan Cui int vss_fd = -1, len;
21296dd86faSK. Y. Srinivasan int error;
21396dd86faSK. Y. Srinivasan struct pollfd pfd;
21496dd86faSK. Y. Srinivasan int op;
215f5722b9bSVitaly Kuznetsov struct hv_vss_msg vss_msg[1];
216170f4beaSVitaly Kuznetsov int daemonize = 1, long_index = 0, opt;
217*9fc3c01aSDexuan Cui int in_handshake;
218cd8dc054SVitaly Kuznetsov __u32 kernel_modver;
21996dd86faSK. Y. Srinivasan
220170f4beaSVitaly Kuznetsov static struct option long_options[] = {
221170f4beaSVitaly Kuznetsov {"help", no_argument, 0, 'h' },
222170f4beaSVitaly Kuznetsov {"no-daemon", no_argument, 0, 'n' },
223170f4beaSVitaly Kuznetsov {0, 0, 0, 0 }
224170f4beaSVitaly Kuznetsov };
225170f4beaSVitaly Kuznetsov
226170f4beaSVitaly Kuznetsov while ((opt = getopt_long(argc, argv, "hn", long_options,
227170f4beaSVitaly Kuznetsov &long_index)) != -1) {
228170f4beaSVitaly Kuznetsov switch (opt) {
229170f4beaSVitaly Kuznetsov case 'n':
230170f4beaSVitaly Kuznetsov daemonize = 0;
231170f4beaSVitaly Kuznetsov break;
232170f4beaSVitaly Kuznetsov case 'h':
233b0995156SAdrian Vladu print_usage(argv);
234b0995156SAdrian Vladu exit(0);
235170f4beaSVitaly Kuznetsov default:
236170f4beaSVitaly Kuznetsov print_usage(argv);
237170f4beaSVitaly Kuznetsov exit(EXIT_FAILURE);
238170f4beaSVitaly Kuznetsov }
239170f4beaSVitaly Kuznetsov }
240170f4beaSVitaly Kuznetsov
241170f4beaSVitaly Kuznetsov if (daemonize && daemon(1, 0))
242eb8905b8SOlaf Hering return 1;
243eb8905b8SOlaf Hering
24496dd86faSK. Y. Srinivasan openlog("Hyper-V VSS", 0, LOG_USER);
24596dd86faSK. Y. Srinivasan syslog(LOG_INFO, "VSS starting; pid is:%d", getpid());
24696dd86faSK. Y. Srinivasan
247*9fc3c01aSDexuan Cui reopen_vss_fd:
248*9fc3c01aSDexuan Cui if (vss_fd != -1)
249*9fc3c01aSDexuan Cui close(vss_fd);
250*9fc3c01aSDexuan Cui if (fs_frozen) {
251*9fc3c01aSDexuan Cui if (vss_operate(VSS_OP_THAW) || fs_frozen) {
252*9fc3c01aSDexuan Cui syslog(LOG_ERR, "failed to thaw file system: err=%d",
253*9fc3c01aSDexuan Cui errno);
254*9fc3c01aSDexuan Cui exit(EXIT_FAILURE);
255*9fc3c01aSDexuan Cui }
256*9fc3c01aSDexuan Cui }
257*9fc3c01aSDexuan Cui
258*9fc3c01aSDexuan Cui in_handshake = 1;
259f5722b9bSVitaly Kuznetsov vss_fd = open("/dev/vmbus/hv_vss", O_RDWR);
260f5722b9bSVitaly Kuznetsov if (vss_fd < 0) {
261f5722b9bSVitaly Kuznetsov syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s",
2625c289269STomas Hozza errno, strerror(errno));
26396dd86faSK. Y. Srinivasan exit(EXIT_FAILURE);
26496dd86faSK. Y. Srinivasan }
26596dd86faSK. Y. Srinivasan /*
26696dd86faSK. Y. Srinivasan * Register ourselves with the kernel.
26796dd86faSK. Y. Srinivasan */
268f5722b9bSVitaly Kuznetsov vss_msg->vss_hdr.operation = VSS_OP_REGISTER1;
26996dd86faSK. Y. Srinivasan
270f5722b9bSVitaly Kuznetsov len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg));
27196dd86faSK. Y. Srinivasan if (len < 0) {
272f5722b9bSVitaly Kuznetsov syslog(LOG_ERR, "registration to kernel failed; error: %d %s",
273f5722b9bSVitaly Kuznetsov errno, strerror(errno));
274f5722b9bSVitaly Kuznetsov close(vss_fd);
27596dd86faSK. Y. Srinivasan exit(EXIT_FAILURE);
27696dd86faSK. Y. Srinivasan }
27796dd86faSK. Y. Srinivasan
278f5722b9bSVitaly Kuznetsov pfd.fd = vss_fd;
27996dd86faSK. Y. Srinivasan
28096dd86faSK. Y. Srinivasan while (1) {
28196dd86faSK. Y. Srinivasan pfd.events = POLLIN;
28296dd86faSK. Y. Srinivasan pfd.revents = 0;
2839561d479STomas Hozza
2849561d479STomas Hozza if (poll(&pfd, 1, -1) < 0) {
2859561d479STomas Hozza syslog(LOG_ERR, "poll failed; error:%d %s", errno, strerror(errno));
2869561d479STomas Hozza if (errno == EINVAL) {
287f5722b9bSVitaly Kuznetsov close(vss_fd);
2889561d479STomas Hozza exit(EXIT_FAILURE);
2899561d479STomas Hozza }
2909561d479STomas Hozza else
2919561d479STomas Hozza continue;
2929561d479STomas Hozza }
29396dd86faSK. Y. Srinivasan
294f5722b9bSVitaly Kuznetsov len = read(vss_fd, vss_msg, sizeof(struct hv_vss_msg));
29596dd86faSK. Y. Srinivasan
296cd8dc054SVitaly Kuznetsov if (in_handshake) {
297cd8dc054SVitaly Kuznetsov if (len != sizeof(kernel_modver)) {
298cd8dc054SVitaly Kuznetsov syslog(LOG_ERR, "invalid version negotiation");
299cd8dc054SVitaly Kuznetsov exit(EXIT_FAILURE);
300cd8dc054SVitaly Kuznetsov }
301cd8dc054SVitaly Kuznetsov kernel_modver = *(__u32 *)vss_msg;
302cd8dc054SVitaly Kuznetsov in_handshake = 0;
303cd8dc054SVitaly Kuznetsov syslog(LOG_INFO, "VSS: kernel module version: %d",
304cd8dc054SVitaly Kuznetsov kernel_modver);
305cd8dc054SVitaly Kuznetsov continue;
306cd8dc054SVitaly Kuznetsov }
307cd8dc054SVitaly Kuznetsov
308f5722b9bSVitaly Kuznetsov if (len != sizeof(struct hv_vss_msg)) {
309f5722b9bSVitaly Kuznetsov syslog(LOG_ERR, "read failed; error:%d %s",
310f5722b9bSVitaly Kuznetsov errno, strerror(errno));
311*9fc3c01aSDexuan Cui goto reopen_vss_fd;
31296dd86faSK. Y. Srinivasan }
31396dd86faSK. Y. Srinivasan
31496dd86faSK. Y. Srinivasan op = vss_msg->vss_hdr.operation;
31596dd86faSK. Y. Srinivasan error = HV_S_OK;
31696dd86faSK. Y. Srinivasan
31796dd86faSK. Y. Srinivasan switch (op) {
31896dd86faSK. Y. Srinivasan case VSS_OP_FREEZE:
31996dd86faSK. Y. Srinivasan case VSS_OP_THAW:
32096dd86faSK. Y. Srinivasan error = vss_operate(op);
3214f689190SDexuan Cui syslog(LOG_INFO, "VSS: op=%s: %s\n",
3224f689190SDexuan Cui op == VSS_OP_FREEZE ? "FREEZE" : "THAW",
3234f689190SDexuan Cui error ? "failed" : "succeeded");
3244f689190SDexuan Cui
3254f689190SDexuan Cui if (error) {
32696dd86faSK. Y. Srinivasan error = HV_E_FAIL;
3274f689190SDexuan Cui syslog(LOG_ERR, "op=%d failed!", op);
3284f689190SDexuan Cui syslog(LOG_ERR, "report it with these files:");
3294f689190SDexuan Cui syslog(LOG_ERR, "/etc/fstab and /proc/mounts");
3304f689190SDexuan Cui }
33196dd86faSK. Y. Srinivasan break;
332db886e4dSAlex Ng case VSS_OP_HOT_BACKUP:
333db886e4dSAlex Ng syslog(LOG_INFO, "VSS: op=CHECK HOT BACKUP\n");
334db886e4dSAlex Ng break;
33596dd86faSK. Y. Srinivasan default:
33696dd86faSK. Y. Srinivasan syslog(LOG_ERR, "Illegal op:%d\n", op);
33796dd86faSK. Y. Srinivasan }
338*9fc3c01aSDexuan Cui
339*9fc3c01aSDexuan Cui /*
340*9fc3c01aSDexuan Cui * The write() may return an error due to the faked VSS_OP_THAW
341*9fc3c01aSDexuan Cui * message upon hibernation. Ignore the error by resetting the
342*9fc3c01aSDexuan Cui * dev file, i.e. closing and re-opening it.
343*9fc3c01aSDexuan Cui */
34496dd86faSK. Y. Srinivasan vss_msg->error = error;
345a689d251SDexuan Cui len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg));
346f5722b9bSVitaly Kuznetsov if (len != sizeof(struct hv_vss_msg)) {
347f5722b9bSVitaly Kuznetsov syslog(LOG_ERR, "write failed; error: %d %s", errno,
348f5722b9bSVitaly Kuznetsov strerror(errno));
349*9fc3c01aSDexuan Cui goto reopen_vss_fd;
35096dd86faSK. Y. Srinivasan }
35196dd86faSK. Y. Srinivasan }
35296dd86faSK. Y. Srinivasan
353f5722b9bSVitaly Kuznetsov close(vss_fd);
354f5722b9bSVitaly Kuznetsov exit(0);
35596dd86faSK. Y. Srinivasan }
356