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