1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * An implementation of the host initiated guest snapshot for Hyper-V. |
4 | * |
5 | * Copyright (C) 2013, Microsoft, Inc. |
6 | * Author : K. Y. Srinivasan <kys@microsoft.com> |
7 | */ |
8 | |
9 | |
10 | #include <sys/types.h> |
11 | #include <sys/poll.h> |
12 | #include <sys/ioctl.h> |
13 | #include <sys/stat.h> |
14 | #include <sys/sysmacros.h> |
15 | #include <fcntl.h> |
16 | #include <stdio.h> |
17 | #include <mntent.h> |
18 | #include <stdlib.h> |
19 | #include <unistd.h> |
20 | #include <string.h> |
21 | #include <ctype.h> |
22 | #include <errno.h> |
23 | #include <linux/fs.h> |
24 | #include <linux/major.h> |
25 | #include <linux/hyperv.h> |
26 | #include <syslog.h> |
27 | #include <getopt.h> |
28 | #include <stdbool.h> |
29 | #include <dirent.h> |
30 | |
31 | static bool fs_frozen; |
32 | |
33 | /* Don't use syslog() in the function since that can cause write to disk */ |
34 | static int vss_do_freeze(char *dir, unsigned int cmd) |
35 | { |
36 | int ret, fd = open(dir, O_RDONLY); |
37 | |
38 | if (fd < 0) |
39 | return 1; |
40 | |
41 | ret = ioctl(fd, cmd, 0); |
42 | |
43 | /* |
44 | * If a partition is mounted more than once, only the first |
45 | * FREEZE/THAW can succeed and the later ones will get |
46 | * EBUSY/EINVAL respectively: there could be 2 cases: |
47 | * 1) a user may mount the same partition to different directories |
48 | * by mistake or on purpose; |
49 | * 2) The subvolume of btrfs appears to have the same partition |
50 | * mounted more than once. |
51 | */ |
52 | if (ret) { |
53 | if ((cmd == FIFREEZE && errno == EBUSY) || |
54 | (cmd == FITHAW && errno == EINVAL)) { |
55 | close(fd); |
56 | return 0; |
57 | } |
58 | } |
59 | |
60 | close(fd); |
61 | return !!ret; |
62 | } |
63 | |
64 | static bool is_dev_loop(const char *blkname) |
65 | { |
66 | char *buffer; |
67 | DIR *dir; |
68 | struct dirent *entry; |
69 | bool ret = false; |
70 | |
71 | buffer = malloc(PATH_MAX); |
72 | if (!buffer) { |
73 | syslog(LOG_ERR, "Can't allocate memory!" ); |
74 | exit(1); |
75 | } |
76 | |
77 | snprintf(buf: buffer, PATH_MAX, fmt: "%s/loop" , blkname); |
78 | if (!access(buffer, R_OK | X_OK)) { |
79 | ret = true; |
80 | goto free_buffer; |
81 | } else if (errno != ENOENT) { |
82 | syslog(LOG_ERR, "Can't access: %s; error:%d %s!" , |
83 | buffer, errno, strerror(errno)); |
84 | } |
85 | |
86 | snprintf(buf: buffer, PATH_MAX, fmt: "%s/slaves" , blkname); |
87 | dir = opendir(buffer); |
88 | if (!dir) { |
89 | if (errno != ENOENT) |
90 | syslog(LOG_ERR, "Can't opendir: %s; error:%d %s!" , |
91 | buffer, errno, strerror(errno)); |
92 | goto free_buffer; |
93 | } |
94 | |
95 | while ((entry = readdir(dir)) != NULL) { |
96 | if (strcmp(entry->d_name, "." ) == 0 || |
97 | strcmp(entry->d_name, ".." ) == 0) |
98 | continue; |
99 | |
100 | snprintf(buf: buffer, PATH_MAX, fmt: "%s/slaves/%s" , blkname, |
101 | entry->d_name); |
102 | if (is_dev_loop(blkname: buffer)) { |
103 | ret = true; |
104 | break; |
105 | } |
106 | } |
107 | closedir(dir); |
108 | free_buffer: |
109 | free(buffer); |
110 | return ret; |
111 | } |
112 | |
113 | static int vss_operate(int operation) |
114 | { |
115 | char match[] = "/dev/" ; |
116 | FILE *mounts; |
117 | struct mntent *ent; |
118 | struct stat sb; |
119 | char errdir[1024] = {0}; |
120 | char blkdir[23]; /* /sys/dev/block/XXX:XXX */ |
121 | unsigned int cmd; |
122 | int error = 0, root_seen = 0, save_errno = 0; |
123 | |
124 | switch (operation) { |
125 | case VSS_OP_FREEZE: |
126 | cmd = FIFREEZE; |
127 | break; |
128 | case VSS_OP_THAW: |
129 | cmd = FITHAW; |
130 | break; |
131 | default: |
132 | return -1; |
133 | } |
134 | |
135 | mounts = setmntent("/proc/mounts" , "r" ); |
136 | if (mounts == NULL) |
137 | return -1; |
138 | |
139 | while ((ent = getmntent(mounts))) { |
140 | if (strncmp(ent->mnt_fsname, match, strlen(match))) |
141 | continue; |
142 | if (stat(ent->mnt_fsname, &sb)) { |
143 | syslog(LOG_ERR, "Can't stat: %s; error:%d %s!" , |
144 | ent->mnt_fsname, errno, strerror(errno)); |
145 | } else { |
146 | sprintf(buf: blkdir, fmt: "/sys/dev/block/%d:%d" , |
147 | major(sb.st_rdev), minor(sb.st_rdev)); |
148 | if (is_dev_loop(blkname: blkdir)) |
149 | continue; |
150 | } |
151 | if (hasmntopt(ent, MNTOPT_RO) != NULL) |
152 | continue; |
153 | if (strcmp(ent->mnt_type, "vfat" ) == 0) |
154 | continue; |
155 | if (strcmp(ent->mnt_dir, "/" ) == 0) { |
156 | root_seen = 1; |
157 | continue; |
158 | } |
159 | error |= vss_do_freeze(dir: ent->mnt_dir, cmd); |
160 | if (operation == VSS_OP_FREEZE) { |
161 | if (error) |
162 | goto err; |
163 | fs_frozen = true; |
164 | } |
165 | } |
166 | |
167 | endmntent(mounts); |
168 | |
169 | if (root_seen) { |
170 | error |= vss_do_freeze(dir: "/" , cmd); |
171 | if (operation == VSS_OP_FREEZE) { |
172 | if (error) |
173 | goto err; |
174 | fs_frozen = true; |
175 | } |
176 | } |
177 | |
178 | if (operation == VSS_OP_THAW && !error) |
179 | fs_frozen = false; |
180 | |
181 | goto out; |
182 | err: |
183 | save_errno = errno; |
184 | if (ent) { |
185 | strncpy(errdir, ent->mnt_dir, sizeof(errdir)-1); |
186 | endmntent(mounts); |
187 | } |
188 | vss_operate(operation: VSS_OP_THAW); |
189 | fs_frozen = false; |
190 | /* Call syslog after we thaw all filesystems */ |
191 | if (ent) |
192 | syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s" , |
193 | errdir, save_errno, strerror(save_errno)); |
194 | else |
195 | syslog(LOG_ERR, "FREEZE of / failed; error:%d %s" , save_errno, |
196 | strerror(save_errno)); |
197 | out: |
198 | return error; |
199 | } |
200 | |
201 | void print_usage(char *argv[]) |
202 | { |
203 | fprintf(stderr, "Usage: %s [options]\n" |
204 | "Options are:\n" |
205 | " -n, --no-daemon stay in foreground, don't daemonize\n" |
206 | " -h, --help print this help\n" , argv[0]); |
207 | } |
208 | |
209 | int main(int argc, char *argv[]) |
210 | { |
211 | int vss_fd = -1, len; |
212 | int error; |
213 | struct pollfd pfd; |
214 | int op; |
215 | struct hv_vss_msg vss_msg[1]; |
216 | int daemonize = 1, long_index = 0, opt; |
217 | int in_handshake; |
218 | __u32 kernel_modver; |
219 | |
220 | static struct option long_options[] = { |
221 | {"help" , no_argument, 0, 'h' }, |
222 | {"no-daemon" , no_argument, 0, 'n' }, |
223 | {0, 0, 0, 0 } |
224 | }; |
225 | |
226 | while ((opt = getopt_long(argc, argv, "hn" , long_options, |
227 | &long_index)) != -1) { |
228 | switch (opt) { |
229 | case 'n': |
230 | daemonize = 0; |
231 | break; |
232 | case 'h': |
233 | print_usage(argv); |
234 | exit(0); |
235 | default: |
236 | print_usage(argv); |
237 | exit(EXIT_FAILURE); |
238 | } |
239 | } |
240 | |
241 | if (daemonize && daemon(1, 0)) |
242 | return 1; |
243 | |
244 | openlog("Hyper-V VSS" , 0, LOG_USER); |
245 | syslog(LOG_INFO, "VSS starting; pid is:%d" , getpid()); |
246 | |
247 | reopen_vss_fd: |
248 | if (vss_fd != -1) |
249 | close(vss_fd); |
250 | if (fs_frozen) { |
251 | if (vss_operate(operation: VSS_OP_THAW) || fs_frozen) { |
252 | syslog(LOG_ERR, "failed to thaw file system: err=%d" , |
253 | errno); |
254 | exit(EXIT_FAILURE); |
255 | } |
256 | } |
257 | |
258 | in_handshake = 1; |
259 | vss_fd = open("/dev/vmbus/hv_vss" , O_RDWR); |
260 | if (vss_fd < 0) { |
261 | syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s" , |
262 | errno, strerror(errno)); |
263 | exit(EXIT_FAILURE); |
264 | } |
265 | /* |
266 | * Register ourselves with the kernel. |
267 | */ |
268 | vss_msg->vss_hdr.operation = VSS_OP_REGISTER1; |
269 | |
270 | len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); |
271 | if (len < 0) { |
272 | syslog(LOG_ERR, "registration to kernel failed; error: %d %s" , |
273 | errno, strerror(errno)); |
274 | close(vss_fd); |
275 | exit(EXIT_FAILURE); |
276 | } |
277 | |
278 | pfd.fd = vss_fd; |
279 | |
280 | while (1) { |
281 | pfd.events = POLLIN; |
282 | pfd.revents = 0; |
283 | |
284 | if (poll(&pfd, 1, -1) < 0) { |
285 | syslog(LOG_ERR, "poll failed; error:%d %s" , errno, strerror(errno)); |
286 | if (errno == EINVAL) { |
287 | close(vss_fd); |
288 | exit(EXIT_FAILURE); |
289 | } |
290 | else |
291 | continue; |
292 | } |
293 | |
294 | len = read(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); |
295 | |
296 | if (in_handshake) { |
297 | if (len != sizeof(kernel_modver)) { |
298 | syslog(LOG_ERR, "invalid version negotiation" ); |
299 | exit(EXIT_FAILURE); |
300 | } |
301 | kernel_modver = *(__u32 *)vss_msg; |
302 | in_handshake = 0; |
303 | syslog(LOG_INFO, "VSS: kernel module version: %d" , |
304 | kernel_modver); |
305 | continue; |
306 | } |
307 | |
308 | if (len != sizeof(struct hv_vss_msg)) { |
309 | syslog(LOG_ERR, "read failed; error:%d %s" , |
310 | errno, strerror(errno)); |
311 | goto reopen_vss_fd; |
312 | } |
313 | |
314 | op = vss_msg->vss_hdr.operation; |
315 | error = HV_S_OK; |
316 | |
317 | switch (op) { |
318 | case VSS_OP_FREEZE: |
319 | case VSS_OP_THAW: |
320 | error = vss_operate(operation: op); |
321 | syslog(LOG_INFO, "VSS: op=%s: %s\n" , |
322 | op == VSS_OP_FREEZE ? "FREEZE" : "THAW" , |
323 | error ? "failed" : "succeeded" ); |
324 | |
325 | if (error) { |
326 | error = HV_E_FAIL; |
327 | syslog(LOG_ERR, "op=%d failed!" , op); |
328 | syslog(LOG_ERR, "report it with these files:" ); |
329 | syslog(LOG_ERR, "/etc/fstab and /proc/mounts" ); |
330 | } |
331 | break; |
332 | case VSS_OP_HOT_BACKUP: |
333 | syslog(LOG_INFO, "VSS: op=CHECK HOT BACKUP\n" ); |
334 | break; |
335 | default: |
336 | syslog(LOG_ERR, "Illegal op:%d\n" , op); |
337 | } |
338 | |
339 | /* |
340 | * The write() may return an error due to the faked VSS_OP_THAW |
341 | * message upon hibernation. Ignore the error by resetting the |
342 | * dev file, i.e. closing and re-opening it. |
343 | */ |
344 | vss_msg->error = error; |
345 | len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); |
346 | if (len != sizeof(struct hv_vss_msg)) { |
347 | syslog(LOG_ERR, "write failed; error: %d %s" , errno, |
348 | strerror(errno)); |
349 | goto reopen_vss_fd; |
350 | } |
351 | } |
352 | |
353 | close(vss_fd); |
354 | exit(0); |
355 | } |
356 | |