* See the COPYING file in the top-level directory.
*/
-#include <glib.h>
-#include <sys/types.h>
+#include "qemu/osdep.h"
#include <sys/ioctl.h>
#include <sys/wait.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
#include <dirent.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <inttypes.h>
#include "qga/guest-agent-core.h"
#include "qga-qmp-commands.h"
#include "qapi/qmp/qerror.h"
#include "qemu/queue.h"
#include "qemu/host-utils.h"
+#include "qemu/sockets.h"
+#include "qemu/base64.h"
+#include "qemu/cutils.h"
#ifndef CONFIG_HAS_ENVIRON
#ifdef __APPLE__
{
int ret;
qemu_timeval tq;
- int64_t time_ns;
ret = qemu_gettimeofday(&tq);
if (ret < 0) {
return -1;
}
- time_ns = tq.tv_sec * 1000000000LL + tq.tv_usec * 1000;
- return time_ns;
+ return tq.tv_sec * 1000000000LL + tq.tv_usec * 1000;
}
void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
/* If user has passed a time, validate and set it. */
if (has_time) {
+ GDate date = { 0, };
+
/* year-2038 will overflow in case time_t is 32bit */
if (time_ns / 1000000000 != (time_t)(time_ns / 1000000000)) {
error_setg(errp, "Time %" PRId64 " is too large", time_ns);
tv.tv_sec = time_ns / 1000000000;
tv.tv_usec = (time_ns % 1000000000) / 1000;
+ g_date_set_time_t(&date, tv.tv_sec);
+ if (date.year < 1970 || date.year >= 2070) {
+ error_setg_errno(errp, errno, "Invalid time");
+ return;
+ }
ret = settimeofday(&tv, NULL);
if (ret < 0) {
}
}
+typedef enum {
+ RW_STATE_NEW,
+ RW_STATE_READING,
+ RW_STATE_WRITING,
+} RwState;
+
typedef struct GuestFileHandle {
uint64_t id;
FILE *fh;
+ RwState state;
QTAILQ_ENTRY(GuestFileHandle) next;
} GuestFileHandle;
static struct {
QTAILQ_HEAD(, GuestFileHandle) filehandles;
-} guest_file_state;
+} guest_file_state = {
+ .filehandles = QTAILQ_HEAD_INITIALIZER(guest_file_state.filehandles),
+};
static int64_t guest_file_handle_add(FILE *fh, Error **errp)
{
return -1;
}
- gfh = g_malloc0(sizeof(GuestFileHandle));
+ gfh = g_new0(GuestFileHandle, 1);
gfh->id = handle;
gfh->fh = fh;
QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
return NULL;
}
-static int guest_file_toggle_flags(int fd, int flags, bool set, Error **err)
-{
- int ret, old_flags;
-
- old_flags = fcntl(fd, F_GETFL);
- if (old_flags == -1) {
- error_set_errno(err, errno, QERR_QGA_COMMAND_FAILED,
- "failed to fetch filehandle flags");
- return -1;
- }
-
- ret = fcntl(fd, F_SETFL, set ? (old_flags | flags) : (old_flags & ~flags));
- if (ret == -1) {
- error_set_errno(err, errno, QERR_QGA_COMMAND_FAILED,
- "failed to set filehandle flags");
- return -1;
- }
-
- return ret;
-}
-
int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode,
Error **errp)
{
/* set fd non-blocking to avoid common use cases (like reading from a
* named pipe) from hanging the agent
*/
- if (guest_file_toggle_flags(fileno(fh), O_NONBLOCK, true, errp) < 0) {
- fclose(fh);
- return -1;
- }
+ qemu_set_nonblock(fileno(fh));
handle = guest_file_handle_add(fh, errp);
if (handle < 0) {
}
fh = gfh->fh;
+
+ /* explicitly flush when switching from writing to reading */
+ if (gfh->state == RW_STATE_WRITING) {
+ int ret = fflush(fh);
+ if (ret == EOF) {
+ error_setg_errno(errp, errno, "failed to flush file");
+ return NULL;
+ }
+ gfh->state = RW_STATE_NEW;
+ }
+
buf = g_malloc0(count+1);
read_count = fread(buf, 1, count, fh);
if (ferror(fh)) {
slog("guest-file-read failed, handle: %" PRId64, handle);
} else {
buf[read_count] = 0;
- read_data = g_malloc0(sizeof(GuestFileRead));
+ read_data = g_new0(GuestFileRead, 1);
read_data->count = read_count;
read_data->eof = feof(fh);
if (read_count) {
read_data->buf_b64 = g_base64_encode(buf, read_count);
}
+ gfh->state = RW_STATE_READING;
}
g_free(buf);
clearerr(fh);
}
fh = gfh->fh;
- buf = g_base64_decode(buf_b64, &buf_len);
+
+ if (gfh->state == RW_STATE_READING) {
+ int ret = fseek(fh, 0, SEEK_CUR);
+ if (ret == -1) {
+ error_setg_errno(errp, errno, "failed to seek file");
+ return NULL;
+ }
+ gfh->state = RW_STATE_NEW;
+ }
+
+ buf = qbase64_decode(buf_b64, -1, &buf_len, errp);
+ if (!buf) {
+ return NULL;
+ }
if (!has_count) {
count = buf_len;
error_setg_errno(errp, errno, "failed to write to file");
slog("guest-file-write failed, handle: %" PRId64, handle);
} else {
- write_data = g_malloc0(sizeof(GuestFileWrite));
+ write_data = g_new0(GuestFileWrite, 1);
write_data->count = write_count;
write_data->eof = feof(fh);
+ gfh->state = RW_STATE_WRITING;
}
g_free(buf);
clearerr(fh);
}
struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
- int64_t whence, Error **errp)
+ GuestFileWhence *whence_code,
+ Error **errp)
{
GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
GuestFileSeek *seek_data = NULL;
FILE *fh;
int ret;
+ int whence;
+ Error *err = NULL;
if (!gfh) {
return NULL;
}
+ /* We stupidly exposed 'whence':'int' in our qapi */
+ whence = ga_parse_whence(whence_code, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return NULL;
+ }
+
fh = gfh->fh;
ret = fseek(fh, offset, whence);
if (ret == -1) {
error_setg_errno(errp, errno, "failed to seek file");
+ if (errno == ESPIPE) {
+ /* file is non-seekable, stdio shouldn't be buffering anyways */
+ gfh->state = RW_STATE_NEW;
+ }
} else {
seek_data = g_new0(GuestFileSeek, 1);
seek_data->position = ftell(fh);
seek_data->eof = feof(fh);
+ gfh->state = RW_STATE_NEW;
}
clearerr(fh);
ret = fflush(fh);
if (ret == EOF) {
error_setg_errno(errp, errno, "failed to flush file");
+ } else {
+ gfh->state = RW_STATE_NEW;
}
}
-static void guest_file_init(void)
-{
- QTAILQ_INIT(&guest_file_state.filehandles);
-}
-
/* linux-specific implementations. avoid this if at all possible. */
#if defined(__linux__)
continue;
}
- mount = g_malloc0(sizeof(FsMount));
+ mount = g_new0(FsMount, 1);
mount->dirname = g_strdup(ment->mnt_dir);
mount->devtype = g_strdup(ment->mnt_type);
mount->devmajor = devmajor;
}
}
- mount = g_malloc0(sizeof(FsMount));
+ mount = g_new0(FsMount, 1);
mount->dirname = g_strdup(line + dir_s);
mount->devtype = g_strdup(dash + type_s);
mount->devmajor = devmajor;
dirpath = g_strdup_printf("%s/slaves", syspath);
dir = opendir(dirpath);
if (!dir) {
- error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
+ if (errno != ENOENT) {
+ error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
+ }
g_free(dirpath);
return;
}
goto error;
}
- /* we try to cull filesytems we know won't work in advance, but other
- * filesytems may not implement fsfreeze for less obvious reasons.
+ /* 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
*/
ret = ioctl(fd, FIFREEZE);
if (ret == -1) {
- if (errno != EOPNOTSUPP) {
+ if (errno != EOPNOTSUPP && errno != EBUSY) {
error_setg_errno(errp, errno, "failed to freeze %s",
mount->dirname);
close(fd);
/*
* Walk list of mounted file systems in the guest, and trim them.
*/
-void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
+GuestFilesystemTrimResponse *
+qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
{
+ GuestFilesystemTrimResponse *response;
+ GuestFilesystemTrimResultList *list;
+ GuestFilesystemTrimResult *result;
int ret = 0;
FsMountList mounts;
struct FsMount *mount;
int fd;
Error *local_err = NULL;
- struct fstrim_range r = {
- .start = 0,
- .len = -1,
- .minlen = has_minimum ? minimum : 0,
- };
+ struct fstrim_range r;
slog("guest-fstrim called");
build_fs_mount_list(&mounts, &local_err);
if (local_err) {
error_propagate(errp, local_err);
- return;
+ return NULL;
}
+ response = g_malloc0(sizeof(*response));
+
QTAILQ_FOREACH(mount, &mounts, next) {
+ result = g_malloc0(sizeof(*result));
+ result->path = g_strdup(mount->dirname);
+
+ list = g_malloc0(sizeof(*list));
+ list->value = result;
+ list->next = response->paths;
+ response->paths = list;
+
fd = qemu_open(mount->dirname, O_RDONLY);
if (fd == -1) {
- error_setg_errno(errp, errno, "failed to open %s", mount->dirname);
- goto error;
+ result->error = g_strdup_printf("failed to open: %s",
+ strerror(errno));
+ result->has_error = true;
+ continue;
}
- /* We try to cull filesytems we know won't work in advance, but other
- * filesytems may not implement fstrim for less obvious reasons. These
- * will report EOPNOTSUPP; we simply ignore these errors. Any other
- * error means an unexpected error, so return it in those cases. In
- * some other cases ENOTTY will be reported (e.g. CD-ROMs).
+ /* 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) {
- if (errno != ENOTTY && errno != EOPNOTSUPP) {
- error_setg_errno(errp, errno, "failed to trim %s",
- mount->dirname);
- close(fd);
- goto error;
+ 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);
}
-error:
free_fs_mount_list(&mounts);
+ return response;
}
#endif /* CONFIG_FSTRIM */
char *chpasswddata = NULL;
size_t chpasswdlen;
- rawpasswddata = (char *)g_base64_decode(password, &rawpasswdlen);
+ rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
+ if (!rawpasswddata) {
+ return;
+ }
rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
rawpasswddata[rawpasswdlen] = '\0';
ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
if (local_err) {
- /* if no 'removable' file, it does't support offline mem blk */
+ /* if no 'removable' file, it doesn't support offline mem blk */
if (errno == ENOENT) {
error_free(local_err);
mem_blk->can_offline = false;
dp = opendir("/sys/devices/system/memory/");
if (!dp) {
- error_setg_errno(errp, errno, "Can't open directory"
- "\"/sys/devices/system/memory/\"\n");
+ /* 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;
}
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);
void qmp_guest_suspend_disk(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
}
void qmp_guest_suspend_ram(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
}
void qmp_guest_suspend_hybrid(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
}
GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return NULL;
}
GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return NULL;
}
int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return -1;
}
bool crypted,
Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
}
GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return NULL;
}
GuestMemoryBlockResponseList *
qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return NULL;
}
GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return NULL;
}
GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return NULL;
}
GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return 0;
}
int64_t qmp_guest_fsfreeze_freeze(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return 0;
}
strList *mountpoints,
Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return 0;
}
int64_t qmp_guest_fsfreeze_thaw(Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
return 0;
}
#endif /* CONFIG_FSFREEZE */
#if !defined(CONFIG_FSTRIM)
-void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
+GuestFilesystemTrimResponse *
+qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
{
- error_set(errp, QERR_UNSUPPORTED);
+ error_setg(errp, QERR_UNSUPPORTED);
+ return NULL;
}
#endif
char **p = (char **)list;
while (*p) {
- blacklist = g_list_append(blacklist, *p++);
+ blacklist = g_list_append(blacklist, g_strdup(*p++));
}
}
#endif
char **p = (char **)list;
while (*p) {
- blacklist = g_list_append(blacklist, *p++);
+ blacklist = g_list_append(blacklist, g_strdup(*p++));
}
}
#endif
#if !defined(CONFIG_FSTRIM)
- blacklist = g_list_append(blacklist, (char *)"guest-fstrim");
+ blacklist = g_list_append(blacklist, g_strdup("guest-fstrim"));
#endif
return blacklist;
#if defined(CONFIG_FSFREEZE)
ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
#endif
- ga_command_state_add(cs, guest_file_init, NULL);
}