xref: /openbmc/linux/tools/hv/hv_fcopy_daemon.c (revision 9fc3c01a)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * An implementation of host to guest copy functionality for Linux.
4  *
5  * Copyright (C) 2014, Microsoft, Inc.
6  *
7  * Author : K. Y. Srinivasan <kys@microsoft.com>
8  */
9 
10 
11 #include <sys/types.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <unistd.h>
15 #include <string.h>
16 #include <errno.h>
17 #include <linux/hyperv.h>
18 #include <linux/limits.h>
19 #include <syslog.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <getopt.h>
23 
24 static int target_fd;
25 static char target_fname[PATH_MAX];
26 static unsigned long long filesize;
27 
hv_start_fcopy(struct hv_start_fcopy * smsg)28 static int hv_start_fcopy(struct hv_start_fcopy *smsg)
29 {
30 	int error = HV_E_FAIL;
31 	char *q, *p;
32 
33 	filesize = 0;
34 	p = (char *)smsg->path_name;
35 	snprintf(target_fname, sizeof(target_fname), "%s/%s",
36 		 (char *)smsg->path_name, (char *)smsg->file_name);
37 
38 	syslog(LOG_INFO, "Target file name: %s", target_fname);
39 	/*
40 	 * Check to see if the path is already in place; if not,
41 	 * create if required.
42 	 */
43 	while ((q = strchr(p, '/')) != NULL) {
44 		if (q == p) {
45 			p++;
46 			continue;
47 		}
48 		*q = '\0';
49 		if (access((char *)smsg->path_name, F_OK)) {
50 			if (smsg->copy_flags & CREATE_PATH) {
51 				if (mkdir((char *)smsg->path_name, 0755)) {
52 					syslog(LOG_ERR, "Failed to create %s",
53 						(char *)smsg->path_name);
54 					goto done;
55 				}
56 			} else {
57 				syslog(LOG_ERR, "Invalid path: %s",
58 					(char *)smsg->path_name);
59 				goto done;
60 			}
61 		}
62 		p = q + 1;
63 		*q = '/';
64 	}
65 
66 	if (!access(target_fname, F_OK)) {
67 		syslog(LOG_INFO, "File: %s exists", target_fname);
68 		if (!(smsg->copy_flags & OVER_WRITE)) {
69 			error = HV_ERROR_ALREADY_EXISTS;
70 			goto done;
71 		}
72 	}
73 
74 	target_fd = open(target_fname,
75 			 O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744);
76 	if (target_fd == -1) {
77 		syslog(LOG_INFO, "Open Failed: %s", strerror(errno));
78 		goto done;
79 	}
80 
81 	error = 0;
82 done:
83 	if (error)
84 		target_fname[0] = '\0';
85 	return error;
86 }
87 
hv_copy_data(struct hv_do_fcopy * cpmsg)88 static int hv_copy_data(struct hv_do_fcopy *cpmsg)
89 {
90 	ssize_t bytes_written;
91 	int ret = 0;
92 
93 	bytes_written = pwrite(target_fd, cpmsg->data, cpmsg->size,
94 				cpmsg->offset);
95 
96 	filesize += cpmsg->size;
97 	if (bytes_written != cpmsg->size) {
98 		switch (errno) {
99 		case ENOSPC:
100 			ret = HV_ERROR_DISK_FULL;
101 			break;
102 		default:
103 			ret = HV_E_FAIL;
104 			break;
105 		}
106 		syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)",
107 		       filesize, (long)bytes_written, strerror(errno));
108 	}
109 
110 	return ret;
111 }
112 
113 /*
114  * Reset target_fname to "" in the two below functions for hibernation: if
115  * the fcopy operation is aborted by hibernation, the daemon should remove the
116  * partially-copied file; to achieve this, the hv_utils driver always fakes a
117  * CANCEL_FCOPY message upon suspend, and later when the VM resumes back,
118  * the daemon calls hv_copy_cancel() to remove the file; if a file is copied
119  * successfully before suspend, hv_copy_finished() must reset target_fname to
120  * avoid that the file can be incorrectly removed upon resume, since the faked
121  * CANCEL_FCOPY message is spurious in this case.
122  */
hv_copy_finished(void)123 static int hv_copy_finished(void)
124 {
125 	close(target_fd);
126 	target_fname[0] = '\0';
127 	return 0;
128 }
hv_copy_cancel(void)129 static int hv_copy_cancel(void)
130 {
131 	close(target_fd);
132 	if (strlen(target_fname) > 0) {
133 		unlink(target_fname);
134 		target_fname[0] = '\0';
135 	}
136 	return 0;
137 
138 }
139 
print_usage(char * argv[])140 void print_usage(char *argv[])
141 {
142 	fprintf(stderr, "Usage: %s [options]\n"
143 		"Options are:\n"
144 		"  -n, --no-daemon        stay in foreground, don't daemonize\n"
145 		"  -h, --help             print this help\n", argv[0]);
146 }
147 
main(int argc,char * argv[])148 int main(int argc, char *argv[])
149 {
150 	int fcopy_fd = -1;
151 	int error;
152 	int daemonize = 1, long_index = 0, opt;
153 	int version = FCOPY_CURRENT_VERSION;
154 	union {
155 		struct hv_fcopy_hdr hdr;
156 		struct hv_start_fcopy start;
157 		struct hv_do_fcopy copy;
158 		__u32 kernel_modver;
159 	} buffer = { };
160 	int in_handshake;
161 
162 	static struct option long_options[] = {
163 		{"help",	no_argument,	   0,  'h' },
164 		{"no-daemon",	no_argument,	   0,  'n' },
165 		{0,		0,		   0,  0   }
166 	};
167 
168 	while ((opt = getopt_long(argc, argv, "hn", long_options,
169 				  &long_index)) != -1) {
170 		switch (opt) {
171 		case 'n':
172 			daemonize = 0;
173 			break;
174 		case 'h':
175 		default:
176 			print_usage(argv);
177 			exit(EXIT_FAILURE);
178 		}
179 	}
180 
181 	if (daemonize && daemon(1, 0)) {
182 		syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno));
183 		exit(EXIT_FAILURE);
184 	}
185 
186 	openlog("HV_FCOPY", 0, LOG_USER);
187 	syslog(LOG_INFO, "starting; pid is:%d", getpid());
188 
189 reopen_fcopy_fd:
190 	if (fcopy_fd != -1)
191 		close(fcopy_fd);
192 	/* Remove any possible partially-copied file on error */
193 	hv_copy_cancel();
194 	in_handshake = 1;
195 	fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR);
196 
197 	if (fcopy_fd < 0) {
198 		syslog(LOG_ERR, "open /dev/vmbus/hv_fcopy failed; error: %d %s",
199 			errno, strerror(errno));
200 		exit(EXIT_FAILURE);
201 	}
202 
203 	/*
204 	 * Register with the kernel.
205 	 */
206 	if ((write(fcopy_fd, &version, sizeof(int))) != sizeof(int)) {
207 		syslog(LOG_ERR, "Registration failed: %s", strerror(errno));
208 		exit(EXIT_FAILURE);
209 	}
210 
211 	while (1) {
212 		/*
213 		 * In this loop we process fcopy messages after the
214 		 * handshake is complete.
215 		 */
216 		ssize_t len;
217 
218 		len = pread(fcopy_fd, &buffer, sizeof(buffer), 0);
219 		if (len < 0) {
220 			syslog(LOG_ERR, "pread failed: %s", strerror(errno));
221 			goto reopen_fcopy_fd;
222 		}
223 
224 		if (in_handshake) {
225 			if (len != sizeof(buffer.kernel_modver)) {
226 				syslog(LOG_ERR, "invalid version negotiation");
227 				exit(EXIT_FAILURE);
228 			}
229 			in_handshake = 0;
230 			syslog(LOG_INFO, "kernel module version: %u",
231 			       buffer.kernel_modver);
232 			continue;
233 		}
234 
235 		switch (buffer.hdr.operation) {
236 		case START_FILE_COPY:
237 			error = hv_start_fcopy(&buffer.start);
238 			break;
239 		case WRITE_TO_FILE:
240 			error = hv_copy_data(&buffer.copy);
241 			break;
242 		case COMPLETE_FCOPY:
243 			error = hv_copy_finished();
244 			break;
245 		case CANCEL_FCOPY:
246 			error = hv_copy_cancel();
247 			break;
248 
249 		default:
250 			error = HV_E_FAIL;
251 			syslog(LOG_ERR, "Unknown operation: %d",
252 				buffer.hdr.operation);
253 
254 		}
255 
256 		/*
257 		 * pwrite() may return an error due to the faked CANCEL_FCOPY
258 		 * message upon hibernation. Ignore the error by resetting the
259 		 * dev file, i.e. closing and re-opening it.
260 		 */
261 		if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) {
262 			syslog(LOG_ERR, "pwrite failed: %s", strerror(errno));
263 			goto reopen_fcopy_fd;
264 		}
265 	}
266 }
267