Merge tag 'smp-urgent-2020-02-09' of git://git.kernel.org/pub/scm/linux/kernel/git...
[platform/kernel/linux-starfive.git] / tools / hv / hv_fcopy_daemon.c
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
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
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  */
123 static int hv_copy_finished(void)
124 {
125         close(target_fd);
126         target_fname[0] = '\0';
127         return 0;
128 }
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
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
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 }