linux/tools/hv/hv_fcopy_daemon.c
<<
>>
Prefs
   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
  24static int target_fd;
  25static char target_fname[PATH_MAX];
  26static unsigned long long filesize;
  27
  28static 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;
  82done:
  83        if (error)
  84                target_fname[0] = '\0';
  85        return error;
  86}
  87
  88static 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 */
 123static int hv_copy_finished(void)
 124{
 125        close(target_fd);
 126        target_fname[0] = '\0';
 127        return 0;
 128}
 129static 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
 140void 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
 148int 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
 189reopen_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