From patchwork Tue Mar 31 08:34:35 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: itamar.tal4@gmail.com X-Patchwork-Id: 456535 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id B5AEA14008F for ; Tue, 31 Mar 2015 19:37:12 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="verification failed; unprotected key" header.d=gmail.com header.i=@gmail.com header.b=IM825xtK; dkim-adsp=none (unprotected policy); dkim-atps=neutral Received: from localhost ([::1]:37530 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Ycrfa-0004W9-TS for incoming@patchwork.ozlabs.org; Tue, 31 Mar 2015 04:37:10 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:54295) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YcrdQ-0000aY-Gs for qemu-devel@nongnu.org; Tue, 31 Mar 2015 04:34:58 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YcrdO-0004FG-MZ for qemu-devel@nongnu.org; Tue, 31 Mar 2015 04:34:56 -0400 Received: from mail-wi0-x230.google.com ([2a00:1450:400c:c05::230]:33732) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YcrdO-0004F9-Bu for qemu-devel@nongnu.org; Tue, 31 Mar 2015 04:34:54 -0400 Received: by wixm2 with SMTP id m2so9018796wix.0 for ; Tue, 31 Mar 2015 01:34:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references :signed-off-by; bh=piYVVvtLocj7epAxcfDmTlQyw3p8UB4/NHVMoZV8sHs=; b=IM825xtKos/2N5kWypu7fkDGUKtpbuQclYUKYFHnG2N9/kdi926mfb/vlSAYrTuCSv 2NL5gzPAhA+93Bq/kvifolqmRBHYeI+acBN1dx2WGOf7tZWZLMJBHkTmvBi7B5Nq4tsD qJQ1nclLOyd+zwqSOOqp7+2gmOEC04c3ZLuPhGpY+N6wlI6p+CwmFP2NVcCtXcsDWeGq xKqm8uxuwZLfKaxIaG7wuChlFdEuE0JOT5DLE9r8tUdvd4AUkQomATjtMuq4gEIJZT/m z7USW3KWzotGRbxTsWNfERw0gMOq1XZ7RnDyrtcmCa4meAgWmNDRM7KDYVRedu1nycBV ahWw== X-Received: by 10.180.8.69 with SMTP id p5mr3422118wia.69.1427790893761; Tue, 31 Mar 2015 01:34:53 -0700 (PDT) Received: from hpvm.guardicore.com (bzq-233-168-31-124.red.bezeqint.net. [31.168.233.124]) by mx.google.com with ESMTPSA id fy2sm19857741wic.15.2015.03.31.01.34.52 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 31 Mar 2015 01:34:53 -0700 (PDT) From: itamar.tal4@gmail.com X-Google-Original-From: itamar@guardicore.com To: qemu-devel@nongnu.org Date: Tue, 31 Mar 2015 11:34:35 +0300 Message-Id: <1427790877-14544-3-git-send-email-itamar@guardicore.com> X-Mailer: git-send-email 2.3.4 In-Reply-To: <1427790877-14544-1-git-send-email-itamar@guardicore.com> References: <1427790877-14544-1-git-send-email-itamar@guardicore.com> Signed-off-by: Itamar Tal X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2a00:1450:400c:c05::230 Cc: ori@guardicore.com, ariel@guardicore.com, mdroth@linux.vnet.ibm.com, pavel@guardicore.com, Itamar Tal Subject: [Qemu-devel] [PATCH 2/4] added missing file commands (attributes, deletion) X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Itamar Tal This patch add support for retrieving file attributes and file deletion by file name, to get a more complete file functionality over the guest agent. --- qga/commands-posix.c | 23 +++++++++ qga/commands-win32.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++ qga/commands.c | 8 +++ qga/qapi-schema.json | 42 ++++++++++++++++ 4 files changed, 212 insertions(+) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index ba8de62..028d533 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -7,6 +7,9 @@ * Michael Roth * Michal Privoznik * + * Changes (itamar@guardicore.com): + * - file attributes, removal, hashes + * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ @@ -2456,3 +2459,23 @@ void ga_command_state_init(GAState *s, GACommandState *cs) #endif ga_command_state_add(cs, guest_file_init, NULL); } + +GuestFileAttributes *qmp_guest_file_attributes(const char *path, Error **errp) +{ + GuestFileAttributes *file_stat = g_malloc0(sizeof(GuestFileAttributes)); + struct stat file_os_stat; + + if (stat(path, &file_os_stat)) { + error_setg(errp, "Failed to get file attributes for '%s' (error %d)", + path, errno); + return NULL; + } + + file_stat->mode = file_os_stat.st_mode; + file_stat->size = file_os_stat.st_size; + file_stat->access_time = file_os_stat.st_atime; + file_stat->modification_time = file_os_stat.st_mtime; + file_stat->creation_time = 0; /* not all Linux FS store file BoD */ + + return file_stat; +} diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 3ef0549..aeb49c5 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -7,6 +7,9 @@ * Michael Roth * Gal Hammer * + * Changes (itamar@guardicore.com): + * - file attributes, removal, hashes + * * This work is licensed under the terms of the GNU GPL, version 2 or later. * See the COPYING file in the top-level directory. */ @@ -742,3 +745,139 @@ void ga_command_state_init(GAState *s, GACommandState *cs) } ga_command_state_add(cs, guest_file_init, NULL); } + +/* The CRT of Windows has a number of flaws wrt. its stat() implementation: + - time stamps are restricted to second resolution + - file modification times suffer from forth-and-back conversions between + UTC and local time + Therefore, we implement our own stat, based on the Win32 API directly. +*/ + +/* Seconds between 1.1.1601 and 1.1.1970 */ +static __int64 secs_between_epochs = 11644473600; + +static void +FILE_TIME_to_time_t_nsec(FILETIME *in_ptr, time_t *time_out, int* nsec_out) +{ + /* XXX endianness. Shouldn't matter, as all Windows implementations are + little-endian */ + /* Cannot simply cast and dereference in_ptr, + since it might not be aligned properly */ + __int64 in; + memcpy(&in, in_ptr, sizeof(in)); + *nsec_out = (int)(in % 10000000) * 100; /* FILETIME is in units of 100 + nsec. */ + *time_out = (time_t)((in / 10000000) - secs_between_epochs); +} + +static int +attributes_to_mode(DWORD attr) +{ + int m = 0; + if (0 != (attr & FILE_ATTRIBUTE_DIRECTORY)) { + m |= _S_IFDIR | 0111; /* IFEXEC for user,group,other */ + } else { + m |= _S_IFREG; + } + if (0 != (attr & FILE_ATTRIBUTE_READONLY)) { + m |= 0444; + } else { + m |= 0666; + } + return m; +} + +static int +attribute_data_to_stat(WIN32_FILE_ATTRIBUTE_DATA *info, + GuestFileAttributes *result) +{ + int nsec = 0; + + memset(result, 0, sizeof(*result)); + result->mode = attributes_to_mode(info->dwFileAttributes); + result->size = (((__int64)info->nFileSizeHigh)<<32) + info->nFileSizeLow; + FILE_TIME_to_time_t_nsec(&info->ftCreationTime, + (time_t *)&result->creation_time, &nsec); + FILE_TIME_to_time_t_nsec(&info->ftLastWriteTime, + (time_t *)&result->modification_time, &nsec); + FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, + (time_t *)&result->access_time, &nsec); + + return 0; +} + +static BOOL +attributes_from_dir(LPCSTR pszFile, LPWIN32_FILE_ATTRIBUTE_DATA pfad) +{ + HANDLE hFindFile; + WIN32_FIND_DATAA FileData; + hFindFile = FindFirstFileA(pszFile, &FileData); + if (hFindFile == INVALID_HANDLE_VALUE) { + return FALSE; + } + (void)FindClose(hFindFile); + pfad->dwFileAttributes = FileData.dwFileAttributes; + pfad->ftCreationTime = FileData.ftCreationTime; + pfad->ftLastAccessTime = FileData.ftLastAccessTime; + pfad->ftLastWriteTime = FileData.ftLastWriteTime; + pfad->nFileSizeHigh = FileData.nFileSizeHigh; + pfad->nFileSizeLow = FileData.nFileSizeLow; + return TRUE; +} + +/* based upon Python implementation of windows file stat() */ +GuestFileAttributes *qmp_guest_file_attributes(const char *path, Error **errp) +{ + WIN32_FILE_ATTRIBUTE_DATA info; + int code; + char *dot = NULL; + GuestFileAttributes *result = NULL; + + if (!GetFileAttributesExA(path, GetFileExInfoStandard, &info)) { + if (GetLastError() != ERROR_SHARING_VIOLATION) { + error_setg(errp, "Failed querying '%s' for attributes (error %d)", + path, (int)GetLastError()); + /* Protocol violation: we explicitly clear errno, instead of + setting it to a POSIX error. Callers should use GetLastError. */ + errno = 0; + return NULL; + } else { + /* Could not get attributes on open file. Fall back to + reading the directory. */ + if (!attributes_from_dir(path, &info)) { + error_setg(errp, "Error querying '%s' attributes (error %d)", + path, (int)GetLastError()); + /* Very strange. This should not fail now */ + errno = 0; + return NULL; + } + } + } + + result = g_malloc(sizeof(GuestFileAttributes)); + if (NULL == result) { + error_setg(errp, "No memory for attributes result for '%s' (error %d)", + path, errno); + return NULL; + } + + code = attribute_data_to_stat(&info, result); + if (code != 0) { + g_free(result); + error_setg(errp, "Error converting attributes result '%s' (code %d)", + path, code); + return NULL; + } + /* Set S_IFEXEC if it is an .exe, .bat, ... */ + dot = strrchr(path, '.'); + if (dot) { + if (stricmp(dot, ".bat") == 0 || + stricmp(dot, ".cmd") == 0 || + stricmp(dot, ".exe") == 0 || + stricmp(dot, ".com") == 0) { + result->mode |= 0111; + } + } + return result; +} + diff --git a/qga/commands.c b/qga/commands.c index f9378f4..64404d6 100644 --- a/qga/commands.c +++ b/qga/commands.c @@ -113,3 +113,11 @@ char *qmp_guest_file_hash(const char *path, Error **errp) return g_strdup((char *)sha256_hexdigest); } + +void qmp_guest_file_remove(const char *path, Error **errp) +{ + if (unlink(path)) { + error_setg(errp, "Error deleting file '%s' (error %d)", path, errno); + errno = 0; + } +} diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 68b420d..cc670b2 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -904,3 +904,45 @@ { 'command': 'guest-file-hash', 'data': { 'path': 'str' }, 'returns': 'str' } + +## +# @GuestFileAttributes +# +# @mode: file access permissions mode +# @size: file size in bytes +# @access-time: file last access time +# @modification-time: file last modification time +# @creation-time: file creation time (when possible) +# +# Since: 2.4 +## +{ 'type': 'GuestFileAttributes', + 'data': {'mode': 'int', 'size': 'uint64', 'access-time': 'int', + 'modification-time': 'int', 'creation-time': 'int' + }} + +## +# @guest-file-attributes: +# +# Get file attributes for a file in the guest's operating system +# for a given file path +# +# Returns: file attributes structure (GuestFileAttributes type) +# +# Since 2.4 +## +{ 'command': 'guest-file-attributes', + 'data': { 'path': 'str' }, + 'returns': 'GuestFileAttributes'} + +## +# @guest-file-remove: +# +# Remove a file in the guest's operating system for a given file path +# +# Returns: +# +# Since 2.4 +## +{ 'command': 'guest-file-remove', + 'data': { 'path': 'str' }}