18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * An implementation of the host initiated guest snapshot for Hyper-V. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2013, Microsoft, Inc. 68c2ecf20Sopenharmony_ci * Author : K. Y. Srinivasan <kys@microsoft.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <sys/types.h> 118c2ecf20Sopenharmony_ci#include <sys/poll.h> 128c2ecf20Sopenharmony_ci#include <sys/ioctl.h> 138c2ecf20Sopenharmony_ci#include <sys/stat.h> 148c2ecf20Sopenharmony_ci#include <sys/sysmacros.h> 158c2ecf20Sopenharmony_ci#include <fcntl.h> 168c2ecf20Sopenharmony_ci#include <stdio.h> 178c2ecf20Sopenharmony_ci#include <mntent.h> 188c2ecf20Sopenharmony_ci#include <stdlib.h> 198c2ecf20Sopenharmony_ci#include <unistd.h> 208c2ecf20Sopenharmony_ci#include <string.h> 218c2ecf20Sopenharmony_ci#include <ctype.h> 228c2ecf20Sopenharmony_ci#include <errno.h> 238c2ecf20Sopenharmony_ci#include <linux/fs.h> 248c2ecf20Sopenharmony_ci#include <linux/major.h> 258c2ecf20Sopenharmony_ci#include <linux/hyperv.h> 268c2ecf20Sopenharmony_ci#include <syslog.h> 278c2ecf20Sopenharmony_ci#include <getopt.h> 288c2ecf20Sopenharmony_ci#include <stdbool.h> 298c2ecf20Sopenharmony_ci#include <dirent.h> 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic bool fs_frozen; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* Don't use syslog() in the function since that can cause write to disk */ 348c2ecf20Sopenharmony_cistatic int vss_do_freeze(char *dir, unsigned int cmd) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci int ret, fd = open(dir, O_RDONLY); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci if (fd < 0) 398c2ecf20Sopenharmony_ci return 1; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci ret = ioctl(fd, cmd, 0); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci /* 448c2ecf20Sopenharmony_ci * If a partition is mounted more than once, only the first 458c2ecf20Sopenharmony_ci * FREEZE/THAW can succeed and the later ones will get 468c2ecf20Sopenharmony_ci * EBUSY/EINVAL respectively: there could be 2 cases: 478c2ecf20Sopenharmony_ci * 1) a user may mount the same partition to different directories 488c2ecf20Sopenharmony_ci * by mistake or on purpose; 498c2ecf20Sopenharmony_ci * 2) The subvolume of btrfs appears to have the same partition 508c2ecf20Sopenharmony_ci * mounted more than once. 518c2ecf20Sopenharmony_ci */ 528c2ecf20Sopenharmony_ci if (ret) { 538c2ecf20Sopenharmony_ci if ((cmd == FIFREEZE && errno == EBUSY) || 548c2ecf20Sopenharmony_ci (cmd == FITHAW && errno == EINVAL)) { 558c2ecf20Sopenharmony_ci close(fd); 568c2ecf20Sopenharmony_ci return 0; 578c2ecf20Sopenharmony_ci } 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci close(fd); 618c2ecf20Sopenharmony_ci return !!ret; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic bool is_dev_loop(const char *blkname) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci char *buffer; 678c2ecf20Sopenharmony_ci DIR *dir; 688c2ecf20Sopenharmony_ci struct dirent *entry; 698c2ecf20Sopenharmony_ci bool ret = false; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci buffer = malloc(PATH_MAX); 728c2ecf20Sopenharmony_ci if (!buffer) { 738c2ecf20Sopenharmony_ci syslog(LOG_ERR, "Can't allocate memory!"); 748c2ecf20Sopenharmony_ci exit(1); 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci snprintf(buffer, PATH_MAX, "%s/loop", blkname); 788c2ecf20Sopenharmony_ci if (!access(buffer, R_OK | X_OK)) { 798c2ecf20Sopenharmony_ci ret = true; 808c2ecf20Sopenharmony_ci goto free_buffer; 818c2ecf20Sopenharmony_ci } else if (errno != ENOENT) { 828c2ecf20Sopenharmony_ci syslog(LOG_ERR, "Can't access: %s; error:%d %s!", 838c2ecf20Sopenharmony_ci buffer, errno, strerror(errno)); 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci snprintf(buffer, PATH_MAX, "%s/slaves", blkname); 878c2ecf20Sopenharmony_ci dir = opendir(buffer); 888c2ecf20Sopenharmony_ci if (!dir) { 898c2ecf20Sopenharmony_ci if (errno != ENOENT) 908c2ecf20Sopenharmony_ci syslog(LOG_ERR, "Can't opendir: %s; error:%d %s!", 918c2ecf20Sopenharmony_ci buffer, errno, strerror(errno)); 928c2ecf20Sopenharmony_ci goto free_buffer; 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci while ((entry = readdir(dir)) != NULL) { 968c2ecf20Sopenharmony_ci if (strcmp(entry->d_name, ".") == 0 || 978c2ecf20Sopenharmony_ci strcmp(entry->d_name, "..") == 0) 988c2ecf20Sopenharmony_ci continue; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci snprintf(buffer, PATH_MAX, "%s/slaves/%s", blkname, 1018c2ecf20Sopenharmony_ci entry->d_name); 1028c2ecf20Sopenharmony_ci if (is_dev_loop(buffer)) { 1038c2ecf20Sopenharmony_ci ret = true; 1048c2ecf20Sopenharmony_ci break; 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci closedir(dir); 1088c2ecf20Sopenharmony_cifree_buffer: 1098c2ecf20Sopenharmony_ci free(buffer); 1108c2ecf20Sopenharmony_ci return ret; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int vss_operate(int operation) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci char match[] = "/dev/"; 1168c2ecf20Sopenharmony_ci FILE *mounts; 1178c2ecf20Sopenharmony_ci struct mntent *ent; 1188c2ecf20Sopenharmony_ci struct stat sb; 1198c2ecf20Sopenharmony_ci char errdir[1024] = {0}; 1208c2ecf20Sopenharmony_ci char blkdir[23]; /* /sys/dev/block/XXX:XXX */ 1218c2ecf20Sopenharmony_ci unsigned int cmd; 1228c2ecf20Sopenharmony_ci int error = 0, root_seen = 0, save_errno = 0; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci switch (operation) { 1258c2ecf20Sopenharmony_ci case VSS_OP_FREEZE: 1268c2ecf20Sopenharmony_ci cmd = FIFREEZE; 1278c2ecf20Sopenharmony_ci break; 1288c2ecf20Sopenharmony_ci case VSS_OP_THAW: 1298c2ecf20Sopenharmony_ci cmd = FITHAW; 1308c2ecf20Sopenharmony_ci break; 1318c2ecf20Sopenharmony_ci default: 1328c2ecf20Sopenharmony_ci return -1; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci mounts = setmntent("/proc/mounts", "r"); 1368c2ecf20Sopenharmony_ci if (mounts == NULL) 1378c2ecf20Sopenharmony_ci return -1; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci while ((ent = getmntent(mounts))) { 1408c2ecf20Sopenharmony_ci if (strncmp(ent->mnt_fsname, match, strlen(match))) 1418c2ecf20Sopenharmony_ci continue; 1428c2ecf20Sopenharmony_ci if (stat(ent->mnt_fsname, &sb)) { 1438c2ecf20Sopenharmony_ci syslog(LOG_ERR, "Can't stat: %s; error:%d %s!", 1448c2ecf20Sopenharmony_ci ent->mnt_fsname, errno, strerror(errno)); 1458c2ecf20Sopenharmony_ci } else { 1468c2ecf20Sopenharmony_ci sprintf(blkdir, "/sys/dev/block/%d:%d", 1478c2ecf20Sopenharmony_ci major(sb.st_rdev), minor(sb.st_rdev)); 1488c2ecf20Sopenharmony_ci if (is_dev_loop(blkdir)) 1498c2ecf20Sopenharmony_ci continue; 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci if (hasmntopt(ent, MNTOPT_RO) != NULL) 1528c2ecf20Sopenharmony_ci continue; 1538c2ecf20Sopenharmony_ci if (strcmp(ent->mnt_type, "vfat") == 0) 1548c2ecf20Sopenharmony_ci continue; 1558c2ecf20Sopenharmony_ci if (strcmp(ent->mnt_dir, "/") == 0) { 1568c2ecf20Sopenharmony_ci root_seen = 1; 1578c2ecf20Sopenharmony_ci continue; 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci error |= vss_do_freeze(ent->mnt_dir, cmd); 1608c2ecf20Sopenharmony_ci if (operation == VSS_OP_FREEZE) { 1618c2ecf20Sopenharmony_ci if (error) 1628c2ecf20Sopenharmony_ci goto err; 1638c2ecf20Sopenharmony_ci fs_frozen = true; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci endmntent(mounts); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci if (root_seen) { 1708c2ecf20Sopenharmony_ci error |= vss_do_freeze("/", cmd); 1718c2ecf20Sopenharmony_ci if (operation == VSS_OP_FREEZE) { 1728c2ecf20Sopenharmony_ci if (error) 1738c2ecf20Sopenharmony_ci goto err; 1748c2ecf20Sopenharmony_ci fs_frozen = true; 1758c2ecf20Sopenharmony_ci } 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci if (operation == VSS_OP_THAW && !error) 1798c2ecf20Sopenharmony_ci fs_frozen = false; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci goto out; 1828c2ecf20Sopenharmony_cierr: 1838c2ecf20Sopenharmony_ci save_errno = errno; 1848c2ecf20Sopenharmony_ci if (ent) { 1858c2ecf20Sopenharmony_ci strncpy(errdir, ent->mnt_dir, sizeof(errdir)-1); 1868c2ecf20Sopenharmony_ci endmntent(mounts); 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci vss_operate(VSS_OP_THAW); 1898c2ecf20Sopenharmony_ci fs_frozen = false; 1908c2ecf20Sopenharmony_ci /* Call syslog after we thaw all filesystems */ 1918c2ecf20Sopenharmony_ci if (ent) 1928c2ecf20Sopenharmony_ci syslog(LOG_ERR, "FREEZE of %s failed; error:%d %s", 1938c2ecf20Sopenharmony_ci errdir, save_errno, strerror(save_errno)); 1948c2ecf20Sopenharmony_ci else 1958c2ecf20Sopenharmony_ci syslog(LOG_ERR, "FREEZE of / failed; error:%d %s", save_errno, 1968c2ecf20Sopenharmony_ci strerror(save_errno)); 1978c2ecf20Sopenharmony_ciout: 1988c2ecf20Sopenharmony_ci return error; 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_civoid print_usage(char *argv[]) 2028c2ecf20Sopenharmony_ci{ 2038c2ecf20Sopenharmony_ci fprintf(stderr, "Usage: %s [options]\n" 2048c2ecf20Sopenharmony_ci "Options are:\n" 2058c2ecf20Sopenharmony_ci " -n, --no-daemon stay in foreground, don't daemonize\n" 2068c2ecf20Sopenharmony_ci " -h, --help print this help\n", argv[0]); 2078c2ecf20Sopenharmony_ci} 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ciint main(int argc, char *argv[]) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci int vss_fd = -1, len; 2128c2ecf20Sopenharmony_ci int error; 2138c2ecf20Sopenharmony_ci struct pollfd pfd; 2148c2ecf20Sopenharmony_ci int op; 2158c2ecf20Sopenharmony_ci struct hv_vss_msg vss_msg[1]; 2168c2ecf20Sopenharmony_ci int daemonize = 1, long_index = 0, opt; 2178c2ecf20Sopenharmony_ci int in_handshake; 2188c2ecf20Sopenharmony_ci __u32 kernel_modver; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci static struct option long_options[] = { 2218c2ecf20Sopenharmony_ci {"help", no_argument, 0, 'h' }, 2228c2ecf20Sopenharmony_ci {"no-daemon", no_argument, 0, 'n' }, 2238c2ecf20Sopenharmony_ci {0, 0, 0, 0 } 2248c2ecf20Sopenharmony_ci }; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci while ((opt = getopt_long(argc, argv, "hn", long_options, 2278c2ecf20Sopenharmony_ci &long_index)) != -1) { 2288c2ecf20Sopenharmony_ci switch (opt) { 2298c2ecf20Sopenharmony_ci case 'n': 2308c2ecf20Sopenharmony_ci daemonize = 0; 2318c2ecf20Sopenharmony_ci break; 2328c2ecf20Sopenharmony_ci case 'h': 2338c2ecf20Sopenharmony_ci print_usage(argv); 2348c2ecf20Sopenharmony_ci exit(0); 2358c2ecf20Sopenharmony_ci default: 2368c2ecf20Sopenharmony_ci print_usage(argv); 2378c2ecf20Sopenharmony_ci exit(EXIT_FAILURE); 2388c2ecf20Sopenharmony_ci } 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci if (daemonize && daemon(1, 0)) 2428c2ecf20Sopenharmony_ci return 1; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci openlog("Hyper-V VSS", 0, LOG_USER); 2458c2ecf20Sopenharmony_ci syslog(LOG_INFO, "VSS starting; pid is:%d", getpid()); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_cireopen_vss_fd: 2488c2ecf20Sopenharmony_ci if (vss_fd != -1) 2498c2ecf20Sopenharmony_ci close(vss_fd); 2508c2ecf20Sopenharmony_ci if (fs_frozen) { 2518c2ecf20Sopenharmony_ci if (vss_operate(VSS_OP_THAW) || fs_frozen) { 2528c2ecf20Sopenharmony_ci syslog(LOG_ERR, "failed to thaw file system: err=%d", 2538c2ecf20Sopenharmony_ci errno); 2548c2ecf20Sopenharmony_ci exit(EXIT_FAILURE); 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci in_handshake = 1; 2598c2ecf20Sopenharmony_ci vss_fd = open("/dev/vmbus/hv_vss", O_RDWR); 2608c2ecf20Sopenharmony_ci if (vss_fd < 0) { 2618c2ecf20Sopenharmony_ci syslog(LOG_ERR, "open /dev/vmbus/hv_vss failed; error: %d %s", 2628c2ecf20Sopenharmony_ci errno, strerror(errno)); 2638c2ecf20Sopenharmony_ci exit(EXIT_FAILURE); 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci /* 2668c2ecf20Sopenharmony_ci * Register ourselves with the kernel. 2678c2ecf20Sopenharmony_ci */ 2688c2ecf20Sopenharmony_ci vss_msg->vss_hdr.operation = VSS_OP_REGISTER1; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); 2718c2ecf20Sopenharmony_ci if (len < 0) { 2728c2ecf20Sopenharmony_ci syslog(LOG_ERR, "registration to kernel failed; error: %d %s", 2738c2ecf20Sopenharmony_ci errno, strerror(errno)); 2748c2ecf20Sopenharmony_ci close(vss_fd); 2758c2ecf20Sopenharmony_ci exit(EXIT_FAILURE); 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci pfd.fd = vss_fd; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci while (1) { 2818c2ecf20Sopenharmony_ci pfd.events = POLLIN; 2828c2ecf20Sopenharmony_ci pfd.revents = 0; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci if (poll(&pfd, 1, -1) < 0) { 2858c2ecf20Sopenharmony_ci syslog(LOG_ERR, "poll failed; error:%d %s", errno, strerror(errno)); 2868c2ecf20Sopenharmony_ci if (errno == EINVAL) { 2878c2ecf20Sopenharmony_ci close(vss_fd); 2888c2ecf20Sopenharmony_ci exit(EXIT_FAILURE); 2898c2ecf20Sopenharmony_ci } 2908c2ecf20Sopenharmony_ci else 2918c2ecf20Sopenharmony_ci continue; 2928c2ecf20Sopenharmony_ci } 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci len = read(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_ci if (in_handshake) { 2978c2ecf20Sopenharmony_ci if (len != sizeof(kernel_modver)) { 2988c2ecf20Sopenharmony_ci syslog(LOG_ERR, "invalid version negotiation"); 2998c2ecf20Sopenharmony_ci exit(EXIT_FAILURE); 3008c2ecf20Sopenharmony_ci } 3018c2ecf20Sopenharmony_ci kernel_modver = *(__u32 *)vss_msg; 3028c2ecf20Sopenharmony_ci in_handshake = 0; 3038c2ecf20Sopenharmony_ci syslog(LOG_INFO, "VSS: kernel module version: %d", 3048c2ecf20Sopenharmony_ci kernel_modver); 3058c2ecf20Sopenharmony_ci continue; 3068c2ecf20Sopenharmony_ci } 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci if (len != sizeof(struct hv_vss_msg)) { 3098c2ecf20Sopenharmony_ci syslog(LOG_ERR, "read failed; error:%d %s", 3108c2ecf20Sopenharmony_ci errno, strerror(errno)); 3118c2ecf20Sopenharmony_ci goto reopen_vss_fd; 3128c2ecf20Sopenharmony_ci } 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci op = vss_msg->vss_hdr.operation; 3158c2ecf20Sopenharmony_ci error = HV_S_OK; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci switch (op) { 3188c2ecf20Sopenharmony_ci case VSS_OP_FREEZE: 3198c2ecf20Sopenharmony_ci case VSS_OP_THAW: 3208c2ecf20Sopenharmony_ci error = vss_operate(op); 3218c2ecf20Sopenharmony_ci syslog(LOG_INFO, "VSS: op=%s: %s\n", 3228c2ecf20Sopenharmony_ci op == VSS_OP_FREEZE ? "FREEZE" : "THAW", 3238c2ecf20Sopenharmony_ci error ? "failed" : "succeeded"); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci if (error) { 3268c2ecf20Sopenharmony_ci error = HV_E_FAIL; 3278c2ecf20Sopenharmony_ci syslog(LOG_ERR, "op=%d failed!", op); 3288c2ecf20Sopenharmony_ci syslog(LOG_ERR, "report it with these files:"); 3298c2ecf20Sopenharmony_ci syslog(LOG_ERR, "/etc/fstab and /proc/mounts"); 3308c2ecf20Sopenharmony_ci } 3318c2ecf20Sopenharmony_ci break; 3328c2ecf20Sopenharmony_ci case VSS_OP_HOT_BACKUP: 3338c2ecf20Sopenharmony_ci syslog(LOG_INFO, "VSS: op=CHECK HOT BACKUP\n"); 3348c2ecf20Sopenharmony_ci break; 3358c2ecf20Sopenharmony_ci default: 3368c2ecf20Sopenharmony_ci syslog(LOG_ERR, "Illegal op:%d\n", op); 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci /* 3408c2ecf20Sopenharmony_ci * The write() may return an error due to the faked VSS_OP_THAW 3418c2ecf20Sopenharmony_ci * message upon hibernation. Ignore the error by resetting the 3428c2ecf20Sopenharmony_ci * dev file, i.e. closing and re-opening it. 3438c2ecf20Sopenharmony_ci */ 3448c2ecf20Sopenharmony_ci vss_msg->error = error; 3458c2ecf20Sopenharmony_ci len = write(vss_fd, vss_msg, sizeof(struct hv_vss_msg)); 3468c2ecf20Sopenharmony_ci if (len != sizeof(struct hv_vss_msg)) { 3478c2ecf20Sopenharmony_ci syslog(LOG_ERR, "write failed; error: %d %s", errno, 3488c2ecf20Sopenharmony_ci strerror(errno)); 3498c2ecf20Sopenharmony_ci goto reopen_vss_fd; 3508c2ecf20Sopenharmony_ci } 3518c2ecf20Sopenharmony_ci } 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_ci close(vss_fd); 3548c2ecf20Sopenharmony_ci exit(0); 3558c2ecf20Sopenharmony_ci} 356