Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tools: hv: Add new fcopy application based on uio driver

New fcopy application using uio_hv_generic driver. This application
copies file from Hyper-V host to guest VM.

A big part of this code is copied from tools/hv/hv_fcopy_daemon.c
which this new application is replacing.

Signed-off-by: Saurabh Sengar <ssengar@linux.microsoft.com>
Reviewed-by: Long Li <longli@microsoft.com>
Link: https://lore.kernel.org/r/1711788723-8593-6-git-send-email-ssengar@linux.microsoft.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Saurabh Sengar and committed by
Greg Kroah-Hartman
82b0945c 45bab4d7

+501 -6
+2 -1
tools/hv/Build
··· 1 1 hv_kvp_daemon-y += hv_kvp_daemon.o 2 2 hv_vss_daemon-y += hv_vss_daemon.o 3 - hv_fcopy_daemon-y += hv_fcopy_daemon.o 3 + hv_fcopy_uio_daemon-y += hv_fcopy_uio_daemon.o 4 + hv_fcopy_uio_daemon-y += vmbus_bufring.o
+9 -5
tools/hv/Makefile
··· 2 2 # Makefile for Hyper-V tools 3 3 include ../scripts/Makefile.include 4 4 5 + ARCH := $(shell uname -m 2>/dev/null) 5 6 sbindir ?= /usr/sbin 6 7 libexecdir ?= /usr/libexec 7 8 sharedstatedir ?= /var/lib ··· 18 17 19 18 override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include 20 19 21 - ALL_TARGETS := hv_kvp_daemon hv_vss_daemon hv_fcopy_daemon 20 + ALL_TARGETS := hv_kvp_daemon hv_vss_daemon 21 + ifneq ($(ARCH), aarch64) 22 + ALL_TARGETS += hv_fcopy_uio_daemon 23 + endif 22 24 ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) 23 25 24 26 ALL_SCRIPTS := hv_get_dhcp_info.sh hv_get_dns_info.sh hv_set_ifconfig.sh ··· 43 39 $(OUTPUT)hv_vss_daemon: $(HV_VSS_DAEMON_IN) 44 40 $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ 45 41 46 - HV_FCOPY_DAEMON_IN := $(OUTPUT)hv_fcopy_daemon-in.o 47 - $(HV_FCOPY_DAEMON_IN): FORCE 48 - $(Q)$(MAKE) $(build)=hv_fcopy_daemon 49 - $(OUTPUT)hv_fcopy_daemon: $(HV_FCOPY_DAEMON_IN) 42 + HV_FCOPY_UIO_DAEMON_IN := $(OUTPUT)hv_fcopy_uio_daemon-in.o 43 + $(HV_FCOPY_UIO_DAEMON_IN): FORCE 44 + $(Q)$(MAKE) $(build)=hv_fcopy_uio_daemon 45 + $(OUTPUT)hv_fcopy_uio_daemon: $(HV_FCOPY_UIO_DAEMON_IN) 50 46 $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ 51 47 52 48 clean:
+490
tools/hv/hv_fcopy_uio_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) 2023, Microsoft, Inc. 6 + * 7 + * Author : K. Y. Srinivasan <kys@microsoft.com> 8 + * Author : Saurabh Sengar <ssengar@microsoft.com> 9 + * 10 + */ 11 + 12 + #include <dirent.h> 13 + #include <errno.h> 14 + #include <fcntl.h> 15 + #include <getopt.h> 16 + #include <locale.h> 17 + #include <stdbool.h> 18 + #include <stddef.h> 19 + #include <stdint.h> 20 + #include <stdio.h> 21 + #include <stdlib.h> 22 + #include <string.h> 23 + #include <syslog.h> 24 + #include <unistd.h> 25 + #include <wchar.h> 26 + #include <sys/stat.h> 27 + #include <linux/hyperv.h> 28 + #include <linux/limits.h> 29 + #include "vmbus_bufring.h" 30 + 31 + #define ICMSGTYPE_NEGOTIATE 0 32 + #define ICMSGTYPE_FCOPY 7 33 + 34 + #define WIN8_SRV_MAJOR 1 35 + #define WIN8_SRV_MINOR 1 36 + #define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR) 37 + 38 + #define MAX_FOLDER_NAME 15 39 + #define MAX_PATH_LEN 15 40 + #define FCOPY_UIO "/sys/bus/vmbus/devices/eb765408-105f-49b6-b4aa-c123b64d17d4/uio" 41 + 42 + #define FCOPY_VER_COUNT 1 43 + static const int fcopy_versions[] = { 44 + WIN8_SRV_VERSION 45 + }; 46 + 47 + #define FW_VER_COUNT 1 48 + static const int fw_versions[] = { 49 + UTIL_FW_VERSION 50 + }; 51 + 52 + #define HV_RING_SIZE 0x4000 /* 16KB ring buffer size */ 53 + 54 + unsigned char desc[HV_RING_SIZE]; 55 + 56 + static int target_fd; 57 + static char target_fname[PATH_MAX]; 58 + static unsigned long long filesize; 59 + 60 + static int hv_fcopy_create_file(char *file_name, char *path_name, __u32 flags) 61 + { 62 + int error = HV_E_FAIL; 63 + char *q, *p; 64 + 65 + filesize = 0; 66 + p = path_name; 67 + snprintf(target_fname, sizeof(target_fname), "%s/%s", 68 + path_name, file_name); 69 + 70 + /* 71 + * Check to see if the path is already in place; if not, 72 + * create if required. 73 + */ 74 + while ((q = strchr(p, '/')) != NULL) { 75 + if (q == p) { 76 + p++; 77 + continue; 78 + } 79 + *q = '\0'; 80 + if (access(path_name, F_OK)) { 81 + if (flags & CREATE_PATH) { 82 + if (mkdir(path_name, 0755)) { 83 + syslog(LOG_ERR, "Failed to create %s", 84 + path_name); 85 + goto done; 86 + } 87 + } else { 88 + syslog(LOG_ERR, "Invalid path: %s", path_name); 89 + goto done; 90 + } 91 + } 92 + p = q + 1; 93 + *q = '/'; 94 + } 95 + 96 + if (!access(target_fname, F_OK)) { 97 + syslog(LOG_INFO, "File: %s exists", target_fname); 98 + if (!(flags & OVER_WRITE)) { 99 + error = HV_ERROR_ALREADY_EXISTS; 100 + goto done; 101 + } 102 + } 103 + 104 + target_fd = open(target_fname, 105 + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744); 106 + if (target_fd == -1) { 107 + syslog(LOG_INFO, "Open Failed: %s", strerror(errno)); 108 + goto done; 109 + } 110 + 111 + error = 0; 112 + done: 113 + if (error) 114 + target_fname[0] = '\0'; 115 + return error; 116 + } 117 + 118 + /* copy the data into the file */ 119 + static int hv_copy_data(struct hv_do_fcopy *cpmsg) 120 + { 121 + ssize_t len; 122 + int ret = 0; 123 + 124 + len = pwrite(target_fd, cpmsg->data, cpmsg->size, cpmsg->offset); 125 + 126 + filesize += cpmsg->size; 127 + if (len != cpmsg->size) { 128 + switch (errno) { 129 + case ENOSPC: 130 + ret = HV_ERROR_DISK_FULL; 131 + break; 132 + default: 133 + ret = HV_E_FAIL; 134 + break; 135 + } 136 + syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)", 137 + filesize, (long)len, strerror(errno)); 138 + } 139 + 140 + return ret; 141 + } 142 + 143 + static int hv_copy_finished(void) 144 + { 145 + close(target_fd); 146 + target_fname[0] = '\0'; 147 + 148 + return 0; 149 + } 150 + 151 + static void print_usage(char *argv[]) 152 + { 153 + fprintf(stderr, "Usage: %s [options]\n" 154 + "Options are:\n" 155 + " -n, --no-daemon stay in foreground, don't daemonize\n" 156 + " -h, --help print this help\n", argv[0]); 157 + } 158 + 159 + static bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, unsigned char *buf, 160 + unsigned int buflen, const int *fw_version, int fw_vercnt, 161 + const int *srv_version, int srv_vercnt, 162 + int *nego_fw_version, int *nego_srv_version) 163 + { 164 + int icframe_major, icframe_minor; 165 + int icmsg_major, icmsg_minor; 166 + int fw_major, fw_minor; 167 + int srv_major, srv_minor; 168 + int i, j; 169 + bool found_match = false; 170 + struct icmsg_negotiate *negop; 171 + 172 + /* Check that there's enough space for icframe_vercnt, icmsg_vercnt */ 173 + if (buflen < ICMSG_HDR + offsetof(struct icmsg_negotiate, reserved)) { 174 + syslog(LOG_ERR, "Invalid icmsg negotiate"); 175 + return false; 176 + } 177 + 178 + icmsghdrp->icmsgsize = 0x10; 179 + negop = (struct icmsg_negotiate *)&buf[ICMSG_HDR]; 180 + 181 + icframe_major = negop->icframe_vercnt; 182 + icframe_minor = 0; 183 + 184 + icmsg_major = negop->icmsg_vercnt; 185 + icmsg_minor = 0; 186 + 187 + /* Validate negop packet */ 188 + if (icframe_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || 189 + icmsg_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || 190 + ICMSG_NEGOTIATE_PKT_SIZE(icframe_major, icmsg_major) > buflen) { 191 + syslog(LOG_ERR, "Invalid icmsg negotiate - icframe_major: %u, icmsg_major: %u\n", 192 + icframe_major, icmsg_major); 193 + goto fw_error; 194 + } 195 + 196 + /* 197 + * Select the framework version number we will 198 + * support. 199 + */ 200 + 201 + for (i = 0; i < fw_vercnt; i++) { 202 + fw_major = (fw_version[i] >> 16); 203 + fw_minor = (fw_version[i] & 0xFFFF); 204 + 205 + for (j = 0; j < negop->icframe_vercnt; j++) { 206 + if (negop->icversion_data[j].major == fw_major && 207 + negop->icversion_data[j].minor == fw_minor) { 208 + icframe_major = negop->icversion_data[j].major; 209 + icframe_minor = negop->icversion_data[j].minor; 210 + found_match = true; 211 + break; 212 + } 213 + } 214 + 215 + if (found_match) 216 + break; 217 + } 218 + 219 + if (!found_match) 220 + goto fw_error; 221 + 222 + found_match = false; 223 + 224 + for (i = 0; i < srv_vercnt; i++) { 225 + srv_major = (srv_version[i] >> 16); 226 + srv_minor = (srv_version[i] & 0xFFFF); 227 + 228 + for (j = negop->icframe_vercnt; 229 + (j < negop->icframe_vercnt + negop->icmsg_vercnt); 230 + j++) { 231 + if (negop->icversion_data[j].major == srv_major && 232 + negop->icversion_data[j].minor == srv_minor) { 233 + icmsg_major = negop->icversion_data[j].major; 234 + icmsg_minor = negop->icversion_data[j].minor; 235 + found_match = true; 236 + break; 237 + } 238 + } 239 + 240 + if (found_match) 241 + break; 242 + } 243 + 244 + /* 245 + * Respond with the framework and service 246 + * version numbers we can support. 247 + */ 248 + fw_error: 249 + if (!found_match) { 250 + negop->icframe_vercnt = 0; 251 + negop->icmsg_vercnt = 0; 252 + } else { 253 + negop->icframe_vercnt = 1; 254 + negop->icmsg_vercnt = 1; 255 + } 256 + 257 + if (nego_fw_version) 258 + *nego_fw_version = (icframe_major << 16) | icframe_minor; 259 + 260 + if (nego_srv_version) 261 + *nego_srv_version = (icmsg_major << 16) | icmsg_minor; 262 + 263 + negop->icversion_data[0].major = icframe_major; 264 + negop->icversion_data[0].minor = icframe_minor; 265 + negop->icversion_data[1].major = icmsg_major; 266 + negop->icversion_data[1].minor = icmsg_minor; 267 + 268 + return found_match; 269 + } 270 + 271 + static void wcstoutf8(char *dest, const __u16 *src, size_t dest_size) 272 + { 273 + size_t len = 0; 274 + 275 + while (len < dest_size) { 276 + if (src[len] < 0x80) 277 + dest[len++] = (char)(*src++); 278 + else 279 + dest[len++] = 'X'; 280 + } 281 + 282 + dest[len] = '\0'; 283 + } 284 + 285 + static int hv_fcopy_start(struct hv_start_fcopy *smsg_in) 286 + { 287 + setlocale(LC_ALL, "en_US.utf8"); 288 + size_t file_size, path_size; 289 + char *file_name, *path_name; 290 + char *in_file_name = (char *)smsg_in->file_name; 291 + char *in_path_name = (char *)smsg_in->path_name; 292 + 293 + file_size = wcstombs(NULL, (const wchar_t *restrict)in_file_name, 0) + 1; 294 + path_size = wcstombs(NULL, (const wchar_t *restrict)in_path_name, 0) + 1; 295 + 296 + file_name = (char *)malloc(file_size * sizeof(char)); 297 + path_name = (char *)malloc(path_size * sizeof(char)); 298 + 299 + wcstoutf8(file_name, (__u16 *)in_file_name, file_size); 300 + wcstoutf8(path_name, (__u16 *)in_path_name, path_size); 301 + 302 + return hv_fcopy_create_file(file_name, path_name, smsg_in->copy_flags); 303 + } 304 + 305 + static int hv_fcopy_send_data(struct hv_fcopy_hdr *fcopy_msg, int recvlen) 306 + { 307 + int operation = fcopy_msg->operation; 308 + 309 + /* 310 + * The strings sent from the host are encoded in 311 + * utf16; convert it to utf8 strings. 312 + * The host assures us that the utf16 strings will not exceed 313 + * the max lengths specified. We will however, reserve room 314 + * for the string terminating character - in the utf16s_utf8s() 315 + * function we limit the size of the buffer where the converted 316 + * string is placed to W_MAX_PATH -1 to guarantee 317 + * that the strings can be properly terminated! 318 + */ 319 + 320 + switch (operation) { 321 + case START_FILE_COPY: 322 + return hv_fcopy_start((struct hv_start_fcopy *)fcopy_msg); 323 + case WRITE_TO_FILE: 324 + return hv_copy_data((struct hv_do_fcopy *)fcopy_msg); 325 + case COMPLETE_FCOPY: 326 + return hv_copy_finished(); 327 + } 328 + 329 + return HV_E_FAIL; 330 + } 331 + 332 + /* process the packet recv from host */ 333 + static int fcopy_pkt_process(struct vmbus_br *txbr) 334 + { 335 + int ret, offset, pktlen; 336 + int fcopy_srv_version; 337 + const struct vmbus_chanpkt_hdr *pkt; 338 + struct hv_fcopy_hdr *fcopy_msg; 339 + struct icmsg_hdr *icmsghdr; 340 + 341 + pkt = (const struct vmbus_chanpkt_hdr *)desc; 342 + offset = pkt->hlen << 3; 343 + pktlen = (pkt->tlen << 3) - offset; 344 + icmsghdr = (struct icmsg_hdr *)&desc[offset + sizeof(struct vmbuspipe_hdr)]; 345 + icmsghdr->status = HV_E_FAIL; 346 + 347 + if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) { 348 + if (vmbus_prep_negotiate_resp(icmsghdr, desc + offset, pktlen, fw_versions, 349 + FW_VER_COUNT, fcopy_versions, FCOPY_VER_COUNT, 350 + NULL, &fcopy_srv_version)) { 351 + syslog(LOG_INFO, "FCopy IC version %d.%d", 352 + fcopy_srv_version >> 16, fcopy_srv_version & 0xFFFF); 353 + icmsghdr->status = 0; 354 + } 355 + } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) { 356 + /* Ensure recvlen is big enough to contain hv_fcopy_hdr */ 357 + if (pktlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) { 358 + syslog(LOG_ERR, "Invalid Fcopy hdr. Packet length too small: %u", 359 + pktlen); 360 + return -ENOBUFS; 361 + } 362 + 363 + fcopy_msg = (struct hv_fcopy_hdr *)&desc[offset + ICMSG_HDR]; 364 + icmsghdr->status = hv_fcopy_send_data(fcopy_msg, pktlen); 365 + } 366 + 367 + icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE; 368 + ret = rte_vmbus_chan_send(txbr, 0x6, desc + offset, pktlen, 0); 369 + if (ret) { 370 + syslog(LOG_ERR, "Write to ringbuffer failed err: %d", ret); 371 + return ret; 372 + } 373 + 374 + return 0; 375 + } 376 + 377 + static void fcopy_get_first_folder(char *path, char *chan_no) 378 + { 379 + DIR *dir = opendir(path); 380 + struct dirent *entry; 381 + 382 + if (!dir) { 383 + syslog(LOG_ERR, "Failed to open directory (errno=%s).\n", strerror(errno)); 384 + return; 385 + } 386 + 387 + while ((entry = readdir(dir)) != NULL) { 388 + if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") != 0 && 389 + strcmp(entry->d_name, "..") != 0) { 390 + strcpy(chan_no, entry->d_name); 391 + break; 392 + } 393 + } 394 + 395 + closedir(dir); 396 + } 397 + 398 + int main(int argc, char *argv[]) 399 + { 400 + int fcopy_fd = -1, tmp = 1; 401 + int daemonize = 1, long_index = 0, opt, ret = -EINVAL; 402 + struct vmbus_br txbr, rxbr; 403 + void *ring; 404 + uint32_t len = HV_RING_SIZE; 405 + char uio_name[MAX_FOLDER_NAME] = {0}; 406 + char uio_dev_path[MAX_PATH_LEN] = {0}; 407 + 408 + static struct option long_options[] = { 409 + {"help", no_argument, 0, 'h' }, 410 + {"no-daemon", no_argument, 0, 'n' }, 411 + {0, 0, 0, 0 } 412 + }; 413 + 414 + while ((opt = getopt_long(argc, argv, "hn", long_options, 415 + &long_index)) != -1) { 416 + switch (opt) { 417 + case 'n': 418 + daemonize = 0; 419 + break; 420 + case 'h': 421 + default: 422 + print_usage(argv); 423 + goto exit; 424 + } 425 + } 426 + 427 + if (daemonize && daemon(1, 0)) { 428 + syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno)); 429 + goto exit; 430 + } 431 + 432 + openlog("HV_UIO_FCOPY", 0, LOG_USER); 433 + syslog(LOG_INFO, "starting; pid is:%d", getpid()); 434 + 435 + fcopy_get_first_folder(FCOPY_UIO, uio_name); 436 + snprintf(uio_dev_path, sizeof(uio_dev_path), "/dev/%s", uio_name); 437 + fcopy_fd = open(uio_dev_path, O_RDWR); 438 + 439 + if (fcopy_fd < 0) { 440 + syslog(LOG_ERR, "open %s failed; error: %d %s", 441 + uio_dev_path, errno, strerror(errno)); 442 + ret = fcopy_fd; 443 + goto exit; 444 + } 445 + 446 + ring = vmbus_uio_map(&fcopy_fd, HV_RING_SIZE); 447 + if (!ring) { 448 + ret = errno; 449 + syslog(LOG_ERR, "mmap ringbuffer failed; error: %d %s", ret, strerror(ret)); 450 + goto close; 451 + } 452 + vmbus_br_setup(&txbr, ring, HV_RING_SIZE); 453 + vmbus_br_setup(&rxbr, (char *)ring + HV_RING_SIZE, HV_RING_SIZE); 454 + 455 + rxbr.vbr->imask = 0; 456 + 457 + while (1) { 458 + /* 459 + * In this loop we process fcopy messages after the 460 + * handshake is complete. 461 + */ 462 + ret = pread(fcopy_fd, &tmp, sizeof(int), 0); 463 + if (ret < 0) { 464 + syslog(LOG_ERR, "pread failed: %s", strerror(errno)); 465 + continue; 466 + } 467 + 468 + len = HV_RING_SIZE; 469 + ret = rte_vmbus_chan_recv_raw(&rxbr, desc, &len); 470 + if (unlikely(ret <= 0)) { 471 + /* This indicates a failure to communicate (or worse) */ 472 + syslog(LOG_ERR, "VMBus channel recv error: %d", ret); 473 + } else { 474 + ret = fcopy_pkt_process(&txbr); 475 + if (ret < 0) 476 + goto close; 477 + 478 + /* Signal host */ 479 + if ((write(fcopy_fd, &tmp, sizeof(int))) != sizeof(int)) { 480 + ret = errno; 481 + syslog(LOG_ERR, "Signal to host failed: %s\n", strerror(ret)); 482 + goto close; 483 + } 484 + } 485 + } 486 + close: 487 + close(fcopy_fd); 488 + exit: 489 + return ret; 490 + }