1 /* 2 * An implementation of the host initiated guest snapshot for Hyper-V. 3 * 4 * 5 * Copyright (C) 2013, Microsoft, Inc. 6 * Author : K. Y. Srinivasan <kys@microsoft.com> 7 * 8 * This program is free software; you can redistribute it and/or modify it 9 * under the terms of the GNU General Public License version 2 as published 10 * by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or 15 * NON INFRINGEMENT. See the GNU General Public License for more 16 * details. 17 * 18 */ 19 20 21 #include <sys/types.h> 22 #include <sys/poll.h> 23 #include <sys/ioctl.h> 24 #include <fcntl.h> 25 #include <stdio.h> 26 #include <mntent.h> 27 #include <stdlib.h> 28 #include <unistd.h> 29 #include <string.h> 30 #include <ctype.h> 31 #include <errno.h> 32 #include <linux/fs.h> 33 #include <linux/hyperv.h> 34 #include <syslog.h> 35 #include <getopt.h> 36 37 /* Don't use syslog() in the function since that can cause write to disk */ 38 static int vss_do_freeze(char *dir, unsigned int cmd) 39 { 40 int ret, fd = open(dir, O_RDONLY); 41 42 if (fd < 0) 43 return 1; 44 45 ret = ioctl(fd, cmd, 0); 46 47 /* 48 * If a partition is mounted more than once, only the first 49 * FREEZE/THAW can succeed and the later ones will get 50 * EBUSY/EINVAL respectively: there could be 2 cases: 51 * 1) a user may mount the same partition to differnt directories 52 * by mistake or on purpose; 53 * 2) The subvolume of btrfs appears to have the same partition 54 * mounted more than once. 55 */ 56 if (ret) { 57 if ((cmd == FIFREEZE && errno == EBUSY) || 58 (cmd == FITHAW && errno == EINVAL)) { 59 close(fd); 60 return 0; 61 } 62 } 63 64 close(fd); 65 return !!ret; 66 } 67 68 static int vss_operate(int operation) 69 { 70 char match[] = "/dev/"; 71 FILE *mounts; 72 struct mntent *ent; 73 char errdir[1024] = {0}; 74 unsigned int cmd; 75 int error = 0, root_seen = 0, save_errno = 0; 76 77 switch (operation) { 78 case VSS_OP_FREEZE: 79 cmd = FIFREEZE; 80 break; 81 case VSS_OP_THAW: 82 cmd = FITHAW; 83 break; 84 default: 85 return -1; 86 } 87 88 mounts = setmntent("/proc/mounts", "r"); 89 if (mounts == NULL) 90 return -1; 91 92 while ((ent = getmntent(mounts))) { 93 if (strncmp(ent->mnt_fsname, match, strlen(match))) 94 continue; 95 if (hasmntopt(ent, MNTOPT_RO) != NULL) 96 continue; 97 if (strcmp(ent->mnt_type, "vfat") == 0) 98 continue; 99 if (strcmp(ent->mnt_dir, "/") == 0) { 100 root_seen = 1; 101 continue; 102 } 103 error |= vss_do_freeze(ent->mnt_dir, cmd); 104 if (error && operation == VSS_OP_FREEZE) 105 goto err; 106 } 107 108 endmntent(mounts); 109 110 if (root_seen) { 111 error |= vss_do_freeze("/", cmd); 112 if (error && operation == VSS_OP_FREEZE) 113 goto err; 114 } 115 116 goto out; 117 err: 118 save_errno = errno; 119 if (ent) { 120 strncpy(errdir, ent->mnt_dir, sizeof(errdir)-1); 121 endmntent(mounts); 122 } 123 vss_operate(VSS_OP_THAW); 124 /* Call syslog after we thaw all filesystems */ 125 if (ent) 126 syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s", 127 errdir, save_errno, strerror(save_errno)); 128 else 129 syslog(LOG_ERR, "FREEZE of / failed; error:%d %s", save_errno, 130 strerror(save_errno)); 131 out: 132 return error; 133 } 134 135 void print_usage(char *argv[]) 136 { 137 fprintf(stderr, "Usage: %s [options]\n" 138 "Options are:\n" 139 " -n, --no-daemon stay in foreground, don't daemonize\n" 140 " -h, --help print this help\n", argv[0]); 141 } 142 143 int main(int argc, char *argv[]) 144 { 145 int vss_fd, len; 146 int error; 147 struct pollfd pfd; 148 int op; 149 struct hv_vss_msg vss_msg[1]; 150 int daemonize = 1, long_index = 0, opt; 151 int in_handshake = 1; 152 __u32 kernel_modver; 153 154 static struct option long_options[] = { 155 {"help", no_argument, 0, 'h' }, 156 {"no-daemon", no_argument, 0, 'n' }, 157 {0, 0, 0, 0 } 158 }; 159 160 while ((opt = getopt_long(argc, argv, "hn", long_options, 161 &long_index)) != -1) { 162 switch (opt) { 163 case 'n': 164 daemonize = 0; 165 break; 166 case 'h': 167 default: 168 print_usage(argv); 169 exit(EXIT_FAILURE); 170 } 171 } 172 173 if (daemonize && daemon(1, 0)) 174 return 1; 175 176 openlog("Hyper-V VSS", 0, LOG_USER); 177 syslog(LOG_INFO, "VSS starting; pid is:%d", getpid()); 178 179 vss_fd = open("/dev/vmbus/hv_vss", O_RDWR); 180 if (vss_fd < 0) { 181 syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s", 182 errno, strerror(errno)); 183 exit(EXIT_FAILURE); 184 } 185 /* 186 * Register ourselves with the kernel. 187 */ 188 vss_msg->vss_hdr.operation = VSS_OP_REGISTER1; 189 190 len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); 191 if (len < 0) { 192 syslog(LOG_ERR, "registration to kernel failed; error: %d %s", 193 errno, strerror(errno)); 194 close(vss_fd); 195 exit(EXIT_FAILURE); 196 } 197 198 pfd.fd = vss_fd; 199 200 while (1) { 201 pfd.events = POLLIN; 202 pfd.revents = 0; 203 204 if (poll(&pfd, 1, -1) < 0) { 205 syslog(LOG_ERR, "poll failed; error:%d %s", errno, strerror(errno)); 206 if (errno == EINVAL) { 207 close(vss_fd); 208 exit(EXIT_FAILURE); 209 } 210 else 211 continue; 212 } 213 214 len = read(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); 215 216 if (in_handshake) { 217 if (len != sizeof(kernel_modver)) { 218 syslog(LOG_ERR, "invalid version negotiation"); 219 exit(EXIT_FAILURE); 220 } 221 kernel_modver = *(__u32 *)vss_msg; 222 in_handshake = 0; 223 syslog(LOG_INFO, "VSS: kernel module version: %d", 224 kernel_modver); 225 continue; 226 } 227 228 if (len != sizeof(struct hv_vss_msg)) { 229 syslog(LOG_ERR, "read failed; error:%d %s", 230 errno, strerror(errno)); 231 close(vss_fd); 232 return EXIT_FAILURE; 233 } 234 235 op = vss_msg->vss_hdr.operation; 236 error = HV_S_OK; 237 238 switch (op) { 239 case VSS_OP_FREEZE: 240 case VSS_OP_THAW: 241 error = vss_operate(op); 242 syslog(LOG_INFO, "VSS: op=%s: %s\n", 243 op == VSS_OP_FREEZE ? "FREEZE" : "THAW", 244 error ? "failed" : "succeeded"); 245 246 if (error) { 247 error = HV_E_FAIL; 248 syslog(LOG_ERR, "op=%d failed!", op); 249 syslog(LOG_ERR, "report it with these files:"); 250 syslog(LOG_ERR, "/etc/fstab and /proc/mounts"); 251 } 252 break; 253 default: 254 syslog(LOG_ERR, "Illegal op:%d\n", op); 255 } 256 vss_msg->error = error; 257 len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); 258 if (len != sizeof(struct hv_vss_msg)) { 259 syslog(LOG_ERR, "write failed; error: %d %s", errno, 260 strerror(errno)); 261 exit(EXIT_FAILURE); 262 } 263 } 264 265 close(vss_fd); 266 exit(0); 267 } 268