From patchwork Thu Sep 22 13:19:57 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Ivanov X-Patchwork-Id: 12985311 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 9D65BC6FA86 for ; Thu, 22 Sep 2022 14:33:49 +0000 (UTC) Received: from localhost ([::1]:59702 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1obNGu-0000S5-Dr for qemu-devel@archiver.kernel.org; Thu, 22 Sep 2022 10:33:48 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:40666) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8f-0001YA-KT for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:21:26 -0400 Received: from mail-eopbgr60136.outbound.protection.outlook.com ([40.107.6.136]:7326 helo=EUR04-DB3-obe.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8W-0004pt-6R for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:21:08 -0400 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=ARNP1AMvp4Q8NCYzjjMGyRYAiorqM9NTnqto6KKPcx7YE7VqjvwEBr9z8Yakbsd4ja1G4r1EFuuMB2heAIqc0oTMPXurWdU+PIPB6zCJPeF8WBSiXhbwzTKj2tlwe1VKi38+Zo65I+7IU/6uTUkj8NcDugZA+BvB7n6O3XwBp6+wFQTNpP9KlVm1miVHC24h7OUz8Rz/6bIEg6R2pihgRX9nwtry+JQafzKjEftxLQjF7wOvEeaszJDLxoS/oGkQS3ImY4ZyWjbUQve+oo6TdNIQ0rQfTYMhG9q6n2yCAkmY/lw9408AKvt86VxnAa3LcKt8Fz/NHHiODcnK53HEvg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=ihseUROg9ZqBMMrrLOa6+A3f4Xw78nf99JdxsBQn0Wg=; b=IvKohOF4T+ugr6gVx3FNFLX45AyJSys3PeaSKliejMKR0AmfZOj6ws6D1PZ/LmaYp483xZjFS0e9sS3QlgPpxou5yh/gSUnPnspCF8HI/lJcbUUI73az2l/8E6I5yJcNsd0Z8iPmAWP8XyxmStEA1O+IlbaXwO0XxBWO/LNE5nj99IuPPYgVj+VrZ4vm3reIRW0wTTvekAr0GRvzoO+4nZ/d3eN6zpmFucqEz9fHut2tY95r4Ae6V2eKENDBEnkx6JuY/fNVpGQoB9qfMQ1xGstqqlpph6kupZ9SpOwmoHqCVCUS03ofAkAMdlyT8UtQFQCDvA4TKMeuuErrGMJd8Q== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=virtuozzo.com; dmarc=pass action=none header.from=virtuozzo.com; dkim=pass header.d=virtuozzo.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=virtuozzo.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=ihseUROg9ZqBMMrrLOa6+A3f4Xw78nf99JdxsBQn0Wg=; b=cZCEnji3HNUIWUwOjXXqnTSDUGHBomb3E6oxXN+X2bc3nKmaPZaC71la68QooniPkZT9gvpAc+E4OXNWKixa5YUerKncY1FqnHFFjw8Cj0zEyP1SgZP8FQKMkpq0dNdEoMEjO9DRot6Ia0kaQfkh7k/W7dOUZnvElj7n3YDE6MupAceZcLZ2hkrba9sH4pkCA7CzMCeMMqTmOJAFbF2NNWLECCbipnJFIvyihmc2hN/wsejqKoftCRwhy3zusQUnLa2lqN8nl6+UiJEL8Iu1Er/JOhg7iewRkG+qLLjsg+JfZD8oRtlob3FiKNdANH/hJjjuFwwy/QmqsO6gFu5/tg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=virtuozzo.com; Received: from AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) by PAXPR08MB6480.eurprd08.prod.outlook.com (2603:10a6:102:155::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5654.16; Thu, 22 Sep 2022 13:20:12 +0000 Received: from AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b]) by AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b%8]) with mapi id 15.20.5654.016; Thu, 22 Sep 2022 13:20:12 +0000 From: Alexander Ivanov To: qemu-devel@nongnu.org Cc: den@virtuozzo.com, michael.roth@amd.com, kkostiuk@redhat.com Subject: [PATCH 1/5] qga: Move Linux-specific commands code to separate file Date: Thu, 22 Sep 2022 15:19:57 +0200 Message-Id: <20220922132001.940334-2-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> References: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> X-ClientProxiedBy: FR0P281CA0083.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:1e::18) To AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: AS8PR08MB7095:EE_|PAXPR08MB6480:EE_ X-MS-Office365-Filtering-Correlation-Id: 53704be6-a1b0-4874-77d8-08da9c9d2dc6 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: 1BmV6Hsn6fNzaiW4opx2c5QG2Bk3+zk2OMeb3iPG0AayyirN2VajPd6wiaudwFxe55PkGrWbI9XMro5/K7rzG21iu0IEhwNH+KSlj/mIb5AFlnLnR1JHjfzAVelX94gV7Qfyzh8J29GSnHVsfdZSZFKpPWJlPwt+a4qNKyQs3RL865P3hEUnD6hdt2j10DZHjYWf9VvDTn8A3Vi4vzgCq4xuDHbS9kQbbr8scvtU71l+DzhqTI/xuXcEzxFFzav79fVsjRX6nl3kRM9w3s96CLUg8RECIhMiMu6Mb8R+EzLhYea0NWjpcjw01ppjvkY+2bErgrp3WPPcxK+O/ttdT7jVr4WZ2BV6TtAUpwvMjDUmg8y6uvKGSu48wfi1u9Y3WWkUKjs3tG1pfxSlqGeKbBnX3h1/fWMkLSnp2UUTav4MtE6BqJ8WDpAjHbGa17UlKhhNjBu63cWKgFmKrEvq48aecDizWV/B/mgPlmetFsNxhqSVwy4dT3E3ZSfyskOu7mKBw2cfGI+og4/x6n7rarblTRzyBIiJ5eIo6ttmy5Bg3/W50Odtk10cc/aqwUzl2u2wE1MXouu/zdhD+m1YZR3TbsXo9wh/8Xr/IMSW7twVsWq+oPmtULq7PJh2/RbUdsdwnxzx0LJawqEQPJ7VKf0MuKQVCez643Orqk+fFTA4o5AmQ50hCTFkgoVY869057kHvMfOLltq3o8Wznpal5g/CACsUHcplxPYnooFcS+kkhLZ+s31GdWCLJYR77FrWZVijw3wfZHUtBEjAmzwb5mu0iwuDm7FvZ2uDBl11KM= X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:AS8PR08MB7095.eurprd08.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230022)(4636009)(346002)(366004)(376002)(136003)(396003)(39850400004)(451199015)(41300700001)(6486002)(478600001)(6666004)(52116002)(36756003)(66476007)(66556008)(316002)(186003)(66946007)(83380400001)(1076003)(5660300002)(2616005)(8936002)(26005)(6506007)(6916009)(86362001)(8676002)(44832011)(4326008)(2906002)(6512007)(30864003)(38350700002)(38100700002)(21314003)(579004)(559001); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: lyAW6eF5HxTPIvgiBLY/aJVGhCpKn40Pbx78BL7AWAihsS+4KiohPfpeUNUlOLBpNQ+HBA2nsBqHHNsN2SY+xBTOPdROLK3i8btqGcpHPpop5ZtvvvRMH8aNmS5v7ExELH39wrGXk03fCG+aGYMzELJVQXNLmBNlLFqLQ7TtDyElWPpbqXzxX//kmZl7XqN+IoKFJ0jNaDBauTxseKg47ZfTnQUxLyRu02FfxQXK0x54KdbIhaegkqZluD1V4UgNFZyvJ2Q1g6iEnT1yOJKpxlCqWdMr3dxckwXwtr6awnkhpTolWYPCBmcXLUCMaIWWx41UDFD0Gj4AofmbxvJfxVdP3MJm9GhirP669YzHQv58iKwGx+JC+dNeyqXg0NMgwbXahsHA9ZJ64uAFf1mYqbDRe3JCmmqkyv4ta7U0j8de0agVoK18crTXSfXiswgplBAiciOb2WudZXSyHNHuEq5BABbu6iH5YWIPBX6Ye89rvU3Ew+l6YleAOmLzfdnsnPcEljwlHBKHIxqyOCxFoW3eR4RK/AAdjcawNww8ozxQG6qZMQKOxIUy+6rVLBs33a4KzzL+w/xVpKfsNABufnzSKWnmNefp5/FDEc90YSDqpV2GWNX5DpoPxpqoI5xOypAIWpqNs4As8pV77fLlLQYJJ0pP2dYY3Fg3gc7TJ2Q7w+UAcdlv8pzfRwffyTdyp4VMg+qZdt1fUdTEB01izC2sSJbvDX/9MLEggZ9X2ZqRtnvL2RYtpp5vuQtFg8bcX81MJKx5eaqcLenOcfpL9o9/nBlCBnwnG/oYv7M+QHgCLsY51qITyzztp3Kbsb9RdFO/bD5piWwasuGIfYkypSDbE+95HmX9OtenGNkISejdcCcbrkXcCqlKnkUVkEl//WnulHkRkymvT12q0KsjMnP1Hq+jfrhf4WkTTeryTXM5u/tzsMIkz9b5sueYwXNGtleYtwaobtnvZPMFRaAZSDGpZbsleYCYMfxTHhx31jczwkJjr8UisxzzPupV9y+pr0qx1yXLJBNhKUEojE2/FZhSl1pd0GxKjuLTDibdFArg0g0kr44WMR2gK/D9Jx1chIC+DWdvGA5+hZfTq8R7qhE2igVOMreV2IT8qb7apCZeFdntNT78aPTbVypq1TtTOjiP6qbvA0/9goKPZ3Sk+L+ID65g2KGWCAXrMxmzh8xkerhjENjKlru3+idyy6x5WLjEBkux42ZV2MLTWXNrIyphu+SzskX8kWgN8x9d5zFqSgQElWiB5xC4F4Pk45hll/Nqphvj2+I4RHBmWN4HCCeBrWNxuhuswmlB3wmueUBrmaggB1e1YlAN2tYlv7vR8XO3MeRhur57UyIRFBjTyMTDmbXOOXnwBiq/QiwxbdZQ3NGVaj3nXBpeAuLm/KOFYFlcv7MAtAiMLRAEEmCfINVvdOP/b4RDTUU4hVDOwovgq9H5apY1KUdIH0223vwugtQssbSkfgXv77BpRBBg5us9S6wh/K2EhWVWYXOphDO6wa1Xa/E8IKeaV4VEBEctbjnkjqKgzEwVykYkqhIzGT/MhVAcxJsFHJJ80vT/fJVezDDywyBaeSag5ktHHZPn1Hv53qNwdU10l8gDL3wpctuACCrzSpVNd0O1A0KMKis= X-OriginatorOrg: virtuozzo.com X-MS-Exchange-CrossTenant-Network-Message-Id: 53704be6-a1b0-4874-77d8-08da9c9d2dc6 X-MS-Exchange-CrossTenant-AuthSource: AS8PR08MB7095.eurprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Sep 2022 13:20:12.2500 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 0bc7f26d-0264-416e-a6fc-8352af79c58f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: Pdpm5D6HNY+Ac4CvKy/aibYZX/wZaXZvBPHNZgJjmvTMLSAsLZRfI6utdmtnO2KPwE1NfoJUUPxwdzQyoJwGrBptcZSyaBZ0rCCPb9WujPw= X-MS-Exchange-Transport-CrossTenantHeadersStamped: PAXPR08MB6480 Received-SPF: pass client-ip=40.107.6.136; envelope-from=alexander.ivanov@virtuozzo.com; helo=EUR04-DB3-obe.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" In the next patches we are going to add FreeBSD support for QEMU Guest Agent. In the result, code in commands-posix.c will be too cumbersome. Move Linux-specific code to a separate file keeping common POSIX code in commands-posix.c. Signed-off-by: Alexander Ivanov --- qga/commands-common.h | 34 + qga/commands-linux.c | 2242 ++++++++++++++++++++++++++++++++++++ qga/commands-posix.c | 2558 +++-------------------------------------- qga/meson.build | 3 + 4 files changed, 2447 insertions(+), 2390 deletions(-) create mode 100644 qga/commands-linux.c diff --git a/qga/commands-common.h b/qga/commands-common.h index d0e4a9696f..aa0472ea4c 100644 --- a/qga/commands-common.h +++ b/qga/commands-common.h @@ -29,4 +29,38 @@ GuestFileRead *guest_file_read_unsafe(GuestFileHandle *gfh, */ char *qga_get_host_name(Error **errp); +void ga_wait_child(pid_t pid, int *status, Error **errp); + +#if defined(__linux__) +#include +#ifdef FIFREEZE +#define CONFIG_FSFREEZE #endif +#ifdef FITRIM +#define CONFIG_FSTRIM +#endif +#endif /* __linux__*/ + +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) +typedef struct FsMount { + char *dirname; + char *devtype; + unsigned int devmajor, devminor; + QTAILQ_ENTRY(FsMount) next; +} FsMount; + +typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList; + +bool build_fs_mount_list(FsMountList *mounts, Error **errp); +void free_fs_mount_list(FsMountList *mounts); +#endif + +#if defined(CONFIG_FSFREEZE) +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints, + strList *mountpoints, + FsMountList mounts, + Error **errp); +int qmp_guest_fsfreeze_do_thaw(Error **errp); +#endif + +#endif /* QGA_COMMANDS_COMMON_H */ diff --git a/qga/commands-linux.c b/qga/commands-linux.c new file mode 100644 index 0000000000..615e9a0027 --- /dev/null +++ b/qga/commands-linux.c @@ -0,0 +1,2242 @@ +/* + * QEMU Guest Agent Linux-specific command implementations + * + * Copyright IBM Corp. 2011 + * + * Authors: + * Michael Roth + * Michal Privoznik + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include +#include "guest-agent-core.h" +#include "qga-qapi-commands.h" +#include "qapi/qmp/qerror.h" +#include "qapi/error.h" +#include "qemu/queue.h" +#include "qemu/base64.h" +#include "commands-common.h" +#include "block/nvme.h" +#include "cutils.h" + +#include +#include +#include + +#ifdef CONFIG_LIBUDEV +#include +#endif + +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) +static int dev_major_minor(const char *devpath, + unsigned int *devmajor, unsigned int *devminor) +{ + struct stat st; + + *devmajor = 0; + *devminor = 0; + + if (stat(devpath, &st) < 0) { + slog("failed to stat device file '%s': %s", devpath, strerror(errno)); + return -1; + } + if (S_ISDIR(st.st_mode)) { + /* It is bind mount */ + return -2; + } + if (S_ISBLK(st.st_mode)) { + *devmajor = major(st.st_rdev); + *devminor = minor(st.st_rdev); + return 0; + } + return -1; +} + +/* + * Walk the mount table and build a list of local file systems + */ +static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp) +{ + struct mntent *ment; + FsMount *mount; + char const *mtab = "/proc/self/mounts"; + FILE *fp; + unsigned int devmajor, devminor; + + fp = setmntent(mtab, "r"); + if (!fp) { + error_setg(errp, "failed to open mtab file: '%s'", mtab); + return false; + } + + while ((ment = getmntent(fp))) { + /* + * An entry which device name doesn't start with a '/' is + * either a dummy file system or a network file system. + * Add special handling for smbfs and cifs as is done by + * coreutils as well. + */ + if ((ment->mnt_fsname[0] != '/') || + (strcmp(ment->mnt_type, "smbfs") == 0) || + (strcmp(ment->mnt_type, "cifs") == 0)) { + continue; + } + if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) { + /* Skip bind mounts */ + continue; + } + + mount = g_new0(FsMount, 1); + mount->dirname = g_strdup(ment->mnt_dir); + mount->devtype = g_strdup(ment->mnt_type); + mount->devmajor = devmajor; + mount->devminor = devminor; + + QTAILQ_INSERT_TAIL(mounts, mount, next); + } + + endmntent(fp); + return true; +} + +static void decode_mntname(char *name, int len) +{ + int i, j = 0; + for (i = 0; i <= len; i++) { + if (name[i] != '\\') { + name[j++] = name[i]; + } else if (name[i + 1] == '\\') { + name[j++] = '\\'; + i++; + } else if (name[i + 1] >= '0' && name[i + 1] <= '3' && + name[i + 2] >= '0' && name[i + 2] <= '7' && + name[i + 3] >= '0' && name[i + 3] <= '7') { + name[j++] = (name[i + 1] - '0') * 64 + + (name[i + 2] - '0') * 8 + + (name[i + 3] - '0'); + i += 3; + } else { + name[j++] = name[i]; + } + } +} + +bool build_fs_mount_list(FsMountList *mounts, Error **errp) +{ + FsMount *mount; + char const *mountinfo = "/proc/self/mountinfo"; + FILE *fp; + char *line = NULL, *dash; + size_t n; + char check; + unsigned int devmajor, devminor; + int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e; + + fp = fopen(mountinfo, "r"); + if (!fp) { + return build_fs_mount_list_from_mtab(mounts, errp); + } + + while (getline(&line, &n, fp) != -1) { + ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c", + &devmajor, &devminor, &dir_s, &dir_e, &check); + if (ret < 3) { + continue; + } + dash = strstr(line + dir_e, " - "); + if (!dash) { + continue; + } + ret = sscanf(dash, " - %n%*s%n %n%*s%n%c", + &type_s, &type_e, &dev_s, &dev_e, &check); + if (ret < 1) { + continue; + } + line[dir_e] = 0; + dash[type_e] = 0; + dash[dev_e] = 0; + decode_mntname(line + dir_s, dir_e - dir_s); + decode_mntname(dash + dev_s, dev_e - dev_s); + if (devmajor == 0) { + /* btrfs reports major number = 0 */ + if (strcmp("btrfs", dash + type_s) != 0 || + dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) { + continue; + } + } + + mount = g_new0(FsMount, 1); + mount->dirname = g_strdup(line + dir_s); + mount->devtype = g_strdup(dash + type_s); + mount->devmajor = devmajor; + mount->devminor = devminor; + + QTAILQ_INSERT_TAIL(mounts, mount, next); + } + free(line); + + fclose(fp); + return true; +} +#endif + +#if defined(CONFIG_FSFREEZE) + +static char *get_pci_driver(char const *syspath, int pathlen, Error **errp) +{ + char *path; + char *dpath; + char *driver = NULL; + char buf[PATH_MAX]; + ssize_t len; + + path = g_strndup(syspath, pathlen); + dpath = g_strdup_printf("%s/driver", path); + len = readlink(dpath, buf, sizeof(buf) - 1); + if (len != -1) { + buf[len] = 0; + driver = g_path_get_basename(buf); + } + g_free(dpath); + g_free(path); + return driver; +} + +static int compare_uint(const void *_a, const void *_b) +{ + unsigned int a = *(unsigned int *)_a; + unsigned int b = *(unsigned int *)_b; + + return a < b ? -1 : a > b ? 1 : 0; +} + +/* Walk the specified sysfs and build a sorted list of host or ata numbers */ +static int build_hosts(char const *syspath, char const *host, bool ata, + unsigned int *hosts, int hosts_max, Error **errp) +{ + char *path; + DIR *dir; + struct dirent *entry; + int i = 0; + + path = g_strndup(syspath, host - syspath); + dir = opendir(path); + if (!dir) { + error_setg_errno(errp, errno, "opendir(\"%s\")", path); + g_free(path); + return -1; + } + + while (i < hosts_max) { + entry = readdir(dir); + if (!entry) { + break; + } + if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) { + ++i; + } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) == 1) { + ++i; + } + } + + qsort(hosts, i, sizeof(hosts[0]), compare_uint); + + g_free(path); + closedir(dir); + return i; +} + +/* + * Store disk device info for devices on the PCI bus. + * Returns true if information has been stored, or false for failure. + */ +static bool build_guest_fsinfo_for_pci_dev(char const *syspath, + GuestDiskAddress *disk, + Error **errp) +{ + unsigned int pci[4], host, hosts[8], tgt[3]; + int i, nhosts = 0, pcilen; + GuestPCIAddress *pciaddr = disk->pci_controller; + bool has_ata = false, has_host = false, has_tgt = false; + char *p, *q, *driver = NULL; + bool ret = false; + + p = strstr(syspath, "/devices/pci"); + if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n", + pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) { + g_debug("only pci device is supported: sysfs path '%s'", syspath); + return false; + } + + p += 12 + pcilen; + while (true) { + driver = get_pci_driver(syspath, p - syspath, errp); + if (driver && (g_str_equal(driver, "ata_piix") || + g_str_equal(driver, "sym53c8xx") || + g_str_equal(driver, "virtio-pci") || + g_str_equal(driver, "ahci") || + g_str_equal(driver, "nvme"))) { + break; + } + + g_free(driver); + if (sscanf(p, "/%x:%x:%x.%x%n", + pci, pci + 1, pci + 2, pci + 3, &pcilen) == 4) { + p += pcilen; + continue; + } + + g_debug("unsupported driver or sysfs path '%s'", syspath); + return false; + } + + p = strstr(syspath, "/target"); + if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u", + tgt, tgt + 1, tgt + 2) == 3) { + has_tgt = true; + } + + p = strstr(syspath, "/ata"); + if (p) { + q = p + 4; + has_ata = true; + } else { + p = strstr(syspath, "/host"); + q = p + 5; + } + if (p && sscanf(q, "%u", &host) == 1) { + has_host = true; + nhosts = build_hosts(syspath, p, has_ata, hosts, + ARRAY_SIZE(hosts), errp); + if (nhosts < 0) { + goto cleanup; + } + } + + pciaddr->domain = pci[0]; + pciaddr->bus = pci[1]; + pciaddr->slot = pci[2]; + pciaddr->function = pci[3]; + + if (strcmp(driver, "ata_piix") == 0) { + /* a host per ide bus, target*:0::0 */ + if (!has_host || !has_tgt) { + g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + for (i = 0; i < nhosts; i++) { + if (host == hosts[i]) { + disk->bus_type = GUEST_DISK_BUS_TYPE_IDE; + disk->bus = i; + disk->unit = tgt[1]; + break; + } + } + if (i >= nhosts) { + g_debug("no host for '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + } else if (strcmp(driver, "sym53c8xx") == 0) { + /* scsi(LSI Logic): target*:0::0 */ + if (!has_tgt) { + g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI; + disk->unit = tgt[1]; + } else if (strcmp(driver, "virtio-pci") == 0) { + if (has_tgt) { + /* virtio-scsi: target*:0:0: */ + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI; + disk->unit = tgt[2]; + } else { + /* virtio-blk: 1 disk per 1 device */ + disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO; + } + } else if (strcmp(driver, "ahci") == 0) { + /* ahci: 1 host per 1 unit */ + if (!has_host || !has_tgt) { + g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + for (i = 0; i < nhosts; i++) { + if (host == hosts[i]) { + disk->unit = i; + disk->bus_type = GUEST_DISK_BUS_TYPE_SATA; + break; + } + } + if (i >= nhosts) { + g_debug("no host for '%s' (driver '%s')", syspath, driver); + goto cleanup; + } + } else if (strcmp(driver, "nvme") == 0) { + disk->bus_type = GUEST_DISK_BUS_TYPE_NVME; + } else { + g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath); + goto cleanup; + } + + ret = true; + +cleanup: + g_free(driver); + return ret; +} + +/* + * Store disk device info for non-PCI virtio devices (for example s390x + * channel I/O devices). Returns true if information has been stored, or + * false for failure. + */ +static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath, + GuestDiskAddress *disk, + Error **errp) +{ + unsigned int tgt[3]; + char *p; + + if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) { + g_debug("Unsupported virtio device '%s'", syspath); + return false; + } + + p = strstr(syspath, "/target"); + if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u", + &tgt[0], &tgt[1], &tgt[2]) == 3) { + /* virtio-scsi: target*:0:: */ + disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI; + disk->bus = tgt[0]; + disk->target = tgt[1]; + disk->unit = tgt[2]; + } else { + /* virtio-blk: 1 disk per 1 device */ + disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO; + } + + return true; +} + +/* + * Store disk device info for CCW devices (s390x channel I/O devices). + * Returns true if information has been stored, or false for failure. + */ +static bool build_guest_fsinfo_for_ccw_dev(char const *syspath, + GuestDiskAddress *disk, + Error **errp) +{ + unsigned int cssid, ssid, subchno, devno; + char *p; + + p = strstr(syspath, "/devices/css"); + if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/", + &cssid, &ssid, &subchno, &devno) < 4) { + g_debug("could not parse ccw device sysfs path: %s", syspath); + return false; + } + + disk->has_ccw_address = true; + disk->ccw_address = g_new0(GuestCCWAddress, 1); + disk->ccw_address->cssid = cssid; + disk->ccw_address->ssid = ssid; + disk->ccw_address->subchno = subchno; + disk->ccw_address->devno = devno; + + if (strstr(p, "/virtio")) { + build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp); + } + + return true; +} + +/* Store disk device info specified by @sysfs into @fs */ +static void build_guest_fsinfo_for_real_device(char const *syspath, + GuestFilesystemInfo *fs, + Error **errp) +{ + GuestDiskAddress *disk; + GuestPCIAddress *pciaddr; + bool has_hwinf; +#ifdef CONFIG_LIBUDEV + struct udev *udev = NULL; + struct udev_device *udevice = NULL; +#endif + + pciaddr = g_new0(GuestPCIAddress, 1); + pciaddr->domain = -1; /* -1 means field is invalid */ + pciaddr->bus = -1; + pciaddr->slot = -1; + pciaddr->function = -1; + + disk = g_new0(GuestDiskAddress, 1); + disk->pci_controller = pciaddr; + disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN; + +#ifdef CONFIG_LIBUDEV + udev = udev_new(); + udevice = udev_device_new_from_syspath(udev, syspath); + if (udev == NULL || udevice == NULL) { + g_debug("failed to query udev"); + } else { + const char *devnode, *serial; + devnode = udev_device_get_devnode(udevice); + if (devnode != NULL) { + disk->dev = g_strdup(devnode); + disk->has_dev = true; + } + serial = udev_device_get_property_value(udevice, "ID_SERIAL"); + if (serial != NULL && *serial != 0) { + disk->serial = g_strdup(serial); + disk->has_serial = true; + } + } + + udev_unref(udev); + udev_device_unref(udevice); +#endif + + if (strstr(syspath, "/devices/pci")) { + has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk, errp); + } else if (strstr(syspath, "/devices/css")) { + has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk, errp); + } else if (strstr(syspath, "/virtio")) { + has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp); + } else { + g_debug("Unsupported device type for '%s'", syspath); + has_hwinf = false; + } + + if (has_hwinf || disk->has_dev || disk->has_serial) { + QAPI_LIST_PREPEND(fs->disk, disk); + } else { + qapi_free_GuestDiskAddress(disk); + } +} + +static void build_guest_fsinfo_for_device(char const *devpath, + GuestFilesystemInfo *fs, + Error **errp); + +/* Store a list of slave devices of virtual volume specified by @syspath into + * @fs */ +static void build_guest_fsinfo_for_virtual_device(char const *syspath, + GuestFilesystemInfo *fs, + Error **errp) +{ + Error *err = NULL; + DIR *dir; + char *dirpath; + struct dirent *entry; + + dirpath = g_strdup_printf("%s/slaves", syspath); + dir = opendir(dirpath); + if (!dir) { + if (errno != ENOENT) { + error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath); + } + g_free(dirpath); + return; + } + + for (;;) { + errno = 0; + entry = readdir(dir); + if (entry == NULL) { + if (errno) { + error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath); + } + break; + } + + if (entry->d_type == DT_LNK) { + char *path; + + g_debug(" slave device '%s'", entry->d_name); + path = g_strdup_printf("%s/slaves/%s", syspath, entry->d_name); + build_guest_fsinfo_for_device(path, fs, &err); + g_free(path); + + if (err) { + error_propagate(errp, err); + break; + } + } + } + + g_free(dirpath); + closedir(dir); +} + +static bool is_disk_virtual(const char *devpath, Error **errp) +{ + g_autofree char *syspath = realpath(devpath, NULL); + + if (!syspath) { + error_setg_errno(errp, errno, "realpath(\"%s\")", devpath); + return false; + } + return strstr(syspath, "/devices/virtual/block/") != NULL; +} + +/* Dispatch to functions for virtual/real device */ +static void build_guest_fsinfo_for_device(char const *devpath, + GuestFilesystemInfo *fs, + Error **errp) +{ + ERRP_GUARD(); + g_autofree char *syspath = NULL; + bool is_virtual = false; + + syspath = realpath(devpath, NULL); + if (!syspath) { + if (errno != ENOENT) { + error_setg_errno(errp, errno, "realpath(\"%s\")", devpath); + return; + } + + /* ENOENT: This devpath may not exist because of container config */ + if (!fs->name) { + fs->name = g_path_get_basename(devpath); + } + return; + } + + if (!fs->name) { + fs->name = g_path_get_basename(syspath); + } + + g_debug(" parse sysfs path '%s'", syspath); + is_virtual = is_disk_virtual(syspath, errp); + if (*errp != NULL) { + return; + } + if (is_virtual) { + build_guest_fsinfo_for_virtual_device(syspath, fs, errp); + } else { + build_guest_fsinfo_for_real_device(syspath, fs, errp); + } +} + +#ifdef CONFIG_LIBUDEV + +/* + * Wrapper around build_guest_fsinfo_for_device() for getting just + * the disk address. + */ +static GuestDiskAddress *get_disk_address(const char *syspath, Error **errp) +{ + g_autoptr(GuestFilesystemInfo) fs = NULL; + + fs = g_new0(GuestFilesystemInfo, 1); + build_guest_fsinfo_for_device(syspath, fs, errp); + if (fs->disk != NULL) { + return g_steal_pointer(&fs->disk->value); + } + return NULL; +} + +static char *get_alias_for_syspath(const char *syspath) +{ + struct udev *udev = NULL; + struct udev_device *udevice = NULL; + char *ret = NULL; + + udev = udev_new(); + if (udev == NULL) { + g_debug("failed to query udev"); + goto out; + } + udevice = udev_device_new_from_syspath(udev, syspath); + if (udevice == NULL) { + g_debug("failed to query udev for path: %s", syspath); + goto out; + } else { + const char *alias = udev_device_get_property_value( + udevice, "DM_NAME"); + /* + * NULL means there was an error and empty string means there is no + * alias. In case of no alias we return NULL instead of empty string. + */ + if (alias == NULL) { + g_debug("failed to query udev for device alias for: %s", + syspath); + } else if (*alias != 0) { + ret = g_strdup(alias); + } + } + +out: + udev_unref(udev); + udev_device_unref(udevice); + return ret; +} + +static char *get_device_for_syspath(const char *syspath) +{ + struct udev *udev = NULL; + struct udev_device *udevice = NULL; + char *ret = NULL; + + udev = udev_new(); + if (udev == NULL) { + g_debug("failed to query udev"); + goto out; + } + udevice = udev_device_new_from_syspath(udev, syspath); + if (udevice == NULL) { + g_debug("failed to query udev for path: %s", syspath); + goto out; + } else { + ret = g_strdup(udev_device_get_devnode(udevice)); + } + +out: + udev_unref(udev); + udev_device_unref(udevice); + return ret; +} + +static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk) +{ + g_autofree char *deps_dir = NULL; + const gchar *dep; + GDir *dp_deps = NULL; + + /* List dependent disks */ + deps_dir = g_strdup_printf("%s/slaves", disk_dir); + g_debug(" listing entries in: %s", deps_dir); + dp_deps = g_dir_open(deps_dir, 0, NULL); + if (dp_deps == NULL) { + g_debug("failed to list entries in %s", deps_dir); + return; + } + disk->has_dependencies = true; + while ((dep = g_dir_read_name(dp_deps)) != NULL) { + g_autofree char *dep_dir = NULL; + char *dev_name; + + /* Add dependent disks */ + dep_dir = g_strdup_printf("%s/%s", deps_dir, dep); + dev_name = get_device_for_syspath(dep_dir); + if (dev_name != NULL) { + g_debug(" adding dependent device: %s", dev_name); + QAPI_LIST_PREPEND(disk->dependencies, dev_name); + } + } + g_dir_close(dp_deps); +} + +/* + * Detect partitions subdirectory, name is "" or + * "p" + * + * @disk_name -- last component of /sys path (e.g. sda) + * @disk_dir -- sys path of the disk (e.g. /sys/block/sda) + * @disk_dev -- device node of the disk (e.g. /dev/sda) + */ +static GuestDiskInfoList *get_disk_partitions( + GuestDiskInfoList *list, + const char *disk_name, const char *disk_dir, + const char *disk_dev) +{ + GuestDiskInfoList *ret = list; + struct dirent *de_disk; + DIR *dp_disk = NULL; + size_t len = strlen(disk_name); + + dp_disk = opendir(disk_dir); + while ((de_disk = readdir(dp_disk)) != NULL) { + g_autofree char *partition_dir = NULL; + char *dev_name; + GuestDiskInfo *partition; + + if (!(de_disk->d_type & DT_DIR)) { + continue; + } + + if (!(strncmp(disk_name, de_disk->d_name, len) == 0 && + ((*(de_disk->d_name + len) == 'p' && + isdigit(*(de_disk->d_name + len + 1))) || + isdigit(*(de_disk->d_name + len))))) { + continue; + } + + partition_dir = g_strdup_printf("%s/%s", + disk_dir, de_disk->d_name); + dev_name = get_device_for_syspath(partition_dir); + if (dev_name == NULL) { + g_debug("Failed to get device name for syspath: %s", + disk_dir); + continue; + } + partition = g_new0(GuestDiskInfo, 1); + partition->name = dev_name; + partition->partition = true; + partition->has_dependencies = true; + /* Add parent disk as dependent for easier tracking of hierarchy */ + QAPI_LIST_PREPEND(partition->dependencies, g_strdup(disk_dev)); + + QAPI_LIST_PREPEND(ret, partition); + } + closedir(dp_disk); + + return ret; +} + +static void get_nvme_smart(GuestDiskInfo *disk) +{ + int fd; + GuestNVMeSmart *smart; + NvmeSmartLog log = {0}; + struct nvme_admin_cmd cmd = { + .opcode = NVME_ADM_CMD_GET_LOG_PAGE, + .nsid = NVME_NSID_BROADCAST, + .addr = (uintptr_t)&log, + .data_len = sizeof(log), + .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */ + | (((sizeof(log) >> 2) - 1) << 16) + }; + + fd = qga_open_cloexec(disk->name, O_RDONLY, 0); + if (fd == -1) { + g_debug("Failed to open device: %s: %s", disk->name, g_strerror(errno)); + return; + } + + if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) { + g_debug("Failed to get smart: %s: %s", disk->name, g_strerror(errno)); + close(fd); + return; + } + + disk->has_smart = true; + disk->smart = g_new0(GuestDiskSmart, 1); + disk->smart->type = GUEST_DISK_BUS_TYPE_NVME; + + smart = &disk->smart->u.nvme; + smart->critical_warning = log.critical_warning; + smart->temperature = lduw_le_p(&log.temperature); /* unaligned field */ + smart->available_spare = log.available_spare; + smart->available_spare_threshold = log.available_spare_threshold; + smart->percentage_used = log.percentage_used; + smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]); + smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]); + smart->data_units_written_lo = le64_to_cpu(log.data_units_written[0]); + smart->data_units_written_hi = le64_to_cpu(log.data_units_written[1]); + smart->host_read_commands_lo = le64_to_cpu(log.host_read_commands[0]); + smart->host_read_commands_hi = le64_to_cpu(log.host_read_commands[1]); + smart->host_write_commands_lo = le64_to_cpu(log.host_write_commands[0]); + smart->host_write_commands_hi = le64_to_cpu(log.host_write_commands[1]); + smart->controller_busy_time_lo = le64_to_cpu(log.controller_busy_time[0]); + smart->controller_busy_time_hi = le64_to_cpu(log.controller_busy_time[1]); + smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]); + smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]); + smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]); + smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]); + smart->unsafe_shutdowns_lo = le64_to_cpu(log.unsafe_shutdowns[0]); + smart->unsafe_shutdowns_hi = le64_to_cpu(log.unsafe_shutdowns[1]); + smart->media_errors_lo = le64_to_cpu(log.media_errors[0]); + smart->media_errors_hi = le64_to_cpu(log.media_errors[1]); + smart->number_of_error_log_entries_lo = + le64_to_cpu(log.number_of_error_log_entries[0]); + smart->number_of_error_log_entries_hi = + le64_to_cpu(log.number_of_error_log_entries[1]); + + close(fd); +} + +static void get_disk_smart(GuestDiskInfo *disk) +{ + if (disk->has_address + && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) { + get_nvme_smart(disk); + } +} + +GuestDiskInfoList *qmp_guest_get_disks(Error **errp) +{ + GuestDiskInfoList *ret = NULL; + GuestDiskInfo *disk; + DIR *dp = NULL; + struct dirent *de = NULL; + + g_debug("listing /sys/block directory"); + dp = opendir("/sys/block"); + if (dp == NULL) { + error_setg_errno(errp, errno, "Can't open directory \"/sys/block\""); + return NULL; + } + while ((de = readdir(dp)) != NULL) { + g_autofree char *disk_dir = NULL, *line = NULL, + *size_path = NULL; + char *dev_name; + Error *local_err = NULL; + if (de->d_type != DT_LNK) { + g_debug(" skipping entry: %s", de->d_name); + continue; + } + + /* Check size and skip zero-sized disks */ + g_debug(" checking disk size"); + size_path = g_strdup_printf("/sys/block/%s/size", de->d_name); + if (!g_file_get_contents(size_path, &line, NULL, NULL)) { + g_debug(" failed to read disk size"); + continue; + } + if (g_strcmp0(line, "0\n") == 0) { + g_debug(" skipping zero-sized disk"); + continue; + } + + g_debug(" adding %s", de->d_name); + disk_dir = g_strdup_printf("/sys/block/%s", de->d_name); + dev_name = get_device_for_syspath(disk_dir); + if (dev_name == NULL) { + g_debug("Failed to get device name for syspath: %s", + disk_dir); + continue; + } + disk = g_new0(GuestDiskInfo, 1); + disk->name = dev_name; + disk->partition = false; + disk->alias = get_alias_for_syspath(disk_dir); + disk->has_alias = (disk->alias != NULL); + QAPI_LIST_PREPEND(ret, disk); + + /* Get address for non-virtual devices */ + bool is_virtual = is_disk_virtual(disk_dir, &local_err); + if (local_err != NULL) { + g_debug(" failed to check disk path, ignoring error: %s", + error_get_pretty(local_err)); + error_free(local_err); + local_err = NULL; + /* Don't try to get the address */ + is_virtual = true; + } + if (!is_virtual) { + disk->address = get_disk_address(disk_dir, &local_err); + if (local_err != NULL) { + g_debug(" failed to get device info, ignoring error: %s", + error_get_pretty(local_err)); + error_free(local_err); + local_err = NULL; + } else if (disk->address != NULL) { + disk->has_address = true; + } + } + + get_disk_deps(disk_dir, disk); + get_disk_smart(disk); + ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name); + } + + closedir(dp); + + return ret; +} + +#else + +GuestDiskInfoList *qmp_guest_get_disks(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +#endif + +/* Return a list of the disk device(s)' info which @mount lies on */ +static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount, + Error **errp) +{ + GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs)); + struct statvfs buf; + unsigned long used, nonroot_total, fr_size; + char *devpath = g_strdup_printf("/sys/dev/block/%u:%u", + mount->devmajor, mount->devminor); + + fs->mountpoint = g_strdup(mount->dirname); + fs->type = g_strdup(mount->devtype); + build_guest_fsinfo_for_device(devpath, fs, errp); + + if (statvfs(fs->mountpoint, &buf) == 0) { + fr_size = buf.f_frsize; + used = buf.f_blocks - buf.f_bfree; + nonroot_total = used + buf.f_bavail; + fs->used_bytes = used * fr_size; + fs->total_bytes = nonroot_total * fr_size; + + fs->has_total_bytes = true; + fs->has_used_bytes = true; + } + + g_free(devpath); + + return fs; +} + +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) +{ + FsMountList mounts; + struct FsMount *mount; + GuestFilesystemInfoList *ret = NULL; + Error *local_err = NULL; + + QTAILQ_INIT(&mounts); + if (!build_fs_mount_list(&mounts, &local_err)) { + error_propagate(errp, local_err); + return NULL; + } + + QTAILQ_FOREACH(mount, &mounts, next) { + g_debug("Building guest fsinfo for '%s'", mount->dirname); + + QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount, &local_err)); + if (local_err) { + error_propagate(errp, local_err); + qapi_free_GuestFilesystemInfoList(ret); + ret = NULL; + break; + } + } + + free_fs_mount_list(&mounts); + return ret; +} + +/* + * Walk list of mounted file systems in the guest, and freeze the ones which + * are real local file systems. + */ +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints, + strList *mountpoints, + FsMountList mounts, + Error **errp) +{ + struct FsMount *mount; + strList *list; + int fd, ret, i = 0; + + QTAILQ_FOREACH_REVERSE(mount, &mounts, next) { + /* To issue fsfreeze in the reverse order of mounts, check if the + * mount is listed in the list here */ + if (has_mountpoints) { + for (list = mountpoints; list; list = list->next) { + if (strcmp(list->value, mount->dirname) == 0) { + break; + } + } + if (!list) { + continue; + } + } + + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); + if (fd == -1) { + error_setg_errno(errp, errno, "failed to open %s", mount->dirname); + return -1; + } + + /* we try to cull filesystems we know won't work in advance, but other + * filesystems may not implement fsfreeze for less obvious reasons. + * these will report EOPNOTSUPP. we simply ignore these when tallying + * the number of frozen filesystems. + * if a filesystem is mounted more than once (aka bind mount) a + * consecutive attempt to freeze an already frozen filesystem will + * return EBUSY. + * + * any other error means a failure to freeze a filesystem we + * expect to be freezable, so return an error in those cases + * and return system to thawed state. + */ + ret = ioctl(fd, FIFREEZE); + if (ret == -1) { + if (errno != EOPNOTSUPP && errno != EBUSY) { + error_setg_errno(errp, errno, "failed to freeze %s", + mount->dirname); + close(fd); + return -1; + } + } else { + i++; + } + close(fd); + } + return i; +} + +int qmp_guest_fsfreeze_do_thaw(Error **errp) +{ + int ret; + FsMountList mounts; + FsMount *mount; + int fd, i = 0, logged; + Error *local_err = NULL; + + QTAILQ_INIT(&mounts); + if (!build_fs_mount_list(&mounts, &local_err)) { + error_propagate(errp, local_err); + return -1; + } + + QTAILQ_FOREACH(mount, &mounts, next) { + logged = false; + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); + if (fd == -1) { + continue; + } + /* we have no way of knowing whether a filesystem was actually unfrozen + * as a result of a successful call to FITHAW, only that if an error + * was returned the filesystem was *not* unfrozen by that particular + * call. + * + * since multiple preceding FIFREEZEs require multiple calls to FITHAW + * to unfreeze, continuing issuing FITHAW until an error is returned, + * in which case either the filesystem is in an unfreezable state, or, + * more likely, it was thawed previously (and remains so afterward). + * + * also, since the most recent successful call is the one that did + * the actual unfreeze, we can use this to provide an accurate count + * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which + * may * be useful for determining whether a filesystem was unfrozen + * during the freeze/thaw phase by a process other than qemu-ga. + */ + do { + ret = ioctl(fd, FITHAW); + if (ret == 0 && !logged) { + i++; + logged = true; + } + } while (ret == 0); + close(fd); + } + + free_fs_mount_list(&mounts); + + return i; +} +#endif /* CONFIG_FSFREEZE */ + +#if defined(CONFIG_FSTRIM) +/* + * Walk list of mounted file systems in the guest, and trim them. + */ +GuestFilesystemTrimResponse * +qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp) +{ + GuestFilesystemTrimResponse *response; + GuestFilesystemTrimResult *result; + int ret = 0; + FsMountList mounts; + struct FsMount *mount; + int fd; + struct fstrim_range r; + + slog("guest-fstrim called"); + + QTAILQ_INIT(&mounts); + if (!build_fs_mount_list(&mounts, errp)) { + return NULL; + } + + response = g_malloc0(sizeof(*response)); + + QTAILQ_FOREACH(mount, &mounts, next) { + result = g_malloc0(sizeof(*result)); + result->path = g_strdup(mount->dirname); + + QAPI_LIST_PREPEND(response->paths, result); + + fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); + if (fd == -1) { + result->error = g_strdup_printf("failed to open: %s", + strerror(errno)); + result->has_error = true; + continue; + } + + /* We try to cull filesystems we know won't work in advance, but other + * filesystems may not implement fstrim for less obvious reasons. + * These will report EOPNOTSUPP; while in some other cases ENOTTY + * will be reported (e.g. CD-ROMs). + * Any other error means an unexpected error. + */ + r.start = 0; + r.len = -1; + r.minlen = has_minimum ? minimum : 0; + ret = ioctl(fd, FITRIM, &r); + if (ret == -1) { + result->has_error = true; + if (errno == ENOTTY || errno == EOPNOTSUPP) { + result->error = g_strdup("trim not supported"); + } else { + result->error = g_strdup_printf("failed to trim: %s", + strerror(errno)); + } + close(fd); + continue; + } + + result->has_minimum = true; + result->minimum = r.minlen; + result->has_trimmed = true; + result->trimmed = r.len; + close(fd); + } + + free_fs_mount_list(&mounts); + return response; +} +#endif /* CONFIG_FSTRIM */ + + +#define LINUX_SYS_STATE_FILE "/sys/power/state" +#define SUSPEND_SUPPORTED 0 +#define SUSPEND_NOT_SUPPORTED 1 + +typedef enum { + SUSPEND_MODE_DISK = 0, + SUSPEND_MODE_RAM = 1, + SUSPEND_MODE_HYBRID = 2, +} SuspendMode; + +/* + * Executes a command in a child process using g_spawn_sync, + * returning an int >= 0 representing the exit status of the + * process. + * + * If the program wasn't found in path, returns -1. + * + * If a problem happened when creating the child process, + * returns -1 and errp is set. + */ +static int run_process_child(const char *command[], Error **errp) +{ + int exit_status, spawn_flag; + GError *g_err = NULL; + bool success; + + spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | + G_SPAWN_STDERR_TO_DEV_NULL; + + success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag, + NULL, NULL, NULL, NULL, + &exit_status, &g_err); + + if (success) { + return WEXITSTATUS(exit_status); + } + + if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) { + error_setg(errp, "failed to create child process, error '%s'", + g_err->message); + } + + g_error_free(g_err); + return -1; +} + +static bool systemd_supports_mode(SuspendMode mode, Error **errp) +{ + const char *systemctl_args[3] = {"systemd-hibernate", "systemd-suspend", + "systemd-hybrid-sleep"}; + const char *cmd[4] = {"systemctl", "status", systemctl_args[mode], NULL}; + int status; + + status = run_process_child(cmd, errp); + + /* + * systemctl status uses LSB return codes so we can expect + * status > 0 and be ok. To assert if the guest has support + * for the selected suspend mode, status should be < 4. 4 is + * the code for unknown service status, the return value when + * the service does not exist. A common value is status = 3 + * (program is not running). + */ + if (status > 0 && status < 4) { + return true; + } + + return false; +} + +static void systemd_suspend(SuspendMode mode, Error **errp) +{ + Error *local_err = NULL; + const char *systemctl_args[3] = {"hibernate", "suspend", "hybrid-sleep"}; + const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL}; + int status; + + status = run_process_child(cmd, &local_err); + + if (status == 0) { + return; + } + + if ((status == -1) && !local_err) { + error_setg(errp, "the helper program 'systemctl %s' was not found", + systemctl_args[mode]); + return; + } + + if (local_err) { + error_propagate(errp, local_err); + } else { + error_setg(errp, "the helper program 'systemctl %s' returned an " + "unexpected exit status code (%d)", + systemctl_args[mode], status); + } +} + +static bool pmutils_supports_mode(SuspendMode mode, Error **errp) +{ + Error *local_err = NULL; + const char *pmutils_args[3] = {"--hibernate", "--suspend", + "--suspend-hybrid"}; + const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL}; + int status; + + status = run_process_child(cmd, &local_err); + + if (status == SUSPEND_SUPPORTED) { + return true; + } + + if ((status == -1) && !local_err) { + return false; + } + + if (local_err) { + error_propagate(errp, local_err); + } else { + error_setg(errp, + "the helper program '%s' returned an unexpected exit" + " status code (%d)", "pm-is-supported", status); + } + + return false; +} + +static void pmutils_suspend(SuspendMode mode, Error **errp) +{ + Error *local_err = NULL; + const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend", + "pm-suspend-hybrid"}; + const char *cmd[2] = {pmutils_binaries[mode], NULL}; + int status; + + status = run_process_child(cmd, &local_err); + + if (status == 0) { + return; + } + + if ((status == -1) && !local_err) { + error_setg(errp, "the helper program '%s' was not found", + pmutils_binaries[mode]); + return; + } + + if (local_err) { + error_propagate(errp, local_err); + } else { + error_setg(errp, + "the helper program '%s' returned an unexpected exit" + " status code (%d)", pmutils_binaries[mode], status); + } +} + +static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp) +{ + const char *sysfile_strs[3] = {"disk", "mem", NULL}; + const char *sysfile_str = sysfile_strs[mode]; + char buf[32]; /* hopefully big enough */ + int fd; + ssize_t ret; + + if (!sysfile_str) { + error_setg(errp, "unknown guest suspend mode"); + return false; + } + + fd = open(LINUX_SYS_STATE_FILE, O_RDONLY); + if (fd < 0) { + return false; + } + + ret = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (ret <= 0) { + return false; + } + buf[ret] = '\0'; + + if (strstr(buf, sysfile_str)) { + return true; + } + return false; +} + +static void linux_sys_state_suspend(SuspendMode mode, Error **errp) +{ + Error *local_err = NULL; + const char *sysfile_strs[3] = {"disk", "mem", NULL}; + const char *sysfile_str = sysfile_strs[mode]; + pid_t pid; + int status; + + if (!sysfile_str) { + error_setg(errp, "unknown guest suspend mode"); + return; + } + + pid = fork(); + if (!pid) { + /* child */ + int fd; + + setsid(); + reopen_fd_to_null(0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + + fd = open(LINUX_SYS_STATE_FILE, O_WRONLY); + if (fd < 0) { + _exit(EXIT_FAILURE); + } + + if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) { + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + return; + } + + ga_wait_child(pid, &status, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + if (WEXITSTATUS(status)) { + error_setg(errp, "child process has failed to suspend"); + } + +} + +static void guest_suspend(SuspendMode mode, Error **errp) +{ + Error *local_err = NULL; + bool mode_supported = false; + + if (systemd_supports_mode(mode, &local_err)) { + mode_supported = true; + systemd_suspend(mode, &local_err); + } + + if (!local_err) { + return; + } + + error_free(local_err); + local_err = NULL; + + if (pmutils_supports_mode(mode, &local_err)) { + mode_supported = true; + pmutils_suspend(mode, &local_err); + } + + if (!local_err) { + return; + } + + error_free(local_err); + local_err = NULL; + + if (linux_sys_state_supports_mode(mode, &local_err)) { + mode_supported = true; + linux_sys_state_suspend(mode, &local_err); + } + + if (!mode_supported) { + error_free(local_err); + error_setg(errp, + "the requested suspend mode is not supported by the guest"); + } else { + error_propagate(errp, local_err); + } +} + +void qmp_guest_suspend_disk(Error **errp) +{ + guest_suspend(SUSPEND_MODE_DISK, errp); +} + +void qmp_guest_suspend_ram(Error **errp) +{ + guest_suspend(SUSPEND_MODE_RAM, errp); +} + +void qmp_guest_suspend_hybrid(Error **errp) +{ + guest_suspend(SUSPEND_MODE_HYBRID, errp); +} + +/* Transfer online/offline status between @vcpu and the guest system. + * + * On input either @errp or *@errp must be NULL. + * + * In system-to-@vcpu direction, the following @vcpu fields are accessed: + * - R: vcpu->logical_id + * - W: vcpu->online + * - W: vcpu->can_offline + * + * In @vcpu-to-system direction, the following @vcpu fields are accessed: + * - R: vcpu->logical_id + * - R: vcpu->online + * + * Written members remain unmodified on error. + */ +static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu, + char *dirpath, Error **errp) +{ + int fd; + int res; + int dirfd; + static const char fn[] = "online"; + + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); + if (dirfd == -1) { + error_setg_errno(errp, errno, "open(\"%s\")", dirpath); + return; + } + + fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR); + if (fd == -1) { + if (errno != ENOENT) { + error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn); + } else if (sys2vcpu) { + vcpu->online = true; + vcpu->can_offline = false; + } else if (!vcpu->online) { + error_setg(errp, "logical processor #%" PRId64 " can't be " + "offlined", vcpu->logical_id); + } /* otherwise pretend successful re-onlining */ + } else { + unsigned char status; + + res = pread(fd, &status, 1, 0); + if (res == -1) { + error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn); + } else if (res == 0) { + error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath, + fn); + } else if (sys2vcpu) { + vcpu->online = (status != '0'); + vcpu->can_offline = true; + } else if (vcpu->online != (status != '0')) { + status = '0' + vcpu->online; + if (pwrite(fd, &status, 1, 0) == -1) { + error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath, + fn); + } + } /* otherwise pretend successful re-(on|off)-lining */ + + res = close(fd); + g_assert(res == 0); + } + + res = close(dirfd); + g_assert(res == 0); +} + +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + GuestLogicalProcessorList *head, **tail; + const char *cpu_dir = "/sys/devices/system/cpu"; + const gchar *line; + g_autoptr(GDir) cpu_gdir = NULL; + Error *local_err = NULL; + + head = NULL; + tail = &head; + cpu_gdir = g_dir_open(cpu_dir, 0, NULL); + + if (cpu_gdir == NULL) { + error_setg_errno(errp, errno, "failed to list entries: %s", cpu_dir); + return NULL; + } + + while (local_err == NULL && (line = g_dir_read_name(cpu_gdir)) != NULL) { + GuestLogicalProcessor *vcpu; + int64_t id; + if (sscanf(line, "cpu%" PRId64, &id)) { + g_autofree char *path = g_strdup_printf("/sys/devices/system/cpu/" + "cpu%" PRId64 "/", id); + vcpu = g_malloc0(sizeof *vcpu); + vcpu->logical_id = id; + vcpu->has_can_offline = true; /* lolspeak ftw */ + transfer_vcpu(vcpu, true, path, &local_err); + QAPI_LIST_APPEND(tail, vcpu); + } + } + + if (local_err == NULL) { + /* there's no guest with zero VCPUs */ + g_assert(head != NULL); + return head; + } + + qapi_free_GuestLogicalProcessorList(head); + error_propagate(errp, local_err); + return NULL; +} + +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + int64_t processed; + Error *local_err = NULL; + + processed = 0; + while (vcpus != NULL) { + char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/", + vcpus->value->logical_id); + + transfer_vcpu(vcpus->value, false, path, &local_err); + g_free(path); + if (local_err != NULL) { + break; + } + ++processed; + vcpus = vcpus->next; + } + + if (local_err != NULL) { + if (processed == 0) { + error_propagate(errp, local_err); + } else { + error_free(local_err); + } + } + + return processed; +} + +void qmp_guest_set_user_password(const char *username, + const char *password, + bool crypted, + Error **errp) +{ + Error *local_err = NULL; + char *passwd_path = NULL; + pid_t pid; + int status; + int datafd[2] = { -1, -1 }; + char *rawpasswddata = NULL; + size_t rawpasswdlen; + char *chpasswddata = NULL; + size_t chpasswdlen; + + rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp); + if (!rawpasswddata) { + return; + } + rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1); + rawpasswddata[rawpasswdlen] = '\0'; + + if (strchr(rawpasswddata, '\n')) { + error_setg(errp, "forbidden characters in raw password"); + goto out; + } + + if (strchr(username, '\n') || + strchr(username, ':')) { + error_setg(errp, "forbidden characters in username"); + goto out; + } + + chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata); + chpasswdlen = strlen(chpasswddata); + + passwd_path = g_find_program_in_path("chpasswd"); + + if (!passwd_path) { + error_setg(errp, "cannot find 'passwd' program in PATH"); + goto out; + } + + if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) { + error_setg(errp, "cannot create pipe FDs"); + goto out; + } + + pid = fork(); + if (pid == 0) { + close(datafd[1]); + /* child */ + setsid(); + dup2(datafd[0], 0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + + if (crypted) { + execl(passwd_path, "chpasswd", "-e", NULL); + } else { + execl(passwd_path, "chpasswd", NULL); + } + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + goto out; + } + close(datafd[0]); + datafd[0] = -1; + + if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) { + error_setg_errno(errp, errno, "cannot write new account password"); + goto out; + } + close(datafd[1]); + datafd[1] = -1; + + ga_wait_child(pid, &status, &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto out; + } + + if (!WIFEXITED(status)) { + error_setg(errp, "child process has terminated abnormally"); + goto out; + } + + if (WEXITSTATUS(status)) { + error_setg(errp, "child process has failed to set user password"); + goto out; + } + +out: + g_free(chpasswddata); + g_free(rawpasswddata); + g_free(passwd_path); + if (datafd[0] != -1) { + close(datafd[0]); + } + if (datafd[1] != -1) { + close(datafd[1]); + } +} + +static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf, + int size, Error **errp) +{ + int fd; + int res; + + errno = 0; + fd = openat(dirfd, pathname, O_RDONLY); + if (fd == -1) { + error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname); + return; + } + + res = pread(fd, buf, size, 0); + if (res == -1) { + error_setg_errno(errp, errno, "pread sysfs file \"%s\"", pathname); + } else if (res == 0) { + error_setg(errp, "pread sysfs file \"%s\": unexpected EOF", pathname); + } + close(fd); +} + +static void ga_write_sysfs_file(int dirfd, const char *pathname, + const char *buf, int size, Error **errp) +{ + int fd; + + errno = 0; + fd = openat(dirfd, pathname, O_WRONLY); + if (fd == -1) { + error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname); + return; + } + + if (pwrite(fd, buf, size, 0) == -1) { + error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"", pathname); + } + + close(fd); +} + +/* Transfer online/offline status between @mem_blk and the guest system. + * + * On input either @errp or *@errp must be NULL. + * + * In system-to-@mem_blk direction, the following @mem_blk fields are accessed: + * - R: mem_blk->phys_index + * - W: mem_blk->online + * - W: mem_blk->can_offline + * + * In @mem_blk-to-system direction, the following @mem_blk fields are accessed: + * - R: mem_blk->phys_index + * - R: mem_blk->online + *- R: mem_blk->can_offline + * Written members remain unmodified on error. + */ +static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool sys2memblk, + GuestMemoryBlockResponse *result, + Error **errp) +{ + char *dirpath; + int dirfd; + char *status; + Error *local_err = NULL; + + if (!sys2memblk) { + DIR *dp; + + if (!result) { + error_setg(errp, "Internal error, 'result' should not be NULL"); + return; + } + errno = 0; + dp = opendir("/sys/devices/system/memory/"); + /* if there is no 'memory' directory in sysfs, + * we think this VM does not support online/offline memory block, + * any other solution? + */ + if (!dp) { + if (errno == ENOENT) { + result->response = + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED; + } + goto out1; + } + closedir(dp); + } + + dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/", + mem_blk->phys_index); + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); + if (dirfd == -1) { + if (sys2memblk) { + error_setg_errno(errp, errno, "open(\"%s\")", dirpath); + } else { + if (errno == ENOENT) { + result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND; + } else { + result->response = + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED; + } + } + g_free(dirpath); + goto out1; + } + g_free(dirpath); + + status = g_malloc0(10); + ga_read_sysfs_file(dirfd, "state", status, 10, &local_err); + if (local_err) { + /* treat with sysfs file that not exist in old kernel */ + if (errno == ENOENT) { + error_free(local_err); + if (sys2memblk) { + mem_blk->online = true; + mem_blk->can_offline = false; + } else if (!mem_blk->online) { + result->response = + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED; + } + } else { + if (sys2memblk) { + error_propagate(errp, local_err); + } else { + error_free(local_err); + result->response = + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED; + } + } + goto out2; + } + + if (sys2memblk) { + char removable = '0'; + + mem_blk->online = (strncmp(status, "online", 6) == 0); + + ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err); + if (local_err) { + /* if no 'removable' file, it doesn't support offline mem blk */ + if (errno == ENOENT) { + error_free(local_err); + mem_blk->can_offline = false; + } else { + error_propagate(errp, local_err); + } + } else { + mem_blk->can_offline = (removable != '0'); + } + } else { + if (mem_blk->online != (strncmp(status, "online", 6) == 0)) { + const char *new_state = mem_blk->online ? "online" : "offline"; + + ga_write_sysfs_file(dirfd, "state", new_state, strlen(new_state), + &local_err); + if (local_err) { + error_free(local_err); + result->response = + GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED; + goto out2; + } + + result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS; + result->has_error_code = false; + } /* otherwise pretend successful re-(on|off)-lining */ + } + g_free(status); + close(dirfd); + return; + +out2: + g_free(status); + close(dirfd); +out1: + if (!sys2memblk) { + result->has_error_code = true; + result->error_code = errno; + } +} + +GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp) +{ + GuestMemoryBlockList *head, **tail; + Error *local_err = NULL; + struct dirent *de; + DIR *dp; + + head = NULL; + tail = &head; + + dp = opendir("/sys/devices/system/memory/"); + if (!dp) { + /* it's ok if this happens to be a system that doesn't expose + * memory blocks via sysfs, but otherwise we should report + * an error + */ + if (errno != ENOENT) { + error_setg_errno(errp, errno, "Can't open directory" + "\"/sys/devices/system/memory/\""); + } + return NULL; + } + + /* Note: the phys_index of memory block may be discontinuous, + * this is because a memblk is the unit of the Sparse Memory design, which + * allows discontinuous memory ranges (ex. NUMA), so here we should + * traverse the memory block directory. + */ + while ((de = readdir(dp)) != NULL) { + GuestMemoryBlock *mem_blk; + + if ((strncmp(de->d_name, "memory", 6) != 0) || + !(de->d_type & DT_DIR)) { + continue; + } + + mem_blk = g_malloc0(sizeof *mem_blk); + /* The d_name is "memoryXXX", phys_index is block id, same as XXX */ + mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10); + mem_blk->has_can_offline = true; /* lolspeak ftw */ + transfer_memory_block(mem_blk, true, NULL, &local_err); + if (local_err) { + break; + } + + QAPI_LIST_APPEND(tail, mem_blk); + } + + closedir(dp); + if (local_err == NULL) { + /* there's no guest with zero memory blocks */ + if (head == NULL) { + error_setg(errp, "guest reported zero memory blocks!"); + } + return head; + } + + qapi_free_GuestMemoryBlockList(head); + error_propagate(errp, local_err); + return NULL; +} + +GuestMemoryBlockResponseList * +qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp) +{ + GuestMemoryBlockResponseList *head, **tail; + Error *local_err = NULL; + + head = NULL; + tail = &head; + + while (mem_blks != NULL) { + GuestMemoryBlockResponse *result; + GuestMemoryBlock *current_mem_blk = mem_blks->value; + + result = g_malloc0(sizeof(*result)); + result->phys_index = current_mem_blk->phys_index; + transfer_memory_block(current_mem_blk, false, result, &local_err); + if (local_err) { /* should never happen */ + goto err; + } + + QAPI_LIST_APPEND(tail, result); + mem_blks = mem_blks->next; + } + + return head; +err: + qapi_free_GuestMemoryBlockResponseList(head); + error_propagate(errp, local_err); + return NULL; +} + +GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp) +{ + Error *local_err = NULL; + char *dirpath; + int dirfd; + char *buf; + GuestMemoryBlockInfo *info; + + dirpath = g_strdup_printf("/sys/devices/system/memory/"); + dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); + if (dirfd == -1) { + error_setg_errno(errp, errno, "open(\"%s\")", dirpath); + g_free(dirpath); + return NULL; + } + g_free(dirpath); + + buf = g_malloc0(20); + ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err); + close(dirfd); + if (local_err) { + g_free(buf); + error_propagate(errp, local_err); + return NULL; + } + + info = g_new0(GuestMemoryBlockInfo, 1); + info->size = strtol(buf, NULL, 16); /* the unit is bytes */ + + g_free(buf); + + return info; +} + +#define MAX_NAME_LEN 128 +static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp) +{ +#ifdef CONFIG_LINUX + GuestDiskStatsInfoList *head = NULL, **tail = &head; + const char *diskstats = "/proc/diskstats"; + FILE *fp; + size_t n; + char *line = NULL; + + fp = fopen(diskstats, "r"); + if (fp == NULL) { + error_setg_errno(errp, errno, "open(\"%s\")", diskstats); + return NULL; + } + + while (getline(&line, &n, fp) != -1) { + g_autofree GuestDiskStatsInfo *diskstatinfo = NULL; + g_autofree GuestDiskStats *diskstat = NULL; + char dev_name[MAX_NAME_LEN]; + unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks, fl_ticks; + unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios; + unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec; + unsigned long dc_ios, dc_merges, dc_sec, fl_ios; + unsigned int major, minor; + int i; + + i = sscanf(line, "%u %u %s %lu %lu %lu" + "%lu %lu %lu %lu %u %u %u %u" + "%lu %lu %lu %u %lu %u", + &major, &minor, dev_name, + &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, + &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec, + &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks, + &dc_ios, &dc_merges, &dc_sec, &dc_ticks, + &fl_ios, &fl_ticks); + + if (i < 7) { + continue; + } + + diskstatinfo = g_new0(GuestDiskStatsInfo, 1); + diskstatinfo->name = g_strdup(dev_name); + diskstatinfo->major = major; + diskstatinfo->minor = minor; + + diskstat = g_new0(GuestDiskStats, 1); + if (i == 7) { + diskstat->has_read_ios = true; + diskstat->read_ios = rd_ios; + diskstat->has_read_sectors = true; + diskstat->read_sectors = rd_merges_or_rd_sec; + diskstat->has_write_ios = true; + diskstat->write_ios = rd_sec_or_wr_ios; + diskstat->has_write_sectors = true; + diskstat->write_sectors = rd_ticks_or_wr_sec; + } + if (i >= 14) { + diskstat->has_read_ios = true; + diskstat->read_ios = rd_ios; + diskstat->has_read_sectors = true; + diskstat->read_sectors = rd_sec_or_wr_ios; + diskstat->has_read_merges = true; + diskstat->read_merges = rd_merges_or_rd_sec; + diskstat->has_read_ticks = true; + diskstat->read_ticks = rd_ticks_or_wr_sec; + diskstat->has_write_ios = true; + diskstat->write_ios = wr_ios; + diskstat->has_write_sectors = true; + diskstat->write_sectors = wr_sec; + diskstat->has_write_merges = true; + diskstat->write_merges = wr_merges; + diskstat->has_write_ticks = true; + diskstat->write_ticks = wr_ticks; + diskstat->has_ios_pgr = true; + diskstat->ios_pgr = ios_pgr; + diskstat->has_total_ticks = true; + diskstat->total_ticks = tot_ticks; + diskstat->has_weight_ticks = true; + diskstat->weight_ticks = rq_ticks; + } + if (i >= 18) { + diskstat->has_discard_ios = true; + diskstat->discard_ios = dc_ios; + diskstat->has_discard_merges = true; + diskstat->discard_merges = dc_merges; + diskstat->has_discard_sectors = true; + diskstat->discard_sectors = dc_sec; + diskstat->has_discard_ticks = true; + diskstat->discard_ticks = dc_ticks; + } + if (i >= 20) { + diskstat->has_flush_ios = true; + diskstat->flush_ios = fl_ios; + diskstat->has_flush_ticks = true; + diskstat->flush_ticks = fl_ticks; + } + + diskstatinfo->stats = g_steal_pointer(&diskstat); + QAPI_LIST_APPEND(tail, diskstatinfo); + diskstatinfo = NULL; + } + free(line); + fclose(fp); + return head; +#else + g_debug("disk stats reporting available only for Linux"); + return NULL; +#endif +} + +GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp) +{ + return guest_get_diskstats(errp); +} + +GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp) +{ + GuestCpuStatsList *head = NULL, **tail = &head; + const char *cpustats = "/proc/stat"; + int clk_tck = sysconf(_SC_CLK_TCK); + FILE *fp; + size_t n; + char *line = NULL; + + fp = fopen(cpustats, "r"); + if (fp == NULL) { + error_setg_errno(errp, errno, "open(\"%s\")", cpustats); + return NULL; + } + + while (getline(&line, &n, fp) != -1) { + GuestCpuStats *cpustat = NULL; + GuestLinuxCpuStats *linuxcpustat; + int i; + unsigned long user, system, idle, iowait, irq, softirq, steal, guest; + unsigned long nice, guest_nice; + char name[64]; + + i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", + name, &user, &nice, &system, &idle, &iowait, &irq, &softirq, + &steal, &guest, &guest_nice); + + /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */ + if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] == '\0')) { + continue; + } + + if (i < 5) { + slog("Parsing cpu stat from %s failed, see \"man proc\"", cpustats); + break; + } + + cpustat = g_new0(GuestCpuStats, 1); + cpustat->type = GUEST_CPU_STATS_TYPE_LINUX; + + linuxcpustat = &cpustat->u.q_linux; + linuxcpustat->cpu = atoi(&name[3]); + linuxcpustat->user = user * 1000 / clk_tck; + linuxcpustat->nice = nice * 1000 / clk_tck; + linuxcpustat->system = system * 1000 / clk_tck; + linuxcpustat->idle = idle * 1000 / clk_tck; + + if (i > 5) { + linuxcpustat->has_iowait = true; + linuxcpustat->iowait = iowait * 1000 / clk_tck; + } + + if (i > 6) { + linuxcpustat->has_irq = true; + linuxcpustat->irq = irq * 1000 / clk_tck; + linuxcpustat->has_softirq = true; + linuxcpustat->softirq = softirq * 1000 / clk_tck; + } + + if (i > 8) { + linuxcpustat->has_steal = true; + linuxcpustat->steal = steal * 1000 / clk_tck; + } + + if (i > 9) { + linuxcpustat->has_guest = true; + linuxcpustat->guest = guest * 1000 / clk_tck; + } + + if (i > 10) { + linuxcpustat->has_guest = true; + linuxcpustat->guest = guest * 1000 / clk_tck; + linuxcpustat->has_guestnice = true; + linuxcpustat->guestnice = guest_nice * 1000 / clk_tck; + } + + QAPI_LIST_APPEND(tail, cpustat); + } + + free(line); + fclose(fp); + return head; +} diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 954efed01b..0bb8b9e2f3 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -15,7 +15,6 @@ #include #include #include -#include #include "guest-agent-core.h" #include "qga-qapi-commands.h" #include "qapi/error.h" @@ -26,31 +25,12 @@ #include "qemu/base64.h" #include "qemu/cutils.h" #include "commands-common.h" -#include "block/nvme.h" #include "cutils.h" #ifdef HAVE_UTMPX #include #endif -#if defined(__linux__) -#include -#include -#include -#include - -#ifdef CONFIG_LIBUDEV -#include -#endif - -#ifdef FIFREEZE -#define CONFIG_FSFREEZE -#endif -#ifdef FITRIM -#define CONFIG_FSTRIM -#endif -#endif - #ifdef HAVE_GETIFADDRS #include #include @@ -62,7 +42,173 @@ #endif #endif -static void ga_wait_child(pid_t pid, int *status, Error **errp) +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) +void free_fs_mount_list(FsMountList *mounts) +{ + FsMount *mount, *temp; + + if (!mounts) { + return; + } + + QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) { + QTAILQ_REMOVE(mounts, mount, next); + g_free(mount->dirname); + g_free(mount->devtype); + g_free(mount); + } +} +#endif + +#if defined(CONFIG_FSFREEZE) +typedef enum { + FSFREEZE_HOOK_THAW = 0, + FSFREEZE_HOOK_FREEZE, +} FsfreezeHookArg; + +static const char *fsfreeze_hook_arg_string[] = { + "thaw", + "freeze", +}; + +static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp) +{ + int status; + pid_t pid; + const char *hook; + const char *arg_str = fsfreeze_hook_arg_string[arg]; + Error *local_err = NULL; + + hook = ga_fsfreeze_hook(ga_state); + if (!hook) { + return; + } + if (access(hook, X_OK) != 0) { + error_setg_errno(errp, errno, "can't access fsfreeze hook '%s'", hook); + return; + } + + slog("executing fsfreeze hook with arg '%s'", arg_str); + pid = fork(); + if (pid == 0) { + setsid(); + reopen_fd_to_null(0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + + execl(hook, hook, arg_str, NULL); + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + return; + } + + ga_wait_child(pid, &status, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + if (!WIFEXITED(status)) { + error_setg(errp, "fsfreeze hook has terminated abnormally"); + return; + } + + status = WEXITSTATUS(status); + if (status) { + error_setg(errp, "fsfreeze hook has failed with status %d", status); + return; + } +} + +int64_t qmp_guest_fsfreeze_thaw(Error **errp) +{ + int ret; + + ret = qmp_guest_fsfreeze_do_thaw(errp); + if (ret >= 0) { + ga_unset_frozen(ga_state); + execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp); + } else { + ret = 0; + } + + return ret; +} + +int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints, + strList *mountpoints, + Error **errp) +{ + int ret; + FsMountList mounts; + Error *local_err = NULL; + + slog("guest-fsfreeze called"); + + execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return -1; + } + + QTAILQ_INIT(&mounts); + if (!build_fs_mount_list(&mounts, &local_err)) { + error_propagate(errp, local_err); + return -1; + } + + /* cannot risk guest agent blocking itself on a write in this state */ + ga_set_frozen(ga_state); + + ret = qmp_guest_fsfreeze_do_freeze_list(has_mountpoints, mountpoints, + mounts, errp); + + free_fs_mount_list(&mounts); + /* We may not issue any FIFREEZE here. + * Just unset ga_state here and ready for the next call. + */ + if (ret == 0) { + ga_unset_frozen(ga_state); + } else if (ret < 0) { + qmp_guest_fsfreeze_thaw(NULL); + } + return ret; +} + +/* + * Return status of freeze/thaw + */ +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp) +{ + if (ga_is_frozen(ga_state)) { + return GUEST_FSFREEZE_STATUS_FROZEN; + } + + return GUEST_FSFREEZE_STATUS_THAWED; +} + +int64_t qmp_guest_fsfreeze_freeze(Error **errp) +{ + return qmp_guest_fsfreeze_freeze_list(false, NULL, errp); +} + +static void guest_fsfreeze_cleanup(void) +{ + Error *err = NULL; + + if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) { + qmp_guest_fsfreeze_thaw(&err); + if (err) { + slog("failed to clean up frozen filesystems: %s", + error_get_pretty(err)); + error_free(err); + } + } +} +#endif /* CONFIG_FSFREEZE */ + +void ga_wait_child(pid_t pid, int *status, Error **errp) { pid_t rpid; @@ -617,2375 +763,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp) } } -/* linux-specific implementations. avoid this if at all possible. */ -#if defined(__linux__) - -#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) -typedef struct FsMount { - char *dirname; - char *devtype; - unsigned int devmajor, devminor; - QTAILQ_ENTRY(FsMount) next; -} FsMount; - -typedef QTAILQ_HEAD(FsMountList, FsMount) FsMountList; - -static void free_fs_mount_list(FsMountList *mounts) -{ - FsMount *mount, *temp; - - if (!mounts) { - return; - } - - QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) { - QTAILQ_REMOVE(mounts, mount, next); - g_free(mount->dirname); - g_free(mount->devtype); - g_free(mount); - } -} - -static int dev_major_minor(const char *devpath, - unsigned int *devmajor, unsigned int *devminor) -{ - struct stat st; - - *devmajor = 0; - *devminor = 0; - - if (stat(devpath, &st) < 0) { - slog("failed to stat device file '%s': %s", devpath, strerror(errno)); - return -1; - } - if (S_ISDIR(st.st_mode)) { - /* It is bind mount */ - return -2; - } - if (S_ISBLK(st.st_mode)) { - *devmajor = major(st.st_rdev); - *devminor = minor(st.st_rdev); - return 0; - } - return -1; -} - -/* - * Walk the mount table and build a list of local file systems - */ -static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp) -{ - struct mntent *ment; - FsMount *mount; - char const *mtab = "/proc/self/mounts"; - FILE *fp; - unsigned int devmajor, devminor; - - fp = setmntent(mtab, "r"); - if (!fp) { - error_setg(errp, "failed to open mtab file: '%s'", mtab); - return false; - } - - while ((ment = getmntent(fp))) { - /* - * An entry which device name doesn't start with a '/' is - * either a dummy file system or a network file system. - * Add special handling for smbfs and cifs as is done by - * coreutils as well. - */ - if ((ment->mnt_fsname[0] != '/') || - (strcmp(ment->mnt_type, "smbfs") == 0) || - (strcmp(ment->mnt_type, "cifs") == 0)) { - continue; - } - if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) { - /* Skip bind mounts */ - continue; - } - - mount = g_new0(FsMount, 1); - mount->dirname = g_strdup(ment->mnt_dir); - mount->devtype = g_strdup(ment->mnt_type); - mount->devmajor = devmajor; - mount->devminor = devminor; - - QTAILQ_INSERT_TAIL(mounts, mount, next); - } - - endmntent(fp); - return true; -} - -static void decode_mntname(char *name, int len) -{ - int i, j = 0; - for (i = 0; i <= len; i++) { - if (name[i] != '\\') { - name[j++] = name[i]; - } else if (name[i + 1] == '\\') { - name[j++] = '\\'; - i++; - } else if (name[i + 1] >= '0' && name[i + 1] <= '3' && - name[i + 2] >= '0' && name[i + 2] <= '7' && - name[i + 3] >= '0' && name[i + 3] <= '7') { - name[j++] = (name[i + 1] - '0') * 64 + - (name[i + 2] - '0') * 8 + - (name[i + 3] - '0'); - i += 3; - } else { - name[j++] = name[i]; - } - } -} - -static bool build_fs_mount_list(FsMountList *mounts, Error **errp) -{ - FsMount *mount; - char const *mountinfo = "/proc/self/mountinfo"; - FILE *fp; - char *line = NULL, *dash; - size_t n; - char check; - unsigned int devmajor, devminor; - int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e; - - fp = fopen(mountinfo, "r"); - if (!fp) { - return build_fs_mount_list_from_mtab(mounts, errp); - } - - while (getline(&line, &n, fp) != -1) { - ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c", - &devmajor, &devminor, &dir_s, &dir_e, &check); - if (ret < 3) { - continue; - } - dash = strstr(line + dir_e, " - "); - if (!dash) { - continue; - } - ret = sscanf(dash, " - %n%*s%n %n%*s%n%c", - &type_s, &type_e, &dev_s, &dev_e, &check); - if (ret < 1) { - continue; - } - line[dir_e] = 0; - dash[type_e] = 0; - dash[dev_e] = 0; - decode_mntname(line + dir_s, dir_e - dir_s); - decode_mntname(dash + dev_s, dev_e - dev_s); - if (devmajor == 0) { - /* btrfs reports major number = 0 */ - if (strcmp("btrfs", dash + type_s) != 0 || - dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) { - continue; - } - } - - mount = g_new0(FsMount, 1); - mount->dirname = g_strdup(line + dir_s); - mount->devtype = g_strdup(dash + type_s); - mount->devmajor = devmajor; - mount->devminor = devminor; - - QTAILQ_INSERT_TAIL(mounts, mount, next); - } - free(line); - - fclose(fp); - return true; -} -#endif - -#if defined(CONFIG_FSFREEZE) - -static char *get_pci_driver(char const *syspath, int pathlen, Error **errp) -{ - char *path; - char *dpath; - char *driver = NULL; - char buf[PATH_MAX]; - ssize_t len; - - path = g_strndup(syspath, pathlen); - dpath = g_strdup_printf("%s/driver", path); - len = readlink(dpath, buf, sizeof(buf) - 1); - if (len != -1) { - buf[len] = 0; - driver = g_path_get_basename(buf); - } - g_free(dpath); - g_free(path); - return driver; -} - -static int compare_uint(const void *_a, const void *_b) -{ - unsigned int a = *(unsigned int *)_a; - unsigned int b = *(unsigned int *)_b; - - return a < b ? -1 : a > b ? 1 : 0; -} - -/* Walk the specified sysfs and build a sorted list of host or ata numbers */ -static int build_hosts(char const *syspath, char const *host, bool ata, - unsigned int *hosts, int hosts_max, Error **errp) -{ - char *path; - DIR *dir; - struct dirent *entry; - int i = 0; - - path = g_strndup(syspath, host - syspath); - dir = opendir(path); - if (!dir) { - error_setg_errno(errp, errno, "opendir(\"%s\")", path); - g_free(path); - return -1; - } - - while (i < hosts_max) { - entry = readdir(dir); - if (!entry) { - break; - } - if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) { - ++i; - } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) == 1) { - ++i; - } - } - - qsort(hosts, i, sizeof(hosts[0]), compare_uint); - - g_free(path); - closedir(dir); - return i; -} - -/* - * Store disk device info for devices on the PCI bus. - * Returns true if information has been stored, or false for failure. - */ -static bool build_guest_fsinfo_for_pci_dev(char const *syspath, - GuestDiskAddress *disk, - Error **errp) -{ - unsigned int pci[4], host, hosts[8], tgt[3]; - int i, nhosts = 0, pcilen; - GuestPCIAddress *pciaddr = disk->pci_controller; - bool has_ata = false, has_host = false, has_tgt = false; - char *p, *q, *driver = NULL; - bool ret = false; - - p = strstr(syspath, "/devices/pci"); - if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n", - pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) { - g_debug("only pci device is supported: sysfs path '%s'", syspath); - return false; - } - - p += 12 + pcilen; - while (true) { - driver = get_pci_driver(syspath, p - syspath, errp); - if (driver && (g_str_equal(driver, "ata_piix") || - g_str_equal(driver, "sym53c8xx") || - g_str_equal(driver, "virtio-pci") || - g_str_equal(driver, "ahci") || - g_str_equal(driver, "nvme"))) { - break; - } - - g_free(driver); - if (sscanf(p, "/%x:%x:%x.%x%n", - pci, pci + 1, pci + 2, pci + 3, &pcilen) == 4) { - p += pcilen; - continue; - } - - g_debug("unsupported driver or sysfs path '%s'", syspath); - return false; - } - - p = strstr(syspath, "/target"); - if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u", - tgt, tgt + 1, tgt + 2) == 3) { - has_tgt = true; - } - - p = strstr(syspath, "/ata"); - if (p) { - q = p + 4; - has_ata = true; - } else { - p = strstr(syspath, "/host"); - q = p + 5; - } - if (p && sscanf(q, "%u", &host) == 1) { - has_host = true; - nhosts = build_hosts(syspath, p, has_ata, hosts, - ARRAY_SIZE(hosts), errp); - if (nhosts < 0) { - goto cleanup; - } - } - - pciaddr->domain = pci[0]; - pciaddr->bus = pci[1]; - pciaddr->slot = pci[2]; - pciaddr->function = pci[3]; - - if (strcmp(driver, "ata_piix") == 0) { - /* a host per ide bus, target*:0::0 */ - if (!has_host || !has_tgt) { - g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); - goto cleanup; - } - for (i = 0; i < nhosts; i++) { - if (host == hosts[i]) { - disk->bus_type = GUEST_DISK_BUS_TYPE_IDE; - disk->bus = i; - disk->unit = tgt[1]; - break; - } - } - if (i >= nhosts) { - g_debug("no host for '%s' (driver '%s')", syspath, driver); - goto cleanup; - } - } else if (strcmp(driver, "sym53c8xx") == 0) { - /* scsi(LSI Logic): target*:0::0 */ - if (!has_tgt) { - g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); - goto cleanup; - } - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI; - disk->unit = tgt[1]; - } else if (strcmp(driver, "virtio-pci") == 0) { - if (has_tgt) { - /* virtio-scsi: target*:0:0: */ - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI; - disk->unit = tgt[2]; - } else { - /* virtio-blk: 1 disk per 1 device */ - disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO; - } - } else if (strcmp(driver, "ahci") == 0) { - /* ahci: 1 host per 1 unit */ - if (!has_host || !has_tgt) { - g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver); - goto cleanup; - } - for (i = 0; i < nhosts; i++) { - if (host == hosts[i]) { - disk->unit = i; - disk->bus_type = GUEST_DISK_BUS_TYPE_SATA; - break; - } - } - if (i >= nhosts) { - g_debug("no host for '%s' (driver '%s')", syspath, driver); - goto cleanup; - } - } else if (strcmp(driver, "nvme") == 0) { - disk->bus_type = GUEST_DISK_BUS_TYPE_NVME; - } else { - g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath); - goto cleanup; - } - - ret = true; - -cleanup: - g_free(driver); - return ret; -} - -/* - * Store disk device info for non-PCI virtio devices (for example s390x - * channel I/O devices). Returns true if information has been stored, or - * false for failure. - */ -static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath, - GuestDiskAddress *disk, - Error **errp) -{ - unsigned int tgt[3]; - char *p; - - if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) { - g_debug("Unsupported virtio device '%s'", syspath); - return false; - } - - p = strstr(syspath, "/target"); - if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u", - &tgt[0], &tgt[1], &tgt[2]) == 3) { - /* virtio-scsi: target*:0:: */ - disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI; - disk->bus = tgt[0]; - disk->target = tgt[1]; - disk->unit = tgt[2]; - } else { - /* virtio-blk: 1 disk per 1 device */ - disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO; - } - - return true; -} - -/* - * Store disk device info for CCW devices (s390x channel I/O devices). - * Returns true if information has been stored, or false for failure. - */ -static bool build_guest_fsinfo_for_ccw_dev(char const *syspath, - GuestDiskAddress *disk, - Error **errp) -{ - unsigned int cssid, ssid, subchno, devno; - char *p; - - p = strstr(syspath, "/devices/css"); - if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/", - &cssid, &ssid, &subchno, &devno) < 4) { - g_debug("could not parse ccw device sysfs path: %s", syspath); - return false; - } - - disk->has_ccw_address = true; - disk->ccw_address = g_new0(GuestCCWAddress, 1); - disk->ccw_address->cssid = cssid; - disk->ccw_address->ssid = ssid; - disk->ccw_address->subchno = subchno; - disk->ccw_address->devno = devno; - - if (strstr(p, "/virtio")) { - build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp); - } - - return true; -} - -/* Store disk device info specified by @sysfs into @fs */ -static void build_guest_fsinfo_for_real_device(char const *syspath, - GuestFilesystemInfo *fs, - Error **errp) -{ - GuestDiskAddress *disk; - GuestPCIAddress *pciaddr; - bool has_hwinf; -#ifdef CONFIG_LIBUDEV - struct udev *udev = NULL; - struct udev_device *udevice = NULL; -#endif - - pciaddr = g_new0(GuestPCIAddress, 1); - pciaddr->domain = -1; /* -1 means field is invalid */ - pciaddr->bus = -1; - pciaddr->slot = -1; - pciaddr->function = -1; - - disk = g_new0(GuestDiskAddress, 1); - disk->pci_controller = pciaddr; - disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN; - -#ifdef CONFIG_LIBUDEV - udev = udev_new(); - udevice = udev_device_new_from_syspath(udev, syspath); - if (udev == NULL || udevice == NULL) { - g_debug("failed to query udev"); - } else { - const char *devnode, *serial; - devnode = udev_device_get_devnode(udevice); - if (devnode != NULL) { - disk->dev = g_strdup(devnode); - disk->has_dev = true; - } - serial = udev_device_get_property_value(udevice, "ID_SERIAL"); - if (serial != NULL && *serial != 0) { - disk->serial = g_strdup(serial); - disk->has_serial = true; - } - } - - udev_unref(udev); - udev_device_unref(udevice); -#endif - - if (strstr(syspath, "/devices/pci")) { - has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk, errp); - } else if (strstr(syspath, "/devices/css")) { - has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk, errp); - } else if (strstr(syspath, "/virtio")) { - has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp); - } else { - g_debug("Unsupported device type for '%s'", syspath); - has_hwinf = false; - } - - if (has_hwinf || disk->has_dev || disk->has_serial) { - QAPI_LIST_PREPEND(fs->disk, disk); - } else { - qapi_free_GuestDiskAddress(disk); - } -} - -static void build_guest_fsinfo_for_device(char const *devpath, - GuestFilesystemInfo *fs, - Error **errp); - -/* Store a list of slave devices of virtual volume specified by @syspath into - * @fs */ -static void build_guest_fsinfo_for_virtual_device(char const *syspath, - GuestFilesystemInfo *fs, - Error **errp) -{ - Error *err = NULL; - DIR *dir; - char *dirpath; - struct dirent *entry; - - dirpath = g_strdup_printf("%s/slaves", syspath); - dir = opendir(dirpath); - if (!dir) { - if (errno != ENOENT) { - error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath); - } - g_free(dirpath); - return; - } - - for (;;) { - errno = 0; - entry = readdir(dir); - if (entry == NULL) { - if (errno) { - error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath); - } - break; - } - - if (entry->d_type == DT_LNK) { - char *path; - - g_debug(" slave device '%s'", entry->d_name); - path = g_strdup_printf("%s/slaves/%s", syspath, entry->d_name); - build_guest_fsinfo_for_device(path, fs, &err); - g_free(path); - - if (err) { - error_propagate(errp, err); - break; - } - } - } - - g_free(dirpath); - closedir(dir); -} - -static bool is_disk_virtual(const char *devpath, Error **errp) -{ - g_autofree char *syspath = realpath(devpath, NULL); - - if (!syspath) { - error_setg_errno(errp, errno, "realpath(\"%s\")", devpath); - return false; - } - return strstr(syspath, "/devices/virtual/block/") != NULL; -} - -/* Dispatch to functions for virtual/real device */ -static void build_guest_fsinfo_for_device(char const *devpath, - GuestFilesystemInfo *fs, - Error **errp) -{ - ERRP_GUARD(); - g_autofree char *syspath = NULL; - bool is_virtual = false; - - syspath = realpath(devpath, NULL); - if (!syspath) { - if (errno != ENOENT) { - error_setg_errno(errp, errno, "realpath(\"%s\")", devpath); - return; - } - - /* ENOENT: This devpath may not exist because of container config */ - if (!fs->name) { - fs->name = g_path_get_basename(devpath); - } - return; - } - - if (!fs->name) { - fs->name = g_path_get_basename(syspath); - } - - g_debug(" parse sysfs path '%s'", syspath); - is_virtual = is_disk_virtual(syspath, errp); - if (*errp != NULL) { - return; - } - if (is_virtual) { - build_guest_fsinfo_for_virtual_device(syspath, fs, errp); - } else { - build_guest_fsinfo_for_real_device(syspath, fs, errp); - } -} - -#ifdef CONFIG_LIBUDEV - -/* - * Wrapper around build_guest_fsinfo_for_device() for getting just - * the disk address. - */ -static GuestDiskAddress *get_disk_address(const char *syspath, Error **errp) -{ - g_autoptr(GuestFilesystemInfo) fs = NULL; - - fs = g_new0(GuestFilesystemInfo, 1); - build_guest_fsinfo_for_device(syspath, fs, errp); - if (fs->disk != NULL) { - return g_steal_pointer(&fs->disk->value); - } - return NULL; -} - -static char *get_alias_for_syspath(const char *syspath) -{ - struct udev *udev = NULL; - struct udev_device *udevice = NULL; - char *ret = NULL; - - udev = udev_new(); - if (udev == NULL) { - g_debug("failed to query udev"); - goto out; - } - udevice = udev_device_new_from_syspath(udev, syspath); - if (udevice == NULL) { - g_debug("failed to query udev for path: %s", syspath); - goto out; - } else { - const char *alias = udev_device_get_property_value( - udevice, "DM_NAME"); - /* - * NULL means there was an error and empty string means there is no - * alias. In case of no alias we return NULL instead of empty string. - */ - if (alias == NULL) { - g_debug("failed to query udev for device alias for: %s", - syspath); - } else if (*alias != 0) { - ret = g_strdup(alias); - } - } - -out: - udev_unref(udev); - udev_device_unref(udevice); - return ret; -} - -static char *get_device_for_syspath(const char *syspath) -{ - struct udev *udev = NULL; - struct udev_device *udevice = NULL; - char *ret = NULL; - - udev = udev_new(); - if (udev == NULL) { - g_debug("failed to query udev"); - goto out; - } - udevice = udev_device_new_from_syspath(udev, syspath); - if (udevice == NULL) { - g_debug("failed to query udev for path: %s", syspath); - goto out; - } else { - ret = g_strdup(udev_device_get_devnode(udevice)); - } - -out: - udev_unref(udev); - udev_device_unref(udevice); - return ret; -} - -static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk) -{ - g_autofree char *deps_dir = NULL; - const gchar *dep; - GDir *dp_deps = NULL; - - /* List dependent disks */ - deps_dir = g_strdup_printf("%s/slaves", disk_dir); - g_debug(" listing entries in: %s", deps_dir); - dp_deps = g_dir_open(deps_dir, 0, NULL); - if (dp_deps == NULL) { - g_debug("failed to list entries in %s", deps_dir); - return; - } - disk->has_dependencies = true; - while ((dep = g_dir_read_name(dp_deps)) != NULL) { - g_autofree char *dep_dir = NULL; - char *dev_name; - - /* Add dependent disks */ - dep_dir = g_strdup_printf("%s/%s", deps_dir, dep); - dev_name = get_device_for_syspath(dep_dir); - if (dev_name != NULL) { - g_debug(" adding dependent device: %s", dev_name); - QAPI_LIST_PREPEND(disk->dependencies, dev_name); - } - } - g_dir_close(dp_deps); -} - -/* - * Detect partitions subdirectory, name is "" or - * "p" - * - * @disk_name -- last component of /sys path (e.g. sda) - * @disk_dir -- sys path of the disk (e.g. /sys/block/sda) - * @disk_dev -- device node of the disk (e.g. /dev/sda) - */ -static GuestDiskInfoList *get_disk_partitions( - GuestDiskInfoList *list, - const char *disk_name, const char *disk_dir, - const char *disk_dev) -{ - GuestDiskInfoList *ret = list; - struct dirent *de_disk; - DIR *dp_disk = NULL; - size_t len = strlen(disk_name); - - dp_disk = opendir(disk_dir); - while ((de_disk = readdir(dp_disk)) != NULL) { - g_autofree char *partition_dir = NULL; - char *dev_name; - GuestDiskInfo *partition; - - if (!(de_disk->d_type & DT_DIR)) { - continue; - } - - if (!(strncmp(disk_name, de_disk->d_name, len) == 0 && - ((*(de_disk->d_name + len) == 'p' && - isdigit(*(de_disk->d_name + len + 1))) || - isdigit(*(de_disk->d_name + len))))) { - continue; - } - - partition_dir = g_strdup_printf("%s/%s", - disk_dir, de_disk->d_name); - dev_name = get_device_for_syspath(partition_dir); - if (dev_name == NULL) { - g_debug("Failed to get device name for syspath: %s", - disk_dir); - continue; - } - partition = g_new0(GuestDiskInfo, 1); - partition->name = dev_name; - partition->partition = true; - partition->has_dependencies = true; - /* Add parent disk as dependent for easier tracking of hierarchy */ - QAPI_LIST_PREPEND(partition->dependencies, g_strdup(disk_dev)); - - QAPI_LIST_PREPEND(ret, partition); - } - closedir(dp_disk); - - return ret; -} - -static void get_nvme_smart(GuestDiskInfo *disk) -{ - int fd; - GuestNVMeSmart *smart; - NvmeSmartLog log = {0}; - struct nvme_admin_cmd cmd = { - .opcode = NVME_ADM_CMD_GET_LOG_PAGE, - .nsid = NVME_NSID_BROADCAST, - .addr = (uintptr_t)&log, - .data_len = sizeof(log), - .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */ - | (((sizeof(log) >> 2) - 1) << 16) - }; - - fd = qga_open_cloexec(disk->name, O_RDONLY, 0); - if (fd == -1) { - g_debug("Failed to open device: %s: %s", disk->name, g_strerror(errno)); - return; - } - - if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) { - g_debug("Failed to get smart: %s: %s", disk->name, g_strerror(errno)); - close(fd); - return; - } - - disk->has_smart = true; - disk->smart = g_new0(GuestDiskSmart, 1); - disk->smart->type = GUEST_DISK_BUS_TYPE_NVME; - - smart = &disk->smart->u.nvme; - smart->critical_warning = log.critical_warning; - smart->temperature = lduw_le_p(&log.temperature); /* unaligned field */ - smart->available_spare = log.available_spare; - smart->available_spare_threshold = log.available_spare_threshold; - smart->percentage_used = log.percentage_used; - smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]); - smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]); - smart->data_units_written_lo = le64_to_cpu(log.data_units_written[0]); - smart->data_units_written_hi = le64_to_cpu(log.data_units_written[1]); - smart->host_read_commands_lo = le64_to_cpu(log.host_read_commands[0]); - smart->host_read_commands_hi = le64_to_cpu(log.host_read_commands[1]); - smart->host_write_commands_lo = le64_to_cpu(log.host_write_commands[0]); - smart->host_write_commands_hi = le64_to_cpu(log.host_write_commands[1]); - smart->controller_busy_time_lo = le64_to_cpu(log.controller_busy_time[0]); - smart->controller_busy_time_hi = le64_to_cpu(log.controller_busy_time[1]); - smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]); - smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]); - smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]); - smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]); - smart->unsafe_shutdowns_lo = le64_to_cpu(log.unsafe_shutdowns[0]); - smart->unsafe_shutdowns_hi = le64_to_cpu(log.unsafe_shutdowns[1]); - smart->media_errors_lo = le64_to_cpu(log.media_errors[0]); - smart->media_errors_hi = le64_to_cpu(log.media_errors[1]); - smart->number_of_error_log_entries_lo = - le64_to_cpu(log.number_of_error_log_entries[0]); - smart->number_of_error_log_entries_hi = - le64_to_cpu(log.number_of_error_log_entries[1]); - - close(fd); -} - -static void get_disk_smart(GuestDiskInfo *disk) -{ - if (disk->has_address - && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) { - get_nvme_smart(disk); - } -} - -GuestDiskInfoList *qmp_guest_get_disks(Error **errp) -{ - GuestDiskInfoList *ret = NULL; - GuestDiskInfo *disk; - DIR *dp = NULL; - struct dirent *de = NULL; - - g_debug("listing /sys/block directory"); - dp = opendir("/sys/block"); - if (dp == NULL) { - error_setg_errno(errp, errno, "Can't open directory \"/sys/block\""); - return NULL; - } - while ((de = readdir(dp)) != NULL) { - g_autofree char *disk_dir = NULL, *line = NULL, - *size_path = NULL; - char *dev_name; - Error *local_err = NULL; - if (de->d_type != DT_LNK) { - g_debug(" skipping entry: %s", de->d_name); - continue; - } - - /* Check size and skip zero-sized disks */ - g_debug(" checking disk size"); - size_path = g_strdup_printf("/sys/block/%s/size", de->d_name); - if (!g_file_get_contents(size_path, &line, NULL, NULL)) { - g_debug(" failed to read disk size"); - continue; - } - if (g_strcmp0(line, "0\n") == 0) { - g_debug(" skipping zero-sized disk"); - continue; - } - - g_debug(" adding %s", de->d_name); - disk_dir = g_strdup_printf("/sys/block/%s", de->d_name); - dev_name = get_device_for_syspath(disk_dir); - if (dev_name == NULL) { - g_debug("Failed to get device name for syspath: %s", - disk_dir); - continue; - } - disk = g_new0(GuestDiskInfo, 1); - disk->name = dev_name; - disk->partition = false; - disk->alias = get_alias_for_syspath(disk_dir); - disk->has_alias = (disk->alias != NULL); - QAPI_LIST_PREPEND(ret, disk); - - /* Get address for non-virtual devices */ - bool is_virtual = is_disk_virtual(disk_dir, &local_err); - if (local_err != NULL) { - g_debug(" failed to check disk path, ignoring error: %s", - error_get_pretty(local_err)); - error_free(local_err); - local_err = NULL; - /* Don't try to get the address */ - is_virtual = true; - } - if (!is_virtual) { - disk->address = get_disk_address(disk_dir, &local_err); - if (local_err != NULL) { - g_debug(" failed to get device info, ignoring error: %s", - error_get_pretty(local_err)); - error_free(local_err); - local_err = NULL; - } else if (disk->address != NULL) { - disk->has_address = true; - } - } - - get_disk_deps(disk_dir, disk); - get_disk_smart(disk); - ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name); - } - - closedir(dp); - - return ret; -} - -#else - -GuestDiskInfoList *qmp_guest_get_disks(Error **errp) -{ - error_setg(errp, QERR_UNSUPPORTED); - return NULL; -} - -#endif - -/* Return a list of the disk device(s)' info which @mount lies on */ -static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount, - Error **errp) -{ - GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs)); - struct statvfs buf; - unsigned long used, nonroot_total, fr_size; - char *devpath = g_strdup_printf("/sys/dev/block/%u:%u", - mount->devmajor, mount->devminor); - - fs->mountpoint = g_strdup(mount->dirname); - fs->type = g_strdup(mount->devtype); - build_guest_fsinfo_for_device(devpath, fs, errp); - - if (statvfs(fs->mountpoint, &buf) == 0) { - fr_size = buf.f_frsize; - used = buf.f_blocks - buf.f_bfree; - nonroot_total = used + buf.f_bavail; - fs->used_bytes = used * fr_size; - fs->total_bytes = nonroot_total * fr_size; - - fs->has_total_bytes = true; - fs->has_used_bytes = true; - } - - g_free(devpath); - - return fs; -} - -GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) -{ - FsMountList mounts; - struct FsMount *mount; - GuestFilesystemInfoList *ret = NULL; - Error *local_err = NULL; - - QTAILQ_INIT(&mounts); - if (!build_fs_mount_list(&mounts, &local_err)) { - error_propagate(errp, local_err); - return NULL; - } - - QTAILQ_FOREACH(mount, &mounts, next) { - g_debug("Building guest fsinfo for '%s'", mount->dirname); - - QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount, &local_err)); - if (local_err) { - error_propagate(errp, local_err); - qapi_free_GuestFilesystemInfoList(ret); - ret = NULL; - break; - } - } - - free_fs_mount_list(&mounts); - return ret; -} - - -typedef enum { - FSFREEZE_HOOK_THAW = 0, - FSFREEZE_HOOK_FREEZE, -} FsfreezeHookArg; - -static const char *fsfreeze_hook_arg_string[] = { - "thaw", - "freeze", -}; - -static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp) -{ - int status; - pid_t pid; - const char *hook; - const char *arg_str = fsfreeze_hook_arg_string[arg]; - Error *local_err = NULL; - - hook = ga_fsfreeze_hook(ga_state); - if (!hook) { - return; - } - if (access(hook, X_OK) != 0) { - error_setg_errno(errp, errno, "can't access fsfreeze hook '%s'", hook); - return; - } - - slog("executing fsfreeze hook with arg '%s'", arg_str); - pid = fork(); - if (pid == 0) { - setsid(); - reopen_fd_to_null(0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - - execl(hook, hook, arg_str, NULL); - _exit(EXIT_FAILURE); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - return; - } - - ga_wait_child(pid, &status, &local_err); - if (local_err) { - error_propagate(errp, local_err); - return; - } - - if (!WIFEXITED(status)) { - error_setg(errp, "fsfreeze hook has terminated abnormally"); - return; - } - - status = WEXITSTATUS(status); - if (status) { - error_setg(errp, "fsfreeze hook has failed with status %d", status); - return; - } -} - -/* - * Return status of freeze/thaw - */ -GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp) -{ - if (ga_is_frozen(ga_state)) { - return GUEST_FSFREEZE_STATUS_FROZEN; - } - - return GUEST_FSFREEZE_STATUS_THAWED; -} - -int64_t qmp_guest_fsfreeze_freeze(Error **errp) -{ - return qmp_guest_fsfreeze_freeze_list(false, NULL, errp); -} - -/* - * Walk list of mounted file systems in the guest, and freeze the ones which - * are real local file systems. - */ -int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints, - strList *mountpoints, - Error **errp) -{ - int ret = 0, i = 0; - strList *list; - FsMountList mounts; - struct FsMount *mount; - Error *local_err = NULL; - int fd; - - slog("guest-fsfreeze called"); - - execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err); - if (local_err) { - error_propagate(errp, local_err); - return -1; - } - - QTAILQ_INIT(&mounts); - if (!build_fs_mount_list(&mounts, &local_err)) { - error_propagate(errp, local_err); - return -1; - } - - /* cannot risk guest agent blocking itself on a write in this state */ - ga_set_frozen(ga_state); - - QTAILQ_FOREACH_REVERSE(mount, &mounts, next) { - /* To issue fsfreeze in the reverse order of mounts, check if the - * mount is listed in the list here */ - if (has_mountpoints) { - for (list = mountpoints; list; list = list->next) { - if (strcmp(list->value, mount->dirname) == 0) { - break; - } - } - if (!list) { - continue; - } - } - - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); - if (fd == -1) { - error_setg_errno(errp, errno, "failed to open %s", mount->dirname); - goto error; - } - - /* we try to cull filesystems we know won't work in advance, but other - * filesystems may not implement fsfreeze for less obvious reasons. - * these will report EOPNOTSUPP. we simply ignore these when tallying - * the number of frozen filesystems. - * if a filesystem is mounted more than once (aka bind mount) a - * consecutive attempt to freeze an already frozen filesystem will - * return EBUSY. - * - * any other error means a failure to freeze a filesystem we - * expect to be freezable, so return an error in those cases - * and return system to thawed state. - */ - ret = ioctl(fd, FIFREEZE); - if (ret == -1) { - if (errno != EOPNOTSUPP && errno != EBUSY) { - error_setg_errno(errp, errno, "failed to freeze %s", - mount->dirname); - close(fd); - goto error; - } - } else { - i++; - } - close(fd); - } - - free_fs_mount_list(&mounts); - /* We may not issue any FIFREEZE here. - * Just unset ga_state here and ready for the next call. - */ - if (i == 0) { - ga_unset_frozen(ga_state); - } - return i; - -error: - free_fs_mount_list(&mounts); - qmp_guest_fsfreeze_thaw(NULL); - return 0; -} - -/* - * Walk list of frozen file systems in the guest, and thaw them. - */ -int64_t qmp_guest_fsfreeze_thaw(Error **errp) -{ - int ret; - FsMountList mounts; - FsMount *mount; - int fd, i = 0, logged; - Error *local_err = NULL; - - QTAILQ_INIT(&mounts); - if (!build_fs_mount_list(&mounts, &local_err)) { - error_propagate(errp, local_err); - return 0; - } - - QTAILQ_FOREACH(mount, &mounts, next) { - logged = false; - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); - if (fd == -1) { - continue; - } - /* we have no way of knowing whether a filesystem was actually unfrozen - * as a result of a successful call to FITHAW, only that if an error - * was returned the filesystem was *not* unfrozen by that particular - * call. - * - * since multiple preceding FIFREEZEs require multiple calls to FITHAW - * to unfreeze, continuing issuing FITHAW until an error is returned, - * in which case either the filesystem is in an unfreezable state, or, - * more likely, it was thawed previously (and remains so afterward). - * - * also, since the most recent successful call is the one that did - * the actual unfreeze, we can use this to provide an accurate count - * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which - * may * be useful for determining whether a filesystem was unfrozen - * during the freeze/thaw phase by a process other than qemu-ga. - */ - do { - ret = ioctl(fd, FITHAW); - if (ret == 0 && !logged) { - i++; - logged = true; - } - } while (ret == 0); - close(fd); - } - - ga_unset_frozen(ga_state); - free_fs_mount_list(&mounts); - - execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp); - - return i; -} - -static void guest_fsfreeze_cleanup(void) -{ - Error *err = NULL; - - if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) { - qmp_guest_fsfreeze_thaw(&err); - if (err) { - slog("failed to clean up frozen filesystems: %s", - error_get_pretty(err)); - error_free(err); - } - } -} -#endif /* CONFIG_FSFREEZE */ - -#if defined(CONFIG_FSTRIM) -/* - * Walk list of mounted file systems in the guest, and trim them. - */ -GuestFilesystemTrimResponse * -qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp) -{ - GuestFilesystemTrimResponse *response; - GuestFilesystemTrimResult *result; - int ret = 0; - FsMountList mounts; - struct FsMount *mount; - int fd; - struct fstrim_range r; - - slog("guest-fstrim called"); - - QTAILQ_INIT(&mounts); - if (!build_fs_mount_list(&mounts, errp)) { - return NULL; - } - - response = g_malloc0(sizeof(*response)); - - QTAILQ_FOREACH(mount, &mounts, next) { - result = g_malloc0(sizeof(*result)); - result->path = g_strdup(mount->dirname); - - QAPI_LIST_PREPEND(response->paths, result); - - fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); - if (fd == -1) { - result->error = g_strdup_printf("failed to open: %s", - strerror(errno)); - result->has_error = true; - continue; - } - - /* We try to cull filesystems we know won't work in advance, but other - * filesystems may not implement fstrim for less obvious reasons. - * These will report EOPNOTSUPP; while in some other cases ENOTTY - * will be reported (e.g. CD-ROMs). - * Any other error means an unexpected error. - */ - r.start = 0; - r.len = -1; - r.minlen = has_minimum ? minimum : 0; - ret = ioctl(fd, FITRIM, &r); - if (ret == -1) { - result->has_error = true; - if (errno == ENOTTY || errno == EOPNOTSUPP) { - result->error = g_strdup("trim not supported"); - } else { - result->error = g_strdup_printf("failed to trim: %s", - strerror(errno)); - } - close(fd); - continue; - } - - result->has_minimum = true; - result->minimum = r.minlen; - result->has_trimmed = true; - result->trimmed = r.len; - close(fd); - } - - free_fs_mount_list(&mounts); - return response; -} -#endif /* CONFIG_FSTRIM */ - - -#define LINUX_SYS_STATE_FILE "/sys/power/state" -#define SUSPEND_SUPPORTED 0 -#define SUSPEND_NOT_SUPPORTED 1 - -typedef enum { - SUSPEND_MODE_DISK = 0, - SUSPEND_MODE_RAM = 1, - SUSPEND_MODE_HYBRID = 2, -} SuspendMode; - -/* - * Executes a command in a child process using g_spawn_sync, - * returning an int >= 0 representing the exit status of the - * process. - * - * If the program wasn't found in path, returns -1. - * - * If a problem happened when creating the child process, - * returns -1 and errp is set. - */ -static int run_process_child(const char *command[], Error **errp) -{ - int exit_status, spawn_flag; - GError *g_err = NULL; - bool success; - - spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | - G_SPAWN_STDERR_TO_DEV_NULL; - - success = g_spawn_sync(NULL, (char **)command, NULL, spawn_flag, - NULL, NULL, NULL, NULL, - &exit_status, &g_err); - - if (success) { - return WEXITSTATUS(exit_status); - } - - if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) { - error_setg(errp, "failed to create child process, error '%s'", - g_err->message); - } - - g_error_free(g_err); - return -1; -} - -static bool systemd_supports_mode(SuspendMode mode, Error **errp) -{ - const char *systemctl_args[3] = {"systemd-hibernate", "systemd-suspend", - "systemd-hybrid-sleep"}; - const char *cmd[4] = {"systemctl", "status", systemctl_args[mode], NULL}; - int status; - - status = run_process_child(cmd, errp); - - /* - * systemctl status uses LSB return codes so we can expect - * status > 0 and be ok. To assert if the guest has support - * for the selected suspend mode, status should be < 4. 4 is - * the code for unknown service status, the return value when - * the service does not exist. A common value is status = 3 - * (program is not running). - */ - if (status > 0 && status < 4) { - return true; - } - - return false; -} - -static void systemd_suspend(SuspendMode mode, Error **errp) -{ - Error *local_err = NULL; - const char *systemctl_args[3] = {"hibernate", "suspend", "hybrid-sleep"}; - const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL}; - int status; - - status = run_process_child(cmd, &local_err); - - if (status == 0) { - return; - } - - if ((status == -1) && !local_err) { - error_setg(errp, "the helper program 'systemctl %s' was not found", - systemctl_args[mode]); - return; - } - - if (local_err) { - error_propagate(errp, local_err); - } else { - error_setg(errp, "the helper program 'systemctl %s' returned an " - "unexpected exit status code (%d)", - systemctl_args[mode], status); - } -} - -static bool pmutils_supports_mode(SuspendMode mode, Error **errp) -{ - Error *local_err = NULL; - const char *pmutils_args[3] = {"--hibernate", "--suspend", - "--suspend-hybrid"}; - const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL}; - int status; - - status = run_process_child(cmd, &local_err); - - if (status == SUSPEND_SUPPORTED) { - return true; - } - - if ((status == -1) && !local_err) { - return false; - } - - if (local_err) { - error_propagate(errp, local_err); - } else { - error_setg(errp, - "the helper program '%s' returned an unexpected exit" - " status code (%d)", "pm-is-supported", status); - } - - return false; -} - -static void pmutils_suspend(SuspendMode mode, Error **errp) -{ - Error *local_err = NULL; - const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend", - "pm-suspend-hybrid"}; - const char *cmd[2] = {pmutils_binaries[mode], NULL}; - int status; - - status = run_process_child(cmd, &local_err); - - if (status == 0) { - return; - } - - if ((status == -1) && !local_err) { - error_setg(errp, "the helper program '%s' was not found", - pmutils_binaries[mode]); - return; - } - - if (local_err) { - error_propagate(errp, local_err); - } else { - error_setg(errp, - "the helper program '%s' returned an unexpected exit" - " status code (%d)", pmutils_binaries[mode], status); - } -} - -static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp) -{ - const char *sysfile_strs[3] = {"disk", "mem", NULL}; - const char *sysfile_str = sysfile_strs[mode]; - char buf[32]; /* hopefully big enough */ - int fd; - ssize_t ret; - - if (!sysfile_str) { - error_setg(errp, "unknown guest suspend mode"); - return false; - } - - fd = open(LINUX_SYS_STATE_FILE, O_RDONLY); - if (fd < 0) { - return false; - } - - ret = read(fd, buf, sizeof(buf) - 1); - close(fd); - if (ret <= 0) { - return false; - } - buf[ret] = '\0'; - - if (strstr(buf, sysfile_str)) { - return true; - } - return false; -} - -static void linux_sys_state_suspend(SuspendMode mode, Error **errp) -{ - Error *local_err = NULL; - const char *sysfile_strs[3] = {"disk", "mem", NULL}; - const char *sysfile_str = sysfile_strs[mode]; - pid_t pid; - int status; - - if (!sysfile_str) { - error_setg(errp, "unknown guest suspend mode"); - return; - } - - pid = fork(); - if (!pid) { - /* child */ - int fd; - - setsid(); - reopen_fd_to_null(0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - - fd = open(LINUX_SYS_STATE_FILE, O_WRONLY); - if (fd < 0) { - _exit(EXIT_FAILURE); - } - - if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) { - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - return; - } - - ga_wait_child(pid, &status, &local_err); - if (local_err) { - error_propagate(errp, local_err); - return; - } - - if (WEXITSTATUS(status)) { - error_setg(errp, "child process has failed to suspend"); - } - -} - -static void guest_suspend(SuspendMode mode, Error **errp) -{ - Error *local_err = NULL; - bool mode_supported = false; - - if (systemd_supports_mode(mode, &local_err)) { - mode_supported = true; - systemd_suspend(mode, &local_err); - } - - if (!local_err) { - return; - } - - error_free(local_err); - local_err = NULL; - - if (pmutils_supports_mode(mode, &local_err)) { - mode_supported = true; - pmutils_suspend(mode, &local_err); - } - - if (!local_err) { - return; - } - - error_free(local_err); - local_err = NULL; - - if (linux_sys_state_supports_mode(mode, &local_err)) { - mode_supported = true; - linux_sys_state_suspend(mode, &local_err); - } - - if (!mode_supported) { - error_free(local_err); - error_setg(errp, - "the requested suspend mode is not supported by the guest"); - } else { - error_propagate(errp, local_err); - } -} - -void qmp_guest_suspend_disk(Error **errp) -{ - guest_suspend(SUSPEND_MODE_DISK, errp); -} - -void qmp_guest_suspend_ram(Error **errp) -{ - guest_suspend(SUSPEND_MODE_RAM, errp); -} - -void qmp_guest_suspend_hybrid(Error **errp) -{ - guest_suspend(SUSPEND_MODE_HYBRID, errp); -} - -/* Transfer online/offline status between @vcpu and the guest system. - * - * On input either @errp or *@errp must be NULL. - * - * In system-to-@vcpu direction, the following @vcpu fields are accessed: - * - R: vcpu->logical_id - * - W: vcpu->online - * - W: vcpu->can_offline - * - * In @vcpu-to-system direction, the following @vcpu fields are accessed: - * - R: vcpu->logical_id - * - R: vcpu->online - * - * Written members remain unmodified on error. - */ -static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu, - char *dirpath, Error **errp) -{ - int fd; - int res; - int dirfd; - static const char fn[] = "online"; - - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); - if (dirfd == -1) { - error_setg_errno(errp, errno, "open(\"%s\")", dirpath); - return; - } - - fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR); - if (fd == -1) { - if (errno != ENOENT) { - error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn); - } else if (sys2vcpu) { - vcpu->online = true; - vcpu->can_offline = false; - } else if (!vcpu->online) { - error_setg(errp, "logical processor #%" PRId64 " can't be " - "offlined", vcpu->logical_id); - } /* otherwise pretend successful re-onlining */ - } else { - unsigned char status; - - res = pread(fd, &status, 1, 0); - if (res == -1) { - error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn); - } else if (res == 0) { - error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath, - fn); - } else if (sys2vcpu) { - vcpu->online = (status != '0'); - vcpu->can_offline = true; - } else if (vcpu->online != (status != '0')) { - status = '0' + vcpu->online; - if (pwrite(fd, &status, 1, 0) == -1) { - error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath, - fn); - } - } /* otherwise pretend successful re-(on|off)-lining */ - - res = close(fd); - g_assert(res == 0); - } - - res = close(dirfd); - g_assert(res == 0); -} - -GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) -{ - GuestLogicalProcessorList *head, **tail; - const char *cpu_dir = "/sys/devices/system/cpu"; - const gchar *line; - g_autoptr(GDir) cpu_gdir = NULL; - Error *local_err = NULL; - - head = NULL; - tail = &head; - cpu_gdir = g_dir_open(cpu_dir, 0, NULL); - - if (cpu_gdir == NULL) { - error_setg_errno(errp, errno, "failed to list entries: %s", cpu_dir); - return NULL; - } - - while (local_err == NULL && (line = g_dir_read_name(cpu_gdir)) != NULL) { - GuestLogicalProcessor *vcpu; - int64_t id; - if (sscanf(line, "cpu%" PRId64, &id)) { - g_autofree char *path = g_strdup_printf("/sys/devices/system/cpu/" - "cpu%" PRId64 "/", id); - vcpu = g_malloc0(sizeof *vcpu); - vcpu->logical_id = id; - vcpu->has_can_offline = true; /* lolspeak ftw */ - transfer_vcpu(vcpu, true, path, &local_err); - QAPI_LIST_APPEND(tail, vcpu); - } - } - - if (local_err == NULL) { - /* there's no guest with zero VCPUs */ - g_assert(head != NULL); - return head; - } - - qapi_free_GuestLogicalProcessorList(head); - error_propagate(errp, local_err); - return NULL; -} - -int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) -{ - int64_t processed; - Error *local_err = NULL; - - processed = 0; - while (vcpus != NULL) { - char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/", - vcpus->value->logical_id); - - transfer_vcpu(vcpus->value, false, path, &local_err); - g_free(path); - if (local_err != NULL) { - break; - } - ++processed; - vcpus = vcpus->next; - } - - if (local_err != NULL) { - if (processed == 0) { - error_propagate(errp, local_err); - } else { - error_free(local_err); - } - } - - return processed; -} - -void qmp_guest_set_user_password(const char *username, - const char *password, - bool crypted, - Error **errp) -{ - Error *local_err = NULL; - char *passwd_path = NULL; - pid_t pid; - int status; - int datafd[2] = { -1, -1 }; - char *rawpasswddata = NULL; - size_t rawpasswdlen; - char *chpasswddata = NULL; - size_t chpasswdlen; - - rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp); - if (!rawpasswddata) { - return; - } - rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1); - rawpasswddata[rawpasswdlen] = '\0'; - - if (strchr(rawpasswddata, '\n')) { - error_setg(errp, "forbidden characters in raw password"); - goto out; - } - - if (strchr(username, '\n') || - strchr(username, ':')) { - error_setg(errp, "forbidden characters in username"); - goto out; - } - - chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata); - chpasswdlen = strlen(chpasswddata); - - passwd_path = g_find_program_in_path("chpasswd"); - - if (!passwd_path) { - error_setg(errp, "cannot find 'passwd' program in PATH"); - goto out; - } - - if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) { - error_setg(errp, "cannot create pipe FDs"); - goto out; - } - - pid = fork(); - if (pid == 0) { - close(datafd[1]); - /* child */ - setsid(); - dup2(datafd[0], 0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - - if (crypted) { - execl(passwd_path, "chpasswd", "-e", NULL); - } else { - execl(passwd_path, "chpasswd", NULL); - } - _exit(EXIT_FAILURE); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - goto out; - } - close(datafd[0]); - datafd[0] = -1; - - if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) { - error_setg_errno(errp, errno, "cannot write new account password"); - goto out; - } - close(datafd[1]); - datafd[1] = -1; - - ga_wait_child(pid, &status, &local_err); - if (local_err) { - error_propagate(errp, local_err); - goto out; - } - - if (!WIFEXITED(status)) { - error_setg(errp, "child process has terminated abnormally"); - goto out; - } - - if (WEXITSTATUS(status)) { - error_setg(errp, "child process has failed to set user password"); - goto out; - } - -out: - g_free(chpasswddata); - g_free(rawpasswddata); - g_free(passwd_path); - if (datafd[0] != -1) { - close(datafd[0]); - } - if (datafd[1] != -1) { - close(datafd[1]); - } -} - -static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf, - int size, Error **errp) -{ - int fd; - int res; - - errno = 0; - fd = openat(dirfd, pathname, O_RDONLY); - if (fd == -1) { - error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname); - return; - } - - res = pread(fd, buf, size, 0); - if (res == -1) { - error_setg_errno(errp, errno, "pread sysfs file \"%s\"", pathname); - } else if (res == 0) { - error_setg(errp, "pread sysfs file \"%s\": unexpected EOF", pathname); - } - close(fd); -} - -static void ga_write_sysfs_file(int dirfd, const char *pathname, - const char *buf, int size, Error **errp) -{ - int fd; - - errno = 0; - fd = openat(dirfd, pathname, O_WRONLY); - if (fd == -1) { - error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname); - return; - } - - if (pwrite(fd, buf, size, 0) == -1) { - error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"", pathname); - } - - close(fd); -} - -/* Transfer online/offline status between @mem_blk and the guest system. - * - * On input either @errp or *@errp must be NULL. - * - * In system-to-@mem_blk direction, the following @mem_blk fields are accessed: - * - R: mem_blk->phys_index - * - W: mem_blk->online - * - W: mem_blk->can_offline - * - * In @mem_blk-to-system direction, the following @mem_blk fields are accessed: - * - R: mem_blk->phys_index - * - R: mem_blk->online - *- R: mem_blk->can_offline - * Written members remain unmodified on error. - */ -static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool sys2memblk, - GuestMemoryBlockResponse *result, - Error **errp) -{ - char *dirpath; - int dirfd; - char *status; - Error *local_err = NULL; - - if (!sys2memblk) { - DIR *dp; - - if (!result) { - error_setg(errp, "Internal error, 'result' should not be NULL"); - return; - } - errno = 0; - dp = opendir("/sys/devices/system/memory/"); - /* if there is no 'memory' directory in sysfs, - * we think this VM does not support online/offline memory block, - * any other solution? - */ - if (!dp) { - if (errno == ENOENT) { - result->response = - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED; - } - goto out1; - } - closedir(dp); - } - - dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/", - mem_blk->phys_index); - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); - if (dirfd == -1) { - if (sys2memblk) { - error_setg_errno(errp, errno, "open(\"%s\")", dirpath); - } else { - if (errno == ENOENT) { - result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND; - } else { - result->response = - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED; - } - } - g_free(dirpath); - goto out1; - } - g_free(dirpath); - - status = g_malloc0(10); - ga_read_sysfs_file(dirfd, "state", status, 10, &local_err); - if (local_err) { - /* treat with sysfs file that not exist in old kernel */ - if (errno == ENOENT) { - error_free(local_err); - if (sys2memblk) { - mem_blk->online = true; - mem_blk->can_offline = false; - } else if (!mem_blk->online) { - result->response = - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED; - } - } else { - if (sys2memblk) { - error_propagate(errp, local_err); - } else { - error_free(local_err); - result->response = - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED; - } - } - goto out2; - } - - if (sys2memblk) { - char removable = '0'; - - mem_blk->online = (strncmp(status, "online", 6) == 0); - - ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err); - if (local_err) { - /* if no 'removable' file, it doesn't support offline mem blk */ - if (errno == ENOENT) { - error_free(local_err); - mem_blk->can_offline = false; - } else { - error_propagate(errp, local_err); - } - } else { - mem_blk->can_offline = (removable != '0'); - } - } else { - if (mem_blk->online != (strncmp(status, "online", 6) == 0)) { - const char *new_state = mem_blk->online ? "online" : "offline"; - - ga_write_sysfs_file(dirfd, "state", new_state, strlen(new_state), - &local_err); - if (local_err) { - error_free(local_err); - result->response = - GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED; - goto out2; - } - - result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS; - result->has_error_code = false; - } /* otherwise pretend successful re-(on|off)-lining */ - } - g_free(status); - close(dirfd); - return; - -out2: - g_free(status); - close(dirfd); -out1: - if (!sys2memblk) { - result->has_error_code = true; - result->error_code = errno; - } -} - -GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp) -{ - GuestMemoryBlockList *head, **tail; - Error *local_err = NULL; - struct dirent *de; - DIR *dp; - - head = NULL; - tail = &head; - - dp = opendir("/sys/devices/system/memory/"); - if (!dp) { - /* it's ok if this happens to be a system that doesn't expose - * memory blocks via sysfs, but otherwise we should report - * an error - */ - if (errno != ENOENT) { - error_setg_errno(errp, errno, "Can't open directory" - "\"/sys/devices/system/memory/\""); - } - return NULL; - } - - /* Note: the phys_index of memory block may be discontinuous, - * this is because a memblk is the unit of the Sparse Memory design, which - * allows discontinuous memory ranges (ex. NUMA), so here we should - * traverse the memory block directory. - */ - while ((de = readdir(dp)) != NULL) { - GuestMemoryBlock *mem_blk; - - if ((strncmp(de->d_name, "memory", 6) != 0) || - !(de->d_type & DT_DIR)) { - continue; - } - - mem_blk = g_malloc0(sizeof *mem_blk); - /* The d_name is "memoryXXX", phys_index is block id, same as XXX */ - mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10); - mem_blk->has_can_offline = true; /* lolspeak ftw */ - transfer_memory_block(mem_blk, true, NULL, &local_err); - if (local_err) { - break; - } - - QAPI_LIST_APPEND(tail, mem_blk); - } - - closedir(dp); - if (local_err == NULL) { - /* there's no guest with zero memory blocks */ - if (head == NULL) { - error_setg(errp, "guest reported zero memory blocks!"); - } - return head; - } - - qapi_free_GuestMemoryBlockList(head); - error_propagate(errp, local_err); - return NULL; -} - -GuestMemoryBlockResponseList * -qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp) -{ - GuestMemoryBlockResponseList *head, **tail; - Error *local_err = NULL; - - head = NULL; - tail = &head; - - while (mem_blks != NULL) { - GuestMemoryBlockResponse *result; - GuestMemoryBlock *current_mem_blk = mem_blks->value; - - result = g_malloc0(sizeof(*result)); - result->phys_index = current_mem_blk->phys_index; - transfer_memory_block(current_mem_blk, false, result, &local_err); - if (local_err) { /* should never happen */ - goto err; - } - - QAPI_LIST_APPEND(tail, result); - mem_blks = mem_blks->next; - } - - return head; -err: - qapi_free_GuestMemoryBlockResponseList(head); - error_propagate(errp, local_err); - return NULL; -} - -GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp) -{ - Error *local_err = NULL; - char *dirpath; - int dirfd; - char *buf; - GuestMemoryBlockInfo *info; - - dirpath = g_strdup_printf("/sys/devices/system/memory/"); - dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); - if (dirfd == -1) { - error_setg_errno(errp, errno, "open(\"%s\")", dirpath); - g_free(dirpath); - return NULL; - } - g_free(dirpath); - - buf = g_malloc0(20); - ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err); - close(dirfd); - if (local_err) { - g_free(buf); - error_propagate(errp, local_err); - return NULL; - } - - info = g_new0(GuestMemoryBlockInfo, 1); - info->size = strtol(buf, NULL, 16); /* the unit is bytes */ - - g_free(buf); - - return info; -} - -#define MAX_NAME_LEN 128 -static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp) -{ -#ifdef CONFIG_LINUX - GuestDiskStatsInfoList *head = NULL, **tail = &head; - const char *diskstats = "/proc/diskstats"; - FILE *fp; - size_t n; - char *line = NULL; - - fp = fopen(diskstats, "r"); - if (fp == NULL) { - error_setg_errno(errp, errno, "open(\"%s\")", diskstats); - return NULL; - } - - while (getline(&line, &n, fp) != -1) { - g_autofree GuestDiskStatsInfo *diskstatinfo = NULL; - g_autofree GuestDiskStats *diskstat = NULL; - char dev_name[MAX_NAME_LEN]; - unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks, fl_ticks; - unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios; - unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec; - unsigned long dc_ios, dc_merges, dc_sec, fl_ios; - unsigned int major, minor; - int i; - - i = sscanf(line, "%u %u %s %lu %lu %lu" - "%lu %lu %lu %lu %u %u %u %u" - "%lu %lu %lu %u %lu %u", - &major, &minor, dev_name, - &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios, - &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec, - &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks, - &dc_ios, &dc_merges, &dc_sec, &dc_ticks, - &fl_ios, &fl_ticks); - - if (i < 7) { - continue; - } - - diskstatinfo = g_new0(GuestDiskStatsInfo, 1); - diskstatinfo->name = g_strdup(dev_name); - diskstatinfo->major = major; - diskstatinfo->minor = minor; - - diskstat = g_new0(GuestDiskStats, 1); - if (i == 7) { - diskstat->has_read_ios = true; - diskstat->read_ios = rd_ios; - diskstat->has_read_sectors = true; - diskstat->read_sectors = rd_merges_or_rd_sec; - diskstat->has_write_ios = true; - diskstat->write_ios = rd_sec_or_wr_ios; - diskstat->has_write_sectors = true; - diskstat->write_sectors = rd_ticks_or_wr_sec; - } - if (i >= 14) { - diskstat->has_read_ios = true; - diskstat->read_ios = rd_ios; - diskstat->has_read_sectors = true; - diskstat->read_sectors = rd_sec_or_wr_ios; - diskstat->has_read_merges = true; - diskstat->read_merges = rd_merges_or_rd_sec; - diskstat->has_read_ticks = true; - diskstat->read_ticks = rd_ticks_or_wr_sec; - diskstat->has_write_ios = true; - diskstat->write_ios = wr_ios; - diskstat->has_write_sectors = true; - diskstat->write_sectors = wr_sec; - diskstat->has_write_merges = true; - diskstat->write_merges = wr_merges; - diskstat->has_write_ticks = true; - diskstat->write_ticks = wr_ticks; - diskstat->has_ios_pgr = true; - diskstat->ios_pgr = ios_pgr; - diskstat->has_total_ticks = true; - diskstat->total_ticks = tot_ticks; - diskstat->has_weight_ticks = true; - diskstat->weight_ticks = rq_ticks; - } - if (i >= 18) { - diskstat->has_discard_ios = true; - diskstat->discard_ios = dc_ios; - diskstat->has_discard_merges = true; - diskstat->discard_merges = dc_merges; - diskstat->has_discard_sectors = true; - diskstat->discard_sectors = dc_sec; - diskstat->has_discard_ticks = true; - diskstat->discard_ticks = dc_ticks; - } - if (i >= 20) { - diskstat->has_flush_ios = true; - diskstat->flush_ios = fl_ios; - diskstat->has_flush_ticks = true; - diskstat->flush_ticks = fl_ticks; - } - - diskstatinfo->stats = g_steal_pointer(&diskstat); - QAPI_LIST_APPEND(tail, diskstatinfo); - diskstatinfo = NULL; - } - free(line); - fclose(fp); - return head; -#else - g_debug("disk stats reporting available only for Linux"); - return NULL; -#endif -} - -GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp) -{ - return guest_get_diskstats(errp); -} - -GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp) -{ - GuestCpuStatsList *head = NULL, **tail = &head; - const char *cpustats = "/proc/stat"; - int clk_tck = sysconf(_SC_CLK_TCK); - FILE *fp; - size_t n; - char *line = NULL; - - fp = fopen(cpustats, "r"); - if (fp == NULL) { - error_setg_errno(errp, errno, "open(\"%s\")", cpustats); - return NULL; - } - - while (getline(&line, &n, fp) != -1) { - GuestCpuStats *cpustat = NULL; - GuestLinuxCpuStats *linuxcpustat; - int i; - unsigned long user, system, idle, iowait, irq, softirq, steal, guest; - unsigned long nice, guest_nice; - char name[64]; - - i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", - name, &user, &nice, &system, &idle, &iowait, &irq, &softirq, - &steal, &guest, &guest_nice); - - /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */ - if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] == '\0')) { - continue; - } - - if (i < 5) { - slog("Parsing cpu stat from %s failed, see \"man proc\"", cpustats); - break; - } - - cpustat = g_new0(GuestCpuStats, 1); - cpustat->type = GUEST_CPU_STATS_TYPE_LINUX; - - linuxcpustat = &cpustat->u.q_linux; - linuxcpustat->cpu = atoi(&name[3]); - linuxcpustat->user = user * 1000 / clk_tck; - linuxcpustat->nice = nice * 1000 / clk_tck; - linuxcpustat->system = system * 1000 / clk_tck; - linuxcpustat->idle = idle * 1000 / clk_tck; - - if (i > 5) { - linuxcpustat->has_iowait = true; - linuxcpustat->iowait = iowait * 1000 / clk_tck; - } - - if (i > 6) { - linuxcpustat->has_irq = true; - linuxcpustat->irq = irq * 1000 / clk_tck; - linuxcpustat->has_softirq = true; - linuxcpustat->softirq = softirq * 1000 / clk_tck; - } - - if (i > 8) { - linuxcpustat->has_steal = true; - linuxcpustat->steal = steal * 1000 / clk_tck; - } - - if (i > 9) { - linuxcpustat->has_guest = true; - linuxcpustat->guest = guest * 1000 / clk_tck; - } - - if (i > 10) { - linuxcpustat->has_guest = true; - linuxcpustat->guest = guest * 1000 / clk_tck; - linuxcpustat->has_guestnice = true; - linuxcpustat->guestnice = guest_nice * 1000 / clk_tck; - } - - QAPI_LIST_APPEND(tail, cpustat); - } - - free(line); - fclose(fp); - return head; -} - -#else /* defined(__linux__) */ +#if !defined(__linux__) void qmp_guest_suspend_disk(Error **errp) { diff --git a/qga/meson.build b/qga/meson.build index 65c1e93846..409f49a000 100644 --- a/qga/meson.build +++ b/qga/meson.build @@ -72,6 +72,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files( 'commands-posix.c', 'commands-posix-ssh.c', )) +qga_ss.add(when: 'CONFIG_LINUX', if_true: files( + 'commands-linux.c', +)) qga_ss.add(when: 'CONFIG_WIN32', if_true: files( 'channel-win32.c', 'commands-win32.c', From patchwork Thu Sep 22 13:19:58 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Ivanov X-Patchwork-Id: 12985261 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 26164C6FA82 for ; Thu, 22 Sep 2022 14:29:48 +0000 (UTC) Received: from localhost ([::1]:60568 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1obND1-00051h-4f for qemu-devel@archiver.kernel.org; Thu, 22 Sep 2022 10:29:47 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:59920) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obMCi-0003pJ-Ok for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:25:27 -0400 Received: from mail-db3eur04on0703.outbound.protection.outlook.com ([2a01:111:f400:fe0c::703]:39940 helo=EUR04-DB3-obe.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obMCg-00082M-FJ for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:25:24 -0400 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=ORrYfGohE1TTYfSv88zkOxHCk8q/w/KR9fa3lVamZohp5Bprq0acTDCKMpqN3faCPWhk+jLNbBls46xQHOcpmE4bQ79q8GRZ3Ohl28PECt2V1l13eQOCV5UCF4qPGJM0LWbmRjXl6bcgRgkC9qQP+ZRXmNYM/dNSlpvBtygp1ruNdiEqTbgIrgiCEZ2IRIjX2CS3Dv/opBjY4MgVHumSyyTh3nDham7wcmS5NSJ/iulvoH2Rj1A7AurQ8E99dQyU03PmSmYvsSXQjTrVRabsiSvMNM94ZSGGH8R/NjAUgyRHm3unO/HRIH1Ks2iOd/xyTFi+5IFQNYA2UvRDpu4Dwg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=eFrv1ufMIEuTjrrp9MSpxURObbKKCc+IpHyH3IZgoEc=; b=YfDKzriKk6Cl3T2qNTq8oCsu+b6LF4xFfTzKYD48vAfvtGM6Px859X3d8OvWOuitv54Wm3dUIGVNImymQIQIySLY9yP1z7qWx1y+LMqUMHeTPI+Qti2JpgefceQsLstloBUaNAgMXxCSJuF1J2bSTXDBsE87tEVdlHPqReRdyxXqRZZjk1SxNa+63LxHiViygqLUqcTRTbIomDCrnXylTeThPIpCEMYUg4imqUe2hlHP+q1HZPY7Av4tvciv1c8q5o/7lOfpzzYHIQJPm8BNh4mEYUn7GPypI5mTHeWM+n1+4zhL5Qowmx0Kei9lhRoFx5UCphgg2+G7r6XlCd/x4g== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=virtuozzo.com; dmarc=pass action=none header.from=virtuozzo.com; dkim=pass header.d=virtuozzo.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=virtuozzo.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=eFrv1ufMIEuTjrrp9MSpxURObbKKCc+IpHyH3IZgoEc=; b=TyNVFUIRTv4Hs94k6IhEyJM2GSx3t5S58XfU+2JukmXtJLgbYIRpPmlWqNFYc0+vVkO3TpU0aKvCpb0+10GsujxDa43FssqVtYdS2AkhSHZ0d44YfKeioRXGyiEdiZTlkHnXZVuiM9r+dXmSTgp+IqPrpYMEyjgsL/+LY//rYB1xdcjIofSj8lTbLcu7UY6kLI31M/RnwegsGQTvxmYcQ8mO8/YNP6Jiuq/welutpOuGCSIHOrWFMJxklWEW2DGWNofj6aQFP4f9JXnuv/0iOsMeODje/akPkv59npfVpcKpHMR8NM4i0Prvjiy3ufDwxpGcHiQjehwTOpj1Jwt5eg== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=virtuozzo.com; Received: from AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) by PAXPR08MB6480.eurprd08.prod.outlook.com (2603:10a6:102:155::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5654.16; Thu, 22 Sep 2022 13:20:13 +0000 Received: from AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b]) by AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b%8]) with mapi id 15.20.5654.016; Thu, 22 Sep 2022 13:20:13 +0000 From: Alexander Ivanov To: qemu-devel@nongnu.org Cc: den@virtuozzo.com, michael.roth@amd.com, kkostiuk@redhat.com Subject: [PATCH 2/5] qga: Add initial FreeBSD support Date: Thu, 22 Sep 2022 15:19:58 +0200 Message-Id: <20220922132001.940334-3-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> References: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> X-ClientProxiedBy: FR0P281CA0083.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:1e::18) To AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: AS8PR08MB7095:EE_|PAXPR08MB6480:EE_ X-MS-Office365-Filtering-Correlation-Id: edd79205-a00b-4666-4ae2-08da9c9d2e72 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: DGLHHiBuAJSUEM6adHfNOsb84RP+TlrHJ35Kju/PkJRKVVGKyt1KXRXfh2MA7yJldU5q+4McYRb2erdUBnl1iw3snEwrr8t1MMSIOD5uzKznRcCu59hExvWW2encr/xaLyDPLDFBUdyt2ADsODwGjxFs38akOmWr6CQCGG6UixpfsZLq7zlcGUPq5h4KFnQa9qAD+Dewxm0D4EkKTprTAF2mw1H1JDmgizCd+kSFr1vfjhMJsgHT4+eIBunpkzQwi96mAt0v8l0PC4vhyht4FKvJGss1/c4kVFBRz7giA0bdRJDoqkIR/Uc7D7R387xG0tDvCVXiLqJ0zIXGXMP2Drfw/I7Our/xNbc5TbxJcQv+gnVhi8438GmGZAne9LryhlVfNR6GJihn2FB0msWjzyDE8epqKrmly43VVALnN/Y83QyeH/QXhNbMqTMusqLjKG3je1mWid0FHH6ZCrIQ0XWRbaqrSdgeef57zC2JTUz/EUtAVUxMsR8YTwR2KRtr8AEXopdmeYd+oZIzs+Ft9x+578s1gkHpq+zi6u0Ze2bDMcv/w5EvYf8tXBqt22o0td+UPX33+7m+nYYCn9JsOR+zwAMYS/p+96qu5SAs5irvyUoitUD5qNo0AfTmLba4H66Sg+kr5Ws2APwLTKDv8klqm+JR6kKmFC8svQUiz/HdqQ8eX5vLr4vi1ZC0iJT3o0q2aDOSFrRsTJEGpX2r5lPBCYfutmYaIMxrUwYmKJQtGvpkZ7an3Jjf4q540na4 X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:AS8PR08MB7095.eurprd08.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230022)(4636009)(346002)(366004)(376002)(136003)(396003)(39850400004)(451199015)(41300700001)(6486002)(478600001)(6666004)(52116002)(36756003)(66476007)(66556008)(316002)(186003)(66946007)(83380400001)(1076003)(5660300002)(2616005)(8936002)(26005)(6506007)(6916009)(86362001)(8676002)(44832011)(4326008)(2906002)(6512007)(38350700002)(38100700002); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: +8bExo5Pkr8dJVisy6Aq0zYKLs7bW7ucu1M5zdvBkSFwCkFx9k8zHdGiD+jXJ4sL5/6I4ZZAzpdAPACrQtlSCB8vzJ7V2fT4gjBjBydyM7xIjpO0ZjxectoPCvfV3KPvqaPPADdvj3eAcO+Kv/x3A20pY4FXO1E7CVNja9kzEMpPCUxuhVV8mUW+s4LyN5J6D+yBz6L8xX6qoJTAaL0ScTunBr4iWJ7OsS5TDK/dzu1IZHbNHcZUySk4iJv4uJi2v897WrtX606HSZo0sq8CuPSaGvk8hA+WJPSk2awTeaMI+YHGr+aYLDfT2oSySF1PdPwIXsZc/afkbZvnf6IBnJkPyeE12N4OGvxBl0DBDmfFqdb24+0YLHXlwbPcA4PW8qorJ1WP5yMhBNEKbaj/kIBGJP1TaV1pwt+CUPwLHRilVCMHzxOylT8RINNPVBMpXULYRBWfuSvFhMHj3vw7DIUWF5gnrq+O5M6lDxjbNVGVcijk1dybPXMUDGJtHdLLXSOXU4pdtsdBipnNy6zswJSIqoBVrT2krQ2IivlecPq6Ru3U4TkuNmlaDTN5e1qBMN7RuGQQP7WRGXnogGQZFSLV6BDMbg4Vg96S/dPJpLaqlNFdYODb6V96noTt5jzlaXtFz4Se8wOhffbkdnVqv9M8A9HEp6gug2W+L+tyWWB9MV55c55CdJqhqg10+hh6y/bOXDFqrFPCuQgcweyHmICF7odapz2VDMLN7l7QvCd1Pitp39DL5EalXau7Hsa8qDsmLlJbjfAMwN2LqjcC8P0NCuc29Dbh17udwzBxJdXpRfIGSd+VdvYIQzyvZcNFlSW2p1YtOQCBXCfGtReBiRjKPtyA1geAGAdwgF8AZj6yVqRhI3b0naV72HVQMmJjC18Hb/MSSvnJ9OqPSSwR6UIKg/WD42tEu3jHoy4m/v1PkYzzEFgYnpjonbcwTe10EyN0yoDL3ljqL759ob5kHPreaStIfiUWZBlYhkKR1Ezcyzun1A+Ul5iv/Ora1YceuAyOd9oG9gHlLfckB6xUpjvp/pDlx48DZwXw9SdUZHgDj0iJ7Gw4E6kewhIvLSPrwUDTsmjgxvFybT21piMAHz7V5WPvTPzyEVQdnaFLqoYqureDNZzdmHCoPluken8wNkiYFiwowXPzbqYKU1TNMPAmEaxLV0D2nEUhfzDMIkv1ZOGZ7h0H8HPVcKBqzYpNV/eMB3/YwAugjS7rtnTrjyU2GAg1npBU/CN7W834jBEb637xne6K6Jw4/h4CjlDIfc8MqPJKjPLGjo8c7l8ANGvvmZ2bK6iCe9/9efNcNliIyJXR1l6S2lCchuX8iM4p0ZPX1bypAvVA148QrNJaheEi+gEaooEIbx6A9oSB+J+Fzuo6StEYnyPQ6jGD5jnx58+Wf1D76Zb7zBwxnNpR+Hcb3+WX+uM17eKatSskyU9O4YTyevt4rGJ7ayk3HXpgarRN3a5YGM/+fGQx4vquSklrZ9I3BD4+X5lQrvxx8SNEWN4ONSTFoi+qsFkaXl34ZiiDcsrqsOITN+UCRdmKyiZhSlsXWrsw+HOwJ43HHr6jX4y8S668MVXkIB5gN8kDVub80pWQO4fBCbYTu4SbfA== X-OriginatorOrg: virtuozzo.com X-MS-Exchange-CrossTenant-Network-Message-Id: edd79205-a00b-4666-4ae2-08da9c9d2e72 X-MS-Exchange-CrossTenant-AuthSource: AS8PR08MB7095.eurprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Sep 2022 13:20:13.0637 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 0bc7f26d-0264-416e-a6fc-8352af79c58f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: J/IRDEq/sSnhKLfCCf21BOO/thSA4lYJlwwlqs6xdexuTJz1uwyxI5TTjsxlrlgv84zPCGvDYky91P/BFVG2sjPl4kaHncJ7iV01+7eeys4= X-MS-Exchange-Transport-CrossTenantHeadersStamped: PAXPR08MB6480 Received-SPF: pass client-ip=2a01:111:f400:fe0c::703; envelope-from=alexander.ivanov@virtuozzo.com; helo=EUR04-DB3-obe.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Add commands-bsd.c file with dumb functions, fix device path and make the code buildable in FreeBSD. Signed-off-by: Alexander Ivanov --- meson.build | 2 +- qga/commands-bsd.c | 121 +++++++++++++++++++++++++++++++++++++++++++ qga/commands-posix.c | 6 ++- qga/main.c | 11 +++- qga/meson.build | 3 ++ 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 qga/commands-bsd.c diff --git a/meson.build b/meson.build index c2adb7caf4..574cc1e91e 100644 --- a/meson.build +++ b/meson.build @@ -75,7 +75,7 @@ have_tools = get_option('tools') \ .allowed() have_ga = get_option('guest_agent') \ .disable_auto_if(not have_system and not have_tools) \ - .require(targetos in ['sunos', 'linux', 'windows'], + .require(targetos in ['sunos', 'linux', 'windows', 'freebsd'], error_message: 'unsupported OS for QEMU guest agent') \ .allowed() have_block = have_system or have_tools diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c new file mode 100644 index 0000000000..c1e3ed13e9 --- /dev/null +++ b/qga/commands-bsd.c @@ -0,0 +1,121 @@ +/* + * QEMU Guest Agent BSD-specific command implementations + * + * Copyright (c) Virtuozzo International GmbH. + * + * Authors: + * Alexander Ivanov + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qga-qapi-commands.h" +#include "qapi/qmp/qerror.h" +#include "qapi/error.h" +#include "qemu/queue.h" +#include "commands-common.h" + +#if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) +bool build_fs_mount_list(FsMountList *mounts, Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return false; +} +#endif + +#if defined(CONFIG_FSFREEZE) +int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints, + strList *mountpoints, + FsMountList mounts, + Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return 0; +} + +int qmp_guest_fsfreeze_do_thaw(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return 0; +} +#endif + +GuestDiskInfoList *qmp_guest_get_disks(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +void qmp_guest_suspend_disk(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); +} + +void qmp_guest_suspend_ram(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); +} + +void qmp_guest_suspend_hybrid(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); +} + +GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return -1; +} + +void qmp_guest_set_user_password(const char *username, + const char *password, + bool crypted, + Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); +} + +GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +GuestMemoryBlockResponseList * +qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 0bb8b9e2f3..3a1055d5c3 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -31,6 +31,10 @@ #include #endif +#ifdef __FreeBSD__ +#undef HAVE_GETIFADDRS +#endif + #ifdef HAVE_GETIFADDRS #include #include @@ -763,7 +767,7 @@ void qmp_guest_file_flush(int64_t handle, Error **errp) } } -#if !defined(__linux__) +#if !(defined(__linux__) || defined(__FreeBSD__)) void qmp_guest_suspend_disk(Error **errp) { diff --git a/qga/main.c b/qga/main.c index 5f1efa2333..22b3c0df11 100644 --- a/qga/main.c +++ b/qga/main.c @@ -45,9 +45,14 @@ #endif #ifndef _WIN32 +#ifdef __FreeBSD__ +#define QGA_VIRTIO_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0" +#define QGA_SERIAL_PATH_DEFAULT "/dev/vtcon/org.qemu.guest_agent.0" +#else /* __FreeBSD__ */ #define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0" -#define QGA_STATE_RELATIVE_DIR "run" #define QGA_SERIAL_PATH_DEFAULT "/dev/ttyS0" +#endif /* __FreeBSD__ */ +#define QGA_STATE_RELATIVE_DIR "run" #else #define QGA_VIRTIO_PATH_DEFAULT "\\\\.\\Global\\org.qemu.guest_agent.0" #define QGA_STATE_RELATIVE_DIR "qemu-ga" @@ -1475,7 +1480,11 @@ int main(int argc, char **argv) } if (config->method == NULL) { +#ifdef CONFIG_BSD + config->method = g_strdup("isa-serial"); +#else config->method = g_strdup("virtio-serial"); +#endif } socket_activation = check_socket_activation(); diff --git a/qga/meson.build b/qga/meson.build index 409f49a000..456ba4c29f 100644 --- a/qga/meson.build +++ b/qga/meson.build @@ -75,6 +75,9 @@ qga_ss.add(when: 'CONFIG_POSIX', if_true: files( qga_ss.add(when: 'CONFIG_LINUX', if_true: files( 'commands-linux.c', )) +qga_ss.add(when: 'CONFIG_BSD', if_true: files( + 'commands-bsd.c', +)) qga_ss.add(when: 'CONFIG_WIN32', if_true: files( 'channel-win32.c', 'commands-win32.c', From patchwork Thu Sep 22 13:19:59 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Ivanov X-Patchwork-Id: 12985227 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id AA1DBC54EE9 for ; Thu, 22 Sep 2022 14:19:17 +0000 (UTC) Received: from localhost ([::1]:57968 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1obN2q-0000yD-H9 for qemu-devel@archiver.kernel.org; Thu, 22 Sep 2022 10:19:16 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:51624) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8O-0001WE-3L for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:21:06 -0400 Received: from mail-eopbgr60136.outbound.protection.outlook.com ([40.107.6.136]:7326 helo=EUR04-DB3-obe.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8K-0004pt-Fz for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:20:54 -0400 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=dCVllDXvaxpU0HbAUSfJpJw9DV0surNFT6cREElYw3y6ri8LmjWCinzC+STdwTRRNfm5MkceC13hZbCXdHHqIVMXjRD0O77ZhAI/8/TSyEJaolncclEwIdyCXzvAJ2QRCqa58+wu5M7K1h0fHlam3BIQO/qQtc1SuN0FQkigofdcIqeE7950IGvzCFV+et2EtStxoWEmlx++B6kPxvIZvIRQvsAkWU++GAXytmInuz35OKIsUEYDErev5UU9IWcotBhegTdVDKKw71Ijm2Q6gDmiwf9EjISW1TU8ZD5ocnzBkF0Or9bv/PY15pgyQWDCHc5e9xBPzn3EsVePSsVdTg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=KnWNATV3S86omQsc/eiTIbd24tH6lGlT3gh9+O1nfU0=; b=goh+2g/FxAixKvxaStwOySFxLFZ0NSmJyV2rbs879QrP+t30IAHqsasK8L+AnSAQoFu3W3UZWzd8iPMcF01Kd6TzHD5hVlc8NtRdg880SKl55G0V/zs84JVghx8nkB/1jqf1gnzp118qLK+EZExpKVZBwpsJhRc4XqW1Z6RlmMwaOKn1Ts26zL4tLPnWEcbON2tf0qUy3QJmTj//2tCCCqtlX0lbVHrvlrWhEvyZVKYG5RZA3CeQ5MRGDO2BswBytpMZpl1I2Y/RNPF8ap/A3upYky3eZuXT/mvCwwNGcaAnw7GBLCVfq0cEgt3Z6VQHgcAhK3HK3Z1C+G9HHEXuRQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=virtuozzo.com; dmarc=pass action=none header.from=virtuozzo.com; dkim=pass header.d=virtuozzo.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=virtuozzo.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=KnWNATV3S86omQsc/eiTIbd24tH6lGlT3gh9+O1nfU0=; b=yZ1Xd16D/Pn2vIUNL+WwVzMgQnsKZ8YaLox5cY+RYy1PvMMq8hzv+Lmtv7DgrulSROqwrVoLJ5xnq69ePsZbePmK0Zs2QAZDaQ1QujavK8zxv84LD19g4TuEe1BxJaYs2Pjmm/SwOoKQLfxKhphCZMCXAwUkGe4PxCAav28PXcf897xqqHk3vfh6QFWJGdMyxsSiZ10drZ7SUS1J+TohfDiMn27V5I1DLqio0UttlfWgKLHjqAufDirrDH1j1ZzLDfIlKsyqi2t6Y/yYGbyyz8LNRrG54wVVQ5REz9rNyesx8AEEKEi8GchDIQ+iyzGZm47926UbT0TFhKSoj0CXVQ== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=virtuozzo.com; Received: from AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) by PAXPR08MB6480.eurprd08.prod.outlook.com (2603:10a6:102:155::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5654.16; Thu, 22 Sep 2022 13:20:14 +0000 Received: from AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b]) by AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b%8]) with mapi id 15.20.5654.016; Thu, 22 Sep 2022 13:20:14 +0000 From: Alexander Ivanov To: qemu-devel@nongnu.org Cc: den@virtuozzo.com, michael.roth@amd.com, kkostiuk@redhat.com Subject: [PATCH 3/5] qga: Add UFS freeze/thaw support for FreeBSD Date: Thu, 22 Sep 2022 15:19:59 +0200 Message-Id: <20220922132001.940334-4-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> References: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> X-ClientProxiedBy: FR0P281CA0083.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:1e::18) To AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: AS8PR08MB7095:EE_|PAXPR08MB6480:EE_ X-MS-Office365-Filtering-Correlation-Id: cdbb2e8b-0181-4085-1906-08da9c9d2ee7 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: N4BJ/Fku7bd6XLfLRA9/AEOETjN9DREI8IVPDsxBJbJo1kYDGEhjxs4OI+zryd7k+GAMYYgf2G0pLN/A1iy/DCAAAy1MQGTLoehgZxRwNcPfOtMxi/W1gsW/ZFr0P2tJzRs+bkDtGdxQtNNa0GlA2PxyHDnDiSZUjAe8WXmLil2W1B8T3MKX+cZE3r8SXmufca8sjPT8ZYVivyzgybOlCDs+YGURonkcYyatf/E1zCmAWk4p/Yb2mtANGqQ56OTwat8s+I6GhXVyDCP9fk+3Fk2D0t3eUH03y49lBpQ+ZeKZ/CVzO1NaQ9lxA+srBcnGSHFGBRdT3RsBjoMhMkVtEyBFGjOZz0ElMzsDnp2oaV26DcwsfCbdGHssWwM6uI2nFRmc78FCnsdZsQSlfZWT9yfRr7/8ro/6bLmdd6Xi2EnWllvmtwidlq2cLSit+eMxvhTljRR5N2JDz7qyKEq3TnebsclGk9wQJcNdtS6cFBq7H99Gy3eMR9pdWeDeVbrCk0+rHR2ZBKzs9lFOSml3grW5n1ecVjV4Mfi1p+gbw3c1if9hZBZRFrxJOlNU8rMtzIL53f7WOkADjCcEBwD+4rQEoGWlL8QeyV2VhETIiTNvWLr3ziviKISbA8yAOXsbX8rA6TKCzUR11ilVR7K7XebM8EaTbqFnTGo759pqUKlHtw4QWnYCaOhh+BnpYZ7p/Zj4PzPOauTjRBc6zbJ16W17ePB3qGC1EhI8UwQ7d66KjXv+p10oKA4S0HbwWWCzZ+1boC08Dzxmd58A6cEcAw== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:AS8PR08MB7095.eurprd08.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230022)(4636009)(346002)(366004)(376002)(136003)(396003)(39850400004)(451199015)(41300700001)(6486002)(478600001)(6666004)(52116002)(36756003)(66476007)(66556008)(316002)(186003)(66946007)(83380400001)(1076003)(5660300002)(2616005)(8936002)(26005)(6506007)(6916009)(86362001)(8676002)(44832011)(4326008)(2906002)(6512007)(38350700002)(38100700002); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: sYVNB6nGz3MJaJJ7rtAeQ9hNC1xelifFRaQ4ocTSaMvHGKsoCFldVw0Brfp1F21ZTF2PMyNIfbbGBCe7gJjw0sp4XTeAP2HLxi4q9jS3g41qW5BHr7WPPL+ZfBfE06LQeoT9F4FOrE1fzomsLUwiRB+OsCHCPYCFoq6bU2aZ8L3cmo5C2Hvbt/JnStGNVb4GtBRxYXXjIknVkSwBMx6WI7Iw1wEdz47H59x2FxofJaUL0GXjOnzDwik/NAPAypn1PNAjkWUUxqhzUvCtpGJhkd3wjXsD7RCZ91WnrNpEErgKQd8vfvARjAWWnvSbHextcuQzVTdcRCBOfN1Bo/2ukF9775YdBy+cLH3GfWaSoQRlM11wcVxVT+2iabLe/h7v4Y2a2gg5gx9uWQSuT4eLs+z/fhVuwMibAFZ2S+cfab7rp4EYbMP12qsm13GVrkb9BVLPy40+p1RlTJc4YJp+kmhAWZnqF9Rr2nugtemorDkwGm+LfvB/lC3JP0P8UIJnmPK0h1Ax6wybnfhpQ7dlnutKd2yJWlSeJ3luKi2zo7OOW0Ve04iEQove/fNk0ZAQvuuhnWvbvn18cFKgro6nlDAR8FqlnCs1sYpjNOiD/Mh7nQ47JZykq7sTCkMhnpCLAh//RLa/5i8vBmeGOl69CTrX8BlWZI7LEx7eUnLw8AmrAjiekas4zBpf2Ppb9R7sJIEAX155bf/XkwkutJ+Lm/V8jHDKTFFHOM7pTx2c4gPgmNyycVAGvZfsFzVndxMQlNqXhStItPsNaoYTKK7ptJaDQv97YpO1e9V6BKxzkuut6ey27cXBMpVBQF+wG2hbSlcHTOHEvABuQiZ2C7JBRx9g5yMYJv7fD92aUfTDouHSpYEJwIqJulYSlEJ/USmfT0hUchww0GPMv3S7FB1e13k4ai3+h5xDKV4A9qMcE7dJitC0WY61IT7bGDtG6H9P4N2A4ArY0/bGqVaUTR0UUDjM02YSHdzvmh5MdWeQ5BUL1tQPUmUiNVPqLMWodOikYlKT/0V2arV68KjWNsPaHkQ9cBhjSOhH5A6CPcdiAIdeUmAduQ0NwT+T8g4R3uIccz0UPToIaMU0UzS5njHwl17nTc0UYNN3Txwbgrbbu71XikhKJHqQwxZvu5SICtysMMotS79rHc1ODTR30N3Fuyh7LnrysngHBiMmThEgW24ygxqpQa4pITcw0s2u12fIp0CzVAurDVuACd3tBbh5rSyyWVaE6zYVQfc9/ujZr10Xl27R98pWKCNDivsYPnm5YQV5H1FW8Xh9mun8WT/yie3wYT1NClsDu4Wipf7MGEsYEPGOMKnNMrCne3Ia6nH6ou7YP6nejYUKrH9NRXFCv0OLKvoE95jBW84WQQSHr8XqA24gex8hs94115+RYuX/Vpa0afO1xfGiv2oc5e8zPoX/zvZYlIAxibu396F92Y4Hifm0GYwqAQ6V4AfEdRlpbekKJnZnFr/dUfUTYkRR76HJmdR8Z3lF2990/m33DRMrysN5maA8q6ks2IyjbvJlD2z39dxXrtjzyvDXH+Kjd9y5Zg5FrM6Z9Qc7Es7OTjadvLq10aAz7PIu8RiDXwTI7hLCu8QVb2Zlqb+HJUrBmQ== X-OriginatorOrg: virtuozzo.com X-MS-Exchange-CrossTenant-Network-Message-Id: cdbb2e8b-0181-4085-1906-08da9c9d2ee7 X-MS-Exchange-CrossTenant-AuthSource: AS8PR08MB7095.eurprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Sep 2022 13:20:14.1260 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 0bc7f26d-0264-416e-a6fc-8352af79c58f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: GY5A6xHA2QImCatCKULwKZvv9lLqDk1gCV7HwmSDXKPZuh719c57LoBR5kjMcqSTZ9eSRidBfsJ+njodkzi4mQ/e6J1Ek/vUBnLkejOLULM= X-MS-Exchange-Transport-CrossTenantHeadersStamped: PAXPR08MB6480 Received-SPF: pass client-ip=40.107.6.136; envelope-from=alexander.ivanov@virtuozzo.com; helo=EUR04-DB3-obe.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" UFS supports FS freezing through ioctl UFSSUSPEND on /dev/ufssuspend. Freezed FS can be thawed by closing /dev/ufssuspend file descriptior. Use getmntinfo to get a list of mounted FS. Signed-off-by: Alexander Ivanov --- qga/commands-bsd.c | 109 +++++++++++++++++++++++++++++++++++++++--- qga/commands-common.h | 11 +++++ qga/main.c | 6 +++ 3 files changed, 120 insertions(+), 6 deletions(-) diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c index c1e3ed13e9..5d3f46804a 100644 --- a/qga/commands-bsd.c +++ b/qga/commands-bsd.c @@ -17,28 +17,125 @@ #include "qemu/queue.h" #include "commands-common.h" +#include +#include +#include +#include +#include + #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) bool build_fs_mount_list(FsMountList *mounts, Error **errp) { - error_setg(errp, QERR_UNSUPPORTED); - return false; + FsMount *mount; + struct statfs *mntbuf, *mntp; + struct stat statbuf; + int i, count, ret; + + count = getmntinfo(&mntbuf, MNT_NOWAIT); + if (count == 0) { + error_setg_errno(errp, errno, "getmntinfo failed"); + return false; + } + + for (i = 0; i < count; i++) { + mntp = &mntbuf[i]; + ret = stat(mntp->f_mntonname, &statbuf); + if (ret != 0) { + continue; + } + + mount = g_new0(FsMount, 1); + + mount->dirname = g_strdup(mntp->f_mntonname); + mount->devtype = g_strdup(mntp->f_fstypename); + mount->devmajor = major(mount->dev); + mount->devminor = minor(mount->dev); + mount->fsid = mntp->f_fsid; + mount->dev = statbuf.st_dev; + + QTAILQ_INSERT_TAIL(mounts, mount, next); + } + return true; } #endif #if defined(CONFIG_FSFREEZE) +static int ufssuspend_fd = -1; +static int ufssuspend_cnt; + int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints, strList *mountpoints, FsMountList mounts, Error **errp) { - error_setg(errp, QERR_UNSUPPORTED); - return 0; + int ret; + strList *list; + struct FsMount *mount; + + if (ufssuspend_fd != -1) { + error_setg(errp, "filesystems have already frozen"); + return -1; + } + + ufssuspend_cnt = 0; + ufssuspend_fd = qemu_open(_PATH_UFSSUSPEND, O_RDWR, errp); + if (ufssuspend_fd == -1) { + return -1; + } + + QTAILQ_FOREACH_REVERSE(mount, &mounts, next) { + /* + * To issue fsfreeze in the reverse order of mounts, check if the + * mount is listed in the list here + */ + if (has_mountpoints) { + for (list = mountpoints; list; list = list->next) { + if (strcmp(list->value, mount->dirname) == 0) { + break; + } + } + if (!list) { + continue; + } + } + + /* Only UFS supports suspend */ + if (strcmp(mount->devtype, "ufs") != 0) { + continue; + } + + ret = ioctl(ufssuspend_fd, UFSSUSPEND, &mount->fsid); + if (ret == -1) { + /* + * ioctl returns EBUSY for all the FS except the first one + * that was suspended + */ + if (errno == EBUSY) { + continue; + } + error_setg_errno(errp, errno, "failed to freeze %s", + mount->dirname); + goto error; + } + ufssuspend_cnt++; + } + return ufssuspend_cnt; +error: + close(ufssuspend_fd); + ufssuspend_fd = -1; + return -1; + } int qmp_guest_fsfreeze_do_thaw(Error **errp) { - error_setg(errp, QERR_UNSUPPORTED); - return 0; + int ret = ufssuspend_cnt; + ufssuspend_cnt = 0; + if (ufssuspend_fd != -1) { + close(ufssuspend_fd); + ufssuspend_fd = -1; + } + return ret; } #endif diff --git a/qga/commands-common.h b/qga/commands-common.h index aa0472ea4c..c3be6db3a9 100644 --- a/qga/commands-common.h +++ b/qga/commands-common.h @@ -41,11 +41,22 @@ void ga_wait_child(pid_t pid, int *status, Error **errp); #endif #endif /* __linux__*/ +#ifdef __FreeBSD__ +#include +#ifdef UFSSUSPEND +#define CONFIG_FSFREEZE +#endif +#endif /* __FreeBSD__ */ + #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) typedef struct FsMount { char *dirname; char *devtype; unsigned int devmajor, devminor; +#if defined(__FreeBSD__) + dev_t dev; + fsid_t fsid; +#endif QTAILQ_ENTRY(FsMount) next; } FsMount; diff --git a/qga/main.c b/qga/main.c index 22b3c0df11..ab420051fb 100644 --- a/qga/main.c +++ b/qga/main.c @@ -43,6 +43,12 @@ #define CONFIG_FSFREEZE #endif #endif +#ifdef __FreeBSD__ +#include +#ifdef UFSSUSPEND +#define CONFIG_FSFREEZE +#endif +#endif #ifndef _WIN32 #ifdef __FreeBSD__ From patchwork Thu Sep 22 13:20:00 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Ivanov X-Patchwork-Id: 12985188 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0BC14C54EE9 for ; Thu, 22 Sep 2022 13:55:06 +0000 (UTC) Received: from localhost ([::1]:52326 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1obMfQ-0008Db-Uh for qemu-devel@archiver.kernel.org; Thu, 22 Sep 2022 09:55:04 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:51626) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8g-0001Y9-6G for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:21:26 -0400 Received: from mail-eopbgr60136.outbound.protection.outlook.com ([40.107.6.136]:7326 helo=EUR04-DB3-obe.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8O-0004pt-0V for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:21:02 -0400 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=LhFClolz/QjvEFWbPNLl+6sgV9kgu8VE2B2PonCa/kqRxlr9W9QDp41XLAYXQWifFx3+XQKkx47W0FacbfmtL4fzlUuwinud+K6UU6vnv5UnGHNlgdDlXlNCWMBvj7etAaPtPgjQ7wwZcZnhwH0x7XIcK1y/qdivJCDhFho+D8gq0WOyW4OvPWX3/o8cplkcEthYt5QCYGROKeIChbglmKVBg2FK4+g/i0O9cNSQjPeOKTOnLW9jGO2RSe02W5QgNjMcg1DWHwuSGSss/W2dMDMaLs1X5oFltLHivyOhwWy9VqAC+HrnwHbGH93q9J4sI8DvXKXr6lIrqWdZe1AUJA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=NEdr7wxFSVRIJNKjBaI3l1HwfauaFWtlEsxfhrZpjH0=; b=KGWkNFswSpqqBTtwDfYz4DxbM+tQ/wju3B3IvgxDz528szpBr+mHu4Y56oIDM2pOtMnjPgFyBDsueH+bNK7DcFTjX3VcEvaMkNW4lcQgNvS5gZo2W1OxFTqjE5ULg47G61u+AI5K/sou7QF3KWfXjJE0QyoLC6ftBXLDH0ZfoJsPfHYCdU54fVGDoLTg69sqJYqYjBwtRc+f+Lpbcsa0FYhL1mSnorwt8bYMMKy30QfJw+2P6qrvq/mCZPmekH3NosMo4Ld9YFMwqm0J5IiUITyAUVU/Dp2APIokqOP4J6n6gpYKCys5aGCOy165xvjvk74CGY/1Ddk3DB8a0g2Yeg== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=virtuozzo.com; dmarc=pass action=none header.from=virtuozzo.com; dkim=pass header.d=virtuozzo.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=virtuozzo.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=NEdr7wxFSVRIJNKjBaI3l1HwfauaFWtlEsxfhrZpjH0=; b=Wtyc61xbypG583C+g5T74KfYMHtE28R2EYO/p522WHVj77dEIyNp1tClSXxdH8WFxcZ7YEFeh1+awat7Gi7yalkufaQpg4SrGleJt817O/CXEcLoPGWyxM6r9HJGOcFnS89hXPddGRFhR5nOgclMdRmcX0LuJPJpyzVsjcWm8agEG/ffZNOuzI0+Uk7AImEj6WGlOD9LXxxMEzpcZZ3THVYiEkQHxOBgUY6NdmMwUFKhx6MYA0JPhYxiK4YkDgrfEAzaAxEN9brryBh0s7OwVqfLegfauBFkoFzwFPQuH4vw8G8MP7N7EXLbpK2o//JDIdQi7uTRAlH9z69PHGje2Q== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=virtuozzo.com; Received: from AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) by PAXPR08MB6480.eurprd08.prod.outlook.com (2603:10a6:102:155::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5654.16; Thu, 22 Sep 2022 13:20:15 +0000 Received: from AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b]) by AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b%8]) with mapi id 15.20.5654.016; Thu, 22 Sep 2022 13:20:15 +0000 From: Alexander Ivanov To: qemu-devel@nongnu.org Cc: den@virtuozzo.com, michael.roth@amd.com, kkostiuk@redhat.com Subject: [PATCH 4/5] qga: Add shutdown/halt/reboot support for FreeBSD Date: Thu, 22 Sep 2022 15:20:00 +0200 Message-Id: <20220922132001.940334-5-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> References: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> X-ClientProxiedBy: FR0P281CA0083.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:1e::18) To AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: AS8PR08MB7095:EE_|PAXPR08MB6480:EE_ X-MS-Office365-Filtering-Correlation-Id: 8c7fe1a6-8a33-4763-38e1-08da9c9d2f84 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: HuFhhZDiQjl9pvIQrTQjdHsXCq2SvCl+ZGTJyknalB/r7rNPCa/4GHJfkAorKwUOAdoCVEFUnvUK/UyBCfmuqTFd197qrRgXFbw8e+jlyrV0LcDp7e3mb7sTlLgyCevK5esOLNKj08x4wTXrE+KJZcZ9Sv7XC8ySivYVGren5Ghtnn50Lvg1QxHBIgIDVk/CpQtjiRbaQPSfaySYviwFRQBRNsPzrxwrIqiOg9E1Rtvsesmc4Er/vbzxfxRtY+VbRFRzCvV4VrgPbnTLcV2otTD08JzJIbfl+gXA+gShXbjFbxn17TUn5ttoSDDmh0xO/nWyuMay46lc/xqTFfWmggLj2RCXM28Y53IlmOMYTHFp43yudIOJczhpKTIBNbBBkEjH2d+XuazFhzvJvZr0cnLnvBZl1XQNWzLpuGODUDtaOjgLep4mTEEA9GvmF2Zxxq6uRUa7Yx2ovtCWseiee3T6uGJqMNIRyap5AmQsvkN+r5tLe7hJD4SqpdpDRU6gmEXA77lRqm/p/3yRyJPmJZjRHSMoNKABmcStWo7JlMSNorfNqFOgyu/1sVW+ML4Bel/L90BP6YVh3yTGtrI3U+MNosxgE6Z2gDoF92l50sB2pS5E3r84YoYNtQN1wDi9hQIc/sXiYXhQcZZPbjKe4e+E4C8ITc3CkFBBObp4hHqEAjRW3rSxKmeI1zF63B88uxy2xXb15zmWGvuY/ZS56RoDdbOpUuJVhycNgA6/zwv/KUDsSPp9ZEc/BhRr5StK8EBRXOwdMrNt6ZUe/2rWyQ== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:AS8PR08MB7095.eurprd08.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230022)(4636009)(346002)(366004)(376002)(136003)(396003)(39850400004)(451199015)(41300700001)(6486002)(478600001)(6666004)(52116002)(36756003)(66476007)(66556008)(316002)(186003)(66946007)(1076003)(5660300002)(2616005)(8936002)(26005)(6506007)(6916009)(86362001)(8676002)(44832011)(4326008)(2906002)(6512007)(38350700002)(38100700002); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: vU61dC9zTf3XJFMaewPnDZ6wjCS3loWhi22NpaPQxpc+mHv97Iuu78jZLCOb4XELsJXZ81LKwHJidvagQRwXp61AkCnQnuEkmlrYDp+KdIJXkpt1nswx2cNZucBNK7qQFtl/2CvI+7qhxM5dB9Sf9pGXWArkZqrG1c0CVdIIKrbY3biHSALmCbE6R3Rcnrckeac5weduYSN7jJaUjS8dInO4hSMhCXhEFqW4NsTWMop8JB7EU5YXqKeFMYkIMcTCuWnMjoeoSdSfXT7bIrxcHW4kQ8KG9n2QyR5YC5Q85xR3MLp1dq9oqbu3g6bcP4M0OraNyYKa0MnbI3El+QyhDbKN0/cBSGXjBMnyxHPc0JeT97V/s362m1dtRt50+GFmT5JC80R2/p1GPA1xYwq2/s2N8wWpPfsq046583qt4U24ZzKBhRP/RzkmiB3DSq8YKxRRMY4KkdWqmVtqZE5LYdC+bdtC5P0JAmT8pCyt1bg5r/amRpvU3bZBbHEKwfyR4x6DebG5+x0PyRlmIRQTDtJwdE74XLRayJDUeTTaxjTviG7PGLkSTjuo/ZoF4Pui+FCDSbIY0+kF+gvmZtqArEL3X7lyaFQxxYHb/ZrUDzqAWtK6yAwnP74vVRtIfJQPd516a/01ReX4qI/rwHREc61lgZmo1f4tI76PAfTAkSALRpeyNyQVtwo0dRwzCyeqmGY64pYqBuqpiOzr0pmXgGm+vQcqsHjDVhHfDtAYqoKCkHL+JTEJXbEnhGINnRu6qvfUGiqt/IQRQ5K3TuJ0KL/ozeReHn+EZmFMyn8dJud/uiQE49rYDuiK1w/ZIHDIIOKkpC9Y0vldjxNHbYKt9WEbewbN3d4mbXMBSIbt4w5Il3fCvwbbhqtVg5K2n+Tew7/Ho7D3+Sko3CDV56uL3LF1vYJI38reLsdlPlnttxyILRYyohbZdtHhqkcrdIa1pNCiTUiJcyIl6eI9+X7DCZqPug4VLd7hSxFVisG8V2Jo+LW2OdawtS7e3P+Uo2hmzZtBO44SXVHG1HFDBXMKr4Gr4h2BBjx7UF40mtyhEeTvYlb83mKX8LbjhDTOUsg4pX6Hy5ro1Vzdrpa2m+5HtFoRLYVqyvLGZ8l9Ozqq1bhKN+7CKEgkOMR/ffmly5nDU2XyljSA6q17METZpk7gtmuTwfeqfhGwsb0WymIoR09eK0qCvrjODZfKiUnsRPoiaf6rj6o2+ITuJ+ZNXdDaXnrjcRe4i4iAW+eooSJTvp+9wxsu/XwU2eafM2o4OYn41t2p432xIDlXKGojEVRN4RechOjm7gsjawk1l01hx+3osbAfXyLTBs18DlEH+FyN/sdIcd5I9jfthm6f75h17xPRxuWQbD6VqfzS0XjNHdVIAkZiPAat8tlJrPc90HoWnc1tA8+uullBOsBl7WiLdnlVCLH6AOzjEVQ0eAekvjk+L3JVyhV0v/M66HDNucaCWEUoRq/kEsaJQ0HKQANQ4dRkaiF/i5/XSy5mePDq4tzFVpZ7onQ2ZwJCUgJ/ZR8PVZLhkiom+bxawPJMH4QdRaA3XVdFllsUZTy4sZs6VJVHX0t1bKpI+4IjXTP1MOGOP5AydljymMDHmrfkgFXXBg== X-OriginatorOrg: virtuozzo.com X-MS-Exchange-CrossTenant-Network-Message-Id: 8c7fe1a6-8a33-4763-38e1-08da9c9d2f84 X-MS-Exchange-CrossTenant-AuthSource: AS8PR08MB7095.eurprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Sep 2022 13:20:15.0307 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 0bc7f26d-0264-416e-a6fc-8352af79c58f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: gZcmoJPr8ZCZFWgaSXXS6CtsqrPLr+xCsKICvQJ/l4pj5YJx1X8TrtB2G4GvMTw/PhF/AN/vp5zuXI0itH3ACrOAlz7tL0b11mnFRk0hOF8= X-MS-Exchange-Transport-CrossTenantHeadersStamped: PAXPR08MB6480 Received-SPF: pass client-ip=40.107.6.136; envelope-from=alexander.ivanov@virtuozzo.com; helo=EUR04-DB3-obe.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Add appropriate shutdown command arguments in qmp_guest_shutdown() for FreeBSD. Signed-off-by: Alexander Ivanov --- qga/commands-posix.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 3a1055d5c3..60cc673f25 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -242,6 +242,10 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp) const char *powerdown_flag = "-i5"; const char *halt_flag = "-i0"; const char *reboot_flag = "-i6"; +#elifdef CONFIG_BSD + const char *powerdown_flag = "-p"; + const char *halt_flag = "-h"; + const char *reboot_flag = "-r"; #else const char *powerdown_flag = "-P"; const char *halt_flag = "-H"; @@ -272,6 +276,9 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp) #ifdef CONFIG_SOLARIS execl("/sbin/shutdown", "shutdown", shutdown_flag, "-g0", "-y", "hypervisor initiated shutdown", (char *)NULL); +#elifdef CONFIG_BSD + execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", + "hypervisor initiated shutdown", (char *)NULL); #else execl("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0", "hypervisor initiated shutdown", (char *)NULL); From patchwork Thu Sep 22 13:20:01 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexander Ivanov X-Patchwork-Id: 12985228 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id BA0EFC54EE9 for ; Thu, 22 Sep 2022 14:25:14 +0000 (UTC) Received: from localhost ([::1]:43130 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1obN8b-0007BI-QJ for qemu-devel@archiver.kernel.org; Thu, 22 Sep 2022 10:25:13 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:46674) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8h-0001YS-NB for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:21:26 -0400 Received: from mail-eopbgr60136.outbound.protection.outlook.com ([40.107.6.136]:7326 helo=EUR04-DB3-obe.outbound.protection.outlook.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1obM8e-0004pt-6d for qemu-devel@nongnu.org; Thu, 22 Sep 2022 09:21:15 -0400 ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=QGmWOUubNGQ/hlCB5AiDztVYD94tf2rm9oGTQfGBGYQMv4WSgH0P3JawA0L728vn0vHXzIg8dgiOHy33PgaDm2GHUeihhZSi6gjqaEJGLGhAgsrfwPwi/ALZjuIiCJHtDWcDEkQIk/iz6VR7juEPFhoii75fI0jO0frkx5aL7dcWisOjWheuDfxlOBpaZHZVio1jPiH6pHdGwlKHU41S9Mm6G4H1wKLKcKmanVXg0+IoB9qx0fPw9Odl8M3hCfHxtGKm3Ah9lgzK/TgSYeCDXXdNNJbDVrLyv4rLkMWk14w4w/cQYxFgqDylSEgH32E8MpIWmlKwGld4rDlbkhvQXw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=qnO7doIdUC9xNB7+sP10/LMdPtsJPq+NbTdQrvdFWAA=; b=QV25mLHHkJY9ADAGaCXymq0VMnNPLJ6lovR/7Q8orzb2v0j6JfQMvwnJc3YZUR9z91nA3zdc9pXEhsWHpvMej17cIF10ORgUecynM0CmEu8zSL+a+yzrPSa1drxDiC6uPqaILsZXBu7zQTermasE4oKQiMST8+3JMStVnHAp3P9xxgudpEXfv70beUNAijjzh4gnaqd+ARqPCUjvaTP53HoOlD8zsBA3O/uhCGWACivsGM09zQ+TjGsJ1yJ4i0sf7Yhp+elaQXJ8qleQ5dHvaHGkY+cbwqzecGDTerVHzrxb6J+j9TWvZLxpx3yHLoUIEtP/MA8QdvdznrVm7UI+vA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=virtuozzo.com; dmarc=pass action=none header.from=virtuozzo.com; dkim=pass header.d=virtuozzo.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=virtuozzo.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=qnO7doIdUC9xNB7+sP10/LMdPtsJPq+NbTdQrvdFWAA=; b=ISnIRoR39DbdbJIrQI0yuZ7plQ2laS8YiV23tfanpHNJpdzyv6hGnQ6e/bSrbX28R2j9YnNB8o+Br++da7t+bCXXpss38jSqWBZliEgWQ/A2a8IOrsLT0LRLgnKuKU7T1RXgskIG0DLaOs76LYAagt2QSoA1fHXEIEkRYtk7HC46p/WxbUZjth8WmAQWNVAxqyJyYw58c2gtVFieIxVe6NOvHsbZOHZGybx65jt0nqzAlZ/tLAT2zety3eUza8ejOJlCGMhN/ofapUeXM9oZ9ok+fj1S4kgyHkTR5XIj1Lnysw+5Bm3Iwmx/D7/PAyJ7e2lDga74bygnlNbais32+Q== Authentication-Results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=virtuozzo.com; Received: from AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) by PAXPR08MB6480.eurprd08.prod.outlook.com (2603:10a6:102:155::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.5654.16; Thu, 22 Sep 2022 13:20:15 +0000 Received: from AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b]) by AS8PR08MB7095.eurprd08.prod.outlook.com ([fe80::5174:25c7:6df8:741b%8]) with mapi id 15.20.5654.016; Thu, 22 Sep 2022 13:20:15 +0000 From: Alexander Ivanov To: qemu-devel@nongnu.org Cc: den@virtuozzo.com, michael.roth@amd.com, kkostiuk@redhat.com Subject: [PATCH 5/5] qga: Add support for user password settinng in FreeeBSD Date: Thu, 22 Sep 2022 15:20:01 +0200 Message-Id: <20220922132001.940334-6-alexander.ivanov@virtuozzo.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> References: <20220922132001.940334-1-alexander.ivanov@virtuozzo.com> X-ClientProxiedBy: FR0P281CA0083.DEUP281.PROD.OUTLOOK.COM (2603:10a6:d10:1e::18) To AS8PR08MB7095.eurprd08.prod.outlook.com (2603:10a6:20b:402::11) MIME-Version: 1.0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: AS8PR08MB7095:EE_|PAXPR08MB6480:EE_ X-MS-Office365-Filtering-Correlation-Id: 6ad83f21-9034-4b18-68c0-08da9c9d2ffb X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0; X-Microsoft-Antispam-Message-Info: ZINIkIFbQIhnOgDQT7ACjtTVXKUpR7IU37sqJB2qUaEphjCb54kTkqlkjrTXZLmfgrgFI4Zd2aqHimK7u/Sp3YEWL6gZ0MTIcLPRr4sWm+gxbSPyAtX8x/NfX1/HSX/s4g6Rc53eDSxIs6c0YKWKqF5ltTz7gAxkcN0pULTcDVXlyPG56vXqfv6IIlA3tPOfNDV5SKiE5/9F6MvJlBY1LNy3F0nYFtMREQkYwTJZ4Ovt0KHsq0EXTheSyMx5nGEBwpYiUGolcpyVSKjpIZmJ4yRaDEfjK1YBUuv05LCtQ1rExJy9QF5x3z/LRNwPKOsVTAp+VpaX5XHLWxQxanIzQKZvfQwsMoPGXTMcDaPh1U6BZhRablHcZepzKW6+CvDGyFLtb3WpzUB3zkbJGRpNqjax1hAaeFpJeFQrQ70aDJ1q+kyj2KogGZ8V6cSYH+iNzrM3Q5KWYeQbsRlboJkCiz7EHdX7iqZwBeaJ9uQAIgYDQ31X677YCRKXdOtxxyR4ZMIeT/yU8CaFhXJNAF6OZpSISeZxP2Q2GdEbUWj5lLCd3ecbZy/l/rvGTUwYDV7I/IHqaMRQOauFt4lOzhE7aXphy0cHfI251NOQkotht8hZQ2y7sYBfB+eG/VFp2r2Dm+1QdtlKXCX9v3TQr2VkmDFaJHz1yykEVqc8w4AyFDlZzo2IgSa595LdwVw0+FoNCCEt+L/FLkYewV8g+9IahK4P82YQ/NAphYRV2zW34qlf0nMyxUoYw8DFvO4OlEhGAhW7LbDue/bEpCwMWmJQIw== X-Forefront-Antispam-Report: CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:; IPV:NLI; SFV:NSPM; H:AS8PR08MB7095.eurprd08.prod.outlook.com; PTR:; CAT:NONE; SFS:(13230022)(4636009)(346002)(366004)(376002)(136003)(396003)(39850400004)(451199015)(41300700001)(6486002)(478600001)(6666004)(52116002)(36756003)(66476007)(66556008)(316002)(186003)(66946007)(83380400001)(1076003)(5660300002)(2616005)(8936002)(26005)(6506007)(6916009)(86362001)(8676002)(44832011)(4326008)(2906002)(6512007)(38350700002)(38100700002); DIR:OUT; SFP:1102; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: hQYoJRhWRIA0xTqXZCnan0cUcwZqANkGicxN9Yp+D6BgbDM6p2tIWh4S04gM+y6/RwaaDhZUEqp3xH7lArRG7OVgNuC52/n9LqjFagBD3NPu4iAZqgi3SYMYgrgNKms+cp3crWMAFCNbdZDOGomJwEhAmj7u8rQYDlftbZNW+K3mIsrqY+IfMB1frsTlAcgZK1U04m+/P+EVmzJ51udfiIjEFBy7ReYyALFDhD3uk851a9OWTH1C534yZH315syi1vraQCdscrKmkl0H95euqrVvalAjU88jnc7R1qPOXADuitht7+Xp9JXP8HaHf1BO2UyL5CAIIPQ2EQk8MG/3OzEBfDe8j+iLosRb6Nm55YdthT78lZSXUO1yQrby3Zqx7u6TgZL3d88n6pvtatf22hCk3zJ9lOgqQEmJGo2HduERVuftGxUqcW7fKd+i+H/5Sv72Ss+CDj+nt8NHpr+JbfejWvSGVY6X6UagdfHM5EFE12DrvbX3AWdKrmcYsxUrUGjvAljCFPAw3HRemF8OH81HFVevBDB7tJ+ypw5G1iv70kvQDkuHEaNU+waB1GL8CFpAwjU/LOtbUf/J4bfUEthdmS8sdwzc8lNFNI4A8rZYA8kmrI4EBliQNdv5ERMWkqMenbr4lO1cES1FwA56nSYp1epqvKvFgSXe2KE75CxLXiQkudf9Rr/tcT2mWx+gtVc7eNgs/DqVY+Y1o3cKeQxm87Ua6usX2ZeFCvXKgSTrOiJBTUOfUcF06MGhI9HpPk1CeSMEHtEES8hHdUsR2YzuKbOhMsG+q4FP0gU69C8j8xk3g7pFqxY1AOKJI7vkcfOkgyI2eBcBCD+ox05IjVVFCqog7xbluCneHHuTt2ULoj2dndcjj9FA2KuwcVfiS/klWaPnw//4B9He0xOpT5Kji+3PiNHR3P0Y6mBoKZ11RGmRooSgBaNq5Kp6zpPtJmXMQvUdYcAiblE2kROX//T3l6yz4UGBUAYul0GY8BTWjgAnNb5sCpSp34opQpyh0MESCdAVCli+9Wsm++ynwBNkHieR0WjM7z7HG+3cIRnvPNQN9wpAKvCuC0VRDoWCB9eQDylM8WVx/MPX9QKulO+N8Zpnqauukiu36abIrswHJZ6O31hQylCccJlk5qEXpiwhPRsABeSbXMbNTzZKBFZWdmgcEJCqulq+ryN92q5Wa9jYpxtP2WJfLDPTGY+yeGbZPS5UDOoWfnvsmWaV8Ec6ubCr7HZvEI3iY600w68fwLmdX7fQZHBU3UkJ0ODJ/h6qdZ7nsarL1PkwZ3jUwsz4HEn8YP9CUyGd8uhn4qFgYKAwTbcY93Ei1c1z/PFr15lcNCpLB7zTvfiExCAhranUm/1lWOT8XcmERTC40/W/c6EBOILKtVkRlWUteVB3e6x5MQYW7Ecmn/eSXpKOckXEnorQxStf9qDhlclilvcDMF0Kcifw8iHlWutZbhTp+xv2QmarM4n9upX5aadmxuQBB9F4uZ86oKJjrjg6rkLHwuRjw4DMNMzV6xo5h0are/p/4o4S4nbjwBM5K5N9NTitxPIKtXmuzSmbvbmwVm0W//0SvJBVMeoy03oU6SWbMSSiAuBQEK5lXqLNijxN5w== X-OriginatorOrg: virtuozzo.com X-MS-Exchange-CrossTenant-Network-Message-Id: 6ad83f21-9034-4b18-68c0-08da9c9d2ffb X-MS-Exchange-CrossTenant-AuthSource: AS8PR08MB7095.eurprd08.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 22 Sep 2022 13:20:15.6868 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 0bc7f26d-0264-416e-a6fc-8352af79c58f X-MS-Exchange-CrossTenant-MailboxType: HOSTED X-MS-Exchange-CrossTenant-UserPrincipalName: BJVNcvKeBpo+2ca/RU0jGoOhV73Gm5E7/PnqoaYgHst0qTqwfie3r3870s2lgl5Hma2/1uO+oOm+NgSAez5arGe+OOlvVF/5hI+rP/yh5E4= X-MS-Exchange-Transport-CrossTenantHeadersStamped: PAXPR08MB6480 Received-SPF: pass client-ip=40.107.6.136; envelope-from=alexander.ivanov@virtuozzo.com; helo=EUR04-DB3-obe.outbound.protection.outlook.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" Move qmp_guest_set_user_password() to commands-posix.c under (__linux__ or __FreeBSD) condition. Add command and arguments for password setting in FreeBSD. Signed-off-by: Alexander Ivanov --- qga/commands-bsd.c | 8 --- qga/commands-linux.c | 105 -------------------------------------- qga/commands-posix.c | 117 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 114 deletions(-) diff --git a/qga/commands-bsd.c b/qga/commands-bsd.c index 5d3f46804a..fa3933f2f4 100644 --- a/qga/commands-bsd.c +++ b/qga/commands-bsd.c @@ -190,14 +190,6 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) return -1; } -void qmp_guest_set_user_password(const char *username, - const char *password, - bool crypted, - Error **errp) -{ - error_setg(errp, QERR_UNSUPPORTED); -} - GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp) { error_setg(errp, QERR_UNSUPPORTED); diff --git a/qga/commands-linux.c b/qga/commands-linux.c index 615e9a0027..1f25c80482 100644 --- a/qga/commands-linux.c +++ b/qga/commands-linux.c @@ -1629,111 +1629,6 @@ int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp) return processed; } -void qmp_guest_set_user_password(const char *username, - const char *password, - bool crypted, - Error **errp) -{ - Error *local_err = NULL; - char *passwd_path = NULL; - pid_t pid; - int status; - int datafd[2] = { -1, -1 }; - char *rawpasswddata = NULL; - size_t rawpasswdlen; - char *chpasswddata = NULL; - size_t chpasswdlen; - - rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp); - if (!rawpasswddata) { - return; - } - rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1); - rawpasswddata[rawpasswdlen] = '\0'; - - if (strchr(rawpasswddata, '\n')) { - error_setg(errp, "forbidden characters in raw password"); - goto out; - } - - if (strchr(username, '\n') || - strchr(username, ':')) { - error_setg(errp, "forbidden characters in username"); - goto out; - } - - chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata); - chpasswdlen = strlen(chpasswddata); - - passwd_path = g_find_program_in_path("chpasswd"); - - if (!passwd_path) { - error_setg(errp, "cannot find 'passwd' program in PATH"); - goto out; - } - - if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) { - error_setg(errp, "cannot create pipe FDs"); - goto out; - } - - pid = fork(); - if (pid == 0) { - close(datafd[1]); - /* child */ - setsid(); - dup2(datafd[0], 0); - reopen_fd_to_null(1); - reopen_fd_to_null(2); - - if (crypted) { - execl(passwd_path, "chpasswd", "-e", NULL); - } else { - execl(passwd_path, "chpasswd", NULL); - } - _exit(EXIT_FAILURE); - } else if (pid < 0) { - error_setg_errno(errp, errno, "failed to create child process"); - goto out; - } - close(datafd[0]); - datafd[0] = -1; - - if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) { - error_setg_errno(errp, errno, "cannot write new account password"); - goto out; - } - close(datafd[1]); - datafd[1] = -1; - - ga_wait_child(pid, &status, &local_err); - if (local_err) { - error_propagate(errp, local_err); - goto out; - } - - if (!WIFEXITED(status)) { - error_setg(errp, "child process has terminated abnormally"); - goto out; - } - - if (WEXITSTATUS(status)) { - error_setg(errp, "child process has failed to set user password"); - goto out; - } - -out: - g_free(chpasswddata); - g_free(rawpasswddata); - g_free(passwd_path); - if (datafd[0] != -1) { - close(datafd[0]); - } - if (datafd[1] != -1) { - close(datafd[1]); - } -} - static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf, int size, Error **errp) { diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 60cc673f25..e8fc7bd516 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -774,8 +774,123 @@ void qmp_guest_file_flush(int64_t handle, Error **errp) } } -#if !(defined(__linux__) || defined(__FreeBSD__)) +#if defined(__linux__) || defined(__FreeBSD__) +void qmp_guest_set_user_password(const char *username, + const char *password, + bool crypted, + Error **errp) +{ + Error *local_err = NULL; + char *passwd_path = NULL; + pid_t pid; + int status; + int datafd[2] = { -1, -1 }; + char *rawpasswddata = NULL; + size_t rawpasswdlen; + char *chpasswddata = NULL; + size_t chpasswdlen; + rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp); + if (!rawpasswddata) { + return; + } + rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1); + rawpasswddata[rawpasswdlen] = '\0'; + + if (strchr(rawpasswddata, '\n')) { + error_setg(errp, "forbidden characters in raw password"); + goto out; + } + + if (strchr(username, '\n') || + strchr(username, ':')) { + error_setg(errp, "forbidden characters in username"); + goto out; + } + +#ifdef __FreeBSD__ + chpasswddata = g_strdup(rawpasswddata); + passwd_path = g_find_program_in_path("pw"); +#else + chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata); + passwd_path = g_find_program_in_path("chpasswd"); +#endif + + chpasswdlen = strlen(chpasswddata); + + if (!passwd_path) { + error_setg(errp, "cannot find 'passwd' program in PATH"); + goto out; + } + + if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) { + error_setg(errp, "cannot create pipe FDs"); + goto out; + } + + pid = fork(); + if (pid == 0) { + close(datafd[1]); + /* child */ + setsid(); + dup2(datafd[0], 0); + reopen_fd_to_null(1); + reopen_fd_to_null(2); + +#ifdef __FreeBSD__ + const char *h_arg; + h_arg = (crypted) ? "-H" : "-h"; + execl(passwd_path, "pw", "usermod", "-n", username, h_arg, "0", NULL); +#else + if (crypted) { + execl(passwd_path, "chpasswd", "-e", NULL); + } else { + execl(passwd_path, "chpasswd", NULL); + } +#endif + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + goto out; + } + close(datafd[0]); + datafd[0] = -1; + + if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) { + error_setg_errno(errp, errno, "cannot write new account password"); + goto out; + } + close(datafd[1]); + datafd[1] = -1; + + ga_wait_child(pid, &status, &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto out; + } + + if (!WIFEXITED(status)) { + error_setg(errp, "child process has terminated abnormally"); + goto out; + } + + if (WEXITSTATUS(status)) { + error_setg(errp, "child process has failed to set user password"); + goto out; + } + +out: + g_free(chpasswddata); + g_free(rawpasswddata); + g_free(passwd_path); + if (datafd[0] != -1) { + close(datafd[0]); + } + if (datafd[1] != -1) { + close(datafd[1]); + } +} +#else void qmp_guest_suspend_disk(Error **errp) { error_setg(errp, QERR_UNSUPPORTED);