From a9de6d01df3153b2ac0cade11e26a66d596d7166 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:01:55 -0200 Subject: [PATCH 01/12] qemu-ga: guest_file_handle_find(): take an Error argument Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth *Fixed missing space character in error message Signed-off-by: Michael Roth --- qga/commands-posix.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index a657201e7a..bbef66dc6a 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -111,7 +111,7 @@ static void guest_file_handle_add(FILE *fh) QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); } -static GuestFileHandle *guest_file_handle_find(int64_t id) +static GuestFileHandle *guest_file_handle_find(int64_t id, Error **err) { GuestFileHandle *gfh; @@ -122,6 +122,7 @@ static GuestFileHandle *guest_file_handle_find(int64_t id) } } + error_setg(err, "handle '%" PRId64 "' has not been found", id); return NULL; } @@ -160,12 +161,11 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E void qmp_guest_file_close(int64_t handle, Error **err) { - GuestFileHandle *gfh = guest_file_handle_find(handle); + GuestFileHandle *gfh = guest_file_handle_find(handle, err); int ret; slog("guest-file-close called, handle: %ld", handle); if (!gfh) { - error_set(err, QERR_FD_NOT_FOUND, "handle"); return; } @@ -182,14 +182,13 @@ void qmp_guest_file_close(int64_t handle, Error **err) struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, int64_t count, Error **err) { - GuestFileHandle *gfh = guest_file_handle_find(handle); + GuestFileHandle *gfh = guest_file_handle_find(handle, err); GuestFileRead *read_data = NULL; guchar *buf; FILE *fh; size_t read_count; if (!gfh) { - error_set(err, QERR_FD_NOT_FOUND, "handle"); return NULL; } @@ -228,11 +227,10 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, guchar *buf; gsize buf_len; int write_count; - GuestFileHandle *gfh = guest_file_handle_find(handle); + GuestFileHandle *gfh = guest_file_handle_find(handle, err); FILE *fh; if (!gfh) { - error_set(err, QERR_FD_NOT_FOUND, "handle"); return NULL; } @@ -265,13 +263,12 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, int64_t whence, Error **err) { - GuestFileHandle *gfh = guest_file_handle_find(handle); + GuestFileHandle *gfh = guest_file_handle_find(handle, err); GuestFileSeek *seek_data = NULL; FILE *fh; int ret; if (!gfh) { - error_set(err, QERR_FD_NOT_FOUND, "handle"); return NULL; } @@ -291,12 +288,11 @@ struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, void qmp_guest_file_flush(int64_t handle, Error **err) { - GuestFileHandle *gfh = guest_file_handle_find(handle); + GuestFileHandle *gfh = guest_file_handle_find(handle, err); FILE *fh; int ret; if (!gfh) { - error_set(err, QERR_FD_NOT_FOUND, "handle"); return; } From 3ac4b7c51e3ba181a86983ba2601a595ed8f3b1d Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:01:56 -0200 Subject: [PATCH 02/12] qemu-ga: qmp_guest_file_close(): fix fclose() error check fclose() returns EOF on error. Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- qga/commands-posix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index bbef66dc6a..ebb58be22e 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -170,7 +170,7 @@ void qmp_guest_file_close(int64_t handle, Error **err) } ret = fclose(gfh->fh); - if (ret == -1) { + if (ret == EOF) { error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed"); return; } From db3edb665549979b44e0376ab9e859f58b89b503 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:01:57 -0200 Subject: [PATCH 03/12] qemu-ga: qmp_guest_file_*: improve error reporting Use error_setg_errno() when possible with an improved error description. Signed-off-by: Luiz Capitulino Signed-off-by: Michael Roth --- qga/commands-posix.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index ebb58be22e..d5f2dcdc09 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -138,7 +138,8 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E slog("guest-file-open called, filepath: %s, mode: %s", path, mode); fh = fopen(path, mode); if (!fh) { - error_set(err, QERR_OPEN_FILE_FAILED, path); + error_setg_errno(err, errno, "failed to open file '%s' (mode: '%s')", + path, mode); return -1; } @@ -149,7 +150,8 @@ int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, E ret = fcntl(fd, F_GETFL); ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK); if (ret == -1) { - error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed"); + error_setg_errno(err, errno, "failed to make file '%s' non-blocking", + path); fclose(fh); return -1; } @@ -171,7 +173,7 @@ void qmp_guest_file_close(int64_t handle, Error **err) ret = fclose(gfh->fh); if (ret == EOF) { - error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed"); + error_setg_errno(err, errno, "failed to close handle"); return; } @@ -195,7 +197,8 @@ struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, if (!has_count) { count = QGA_READ_COUNT_DEFAULT; } else if (count < 0) { - error_set(err, QERR_INVALID_PARAMETER, "count"); + error_setg(err, "value '%" PRId64 "' is invalid for argument count", + count); return NULL; } @@ -203,8 +206,8 @@ struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, buf = g_malloc0(count+1); read_count = fread(buf, 1, count, fh); if (ferror(fh)) { + error_setg_errno(err, errno, "failed to read file"); slog("guest-file-read failed, handle: %ld", handle); - error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed"); } else { buf[read_count] = 0; read_data = g_malloc0(sizeof(GuestFileRead)); @@ -240,15 +243,16 @@ GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, if (!has_count) { count = buf_len; } else if (count < 0 || count > buf_len) { + error_setg(err, "value '%" PRId64 "' is invalid for argument count", + count); g_free(buf); - error_set(err, QERR_INVALID_PARAMETER, "count"); return NULL; } write_count = fwrite(buf, 1, count, fh); if (ferror(fh)) { + error_setg_errno(err, errno, "failed to write to file"); slog("guest-file-write failed, handle: %ld", handle); - error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error"); } else { write_data = g_malloc0(sizeof(GuestFileWrite)); write_data->count = write_count; @@ -275,7 +279,7 @@ struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, fh = gfh->fh; ret = fseek(fh, offset, whence); if (ret == -1) { - error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); + error_setg_errno(err, errno, "failed to seek file"); } else { seek_data = g_malloc0(sizeof(GuestFileRead)); seek_data->position = ftell(fh); @@ -299,7 +303,7 @@ void qmp_guest_file_flush(int64_t handle, Error **err) fh = gfh->fh; ret = fflush(fh); if (ret == EOF) { - error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); + error_setg_errno(err, errno, "failed to flush file"); } } From d220a6dfea10655efe70d37748a3c23cf0a00647 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:01:58 -0200 Subject: [PATCH 04/12] qemu-ga: qmp_guest_shutdown(): improve error reporting Most errors are QERR_UNDEFINED_ERROR. Also, adds ga_wait_child() as a future commit will use it too. Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- qga/commands-posix.c | 52 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index d5f2dcdc09..247e8dc855 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -46,10 +46,29 @@ extern char **environ; #endif #endif +static void ga_wait_child(pid_t pid, int *status, Error **err) +{ + pid_t rpid; + + *status = 0; + + do { + rpid = waitpid(pid, status, 0); + } while (rpid == -1 && errno == EINTR); + + if (rpid == -1) { + error_setg_errno(err, errno, "failed to wait for child (pid: %d)", pid); + return; + } + + g_assert(rpid == pid); +} + void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) { const char *shutdown_flag; - pid_t rpid, pid; + Error *local_err = NULL; + pid_t pid; int status; slog("guest-shutdown called, mode: %s", mode); @@ -60,8 +79,8 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) } else if (strcmp(mode, "reboot") == 0) { shutdown_flag = "-r"; } else { - error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode", - "halt|powerdown|reboot"); + error_setg(err, + "mode is invalid (valid values are: halt|powerdown|reboot"); return; } @@ -77,18 +96,27 @@ void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) "hypervisor initiated shutdown", (char*)NULL, environ); _exit(EXIT_FAILURE); } else if (pid < 0) { - goto exit_err; - } - - do { - rpid = waitpid(pid, &status, 0); - } while (rpid == -1 && errno == EINTR); - if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) { + error_setg_errno(err, errno, "failed to create child process"); return; } -exit_err: - error_set(err, QERR_UNDEFINED_ERROR); + ga_wait_child(pid, &status, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); + return; + } + + if (!WIFEXITED(status)) { + error_setg(err, "child process has terminated abnormally"); + return; + } + + if (WEXITSTATUS(status)) { + error_setg(err, "child process has failed to shutdown"); + return; + } + + /* succeded */ } typedef struct GuestFileHandle { From 261551d1cc3a830e9623971dffa8033b216f1d63 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Thu, 29 Nov 2012 15:29:11 -0200 Subject: [PATCH 05/12] qemu-ga: build_fs_mount_list(): take an Error argument Signed-off-by: Luiz Capitulino Signed-off-by: Michael Roth --- qga/commands-posix.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 247e8dc855..f7b85b284c 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -371,7 +371,7 @@ static void free_fs_mount_list(FsMountList *mounts) /* * Walk the mount table and build a list of local file systems */ -static int build_fs_mount_list(FsMountList *mounts) +static void build_fs_mount_list(FsMountList *mounts, Error **err) { struct mntent *ment; FsMount *mount; @@ -380,8 +380,8 @@ static int build_fs_mount_list(FsMountList *mounts) fp = setmntent(mtab, "r"); if (!fp) { - g_warning("fsfreeze: unable to read mtab"); - return -1; + error_setg(err, "failed to open mtab file: '%s'", mtab); + return; } while ((ment = getmntent(fp))) { @@ -405,8 +405,6 @@ static int build_fs_mount_list(FsMountList *mounts) } endmntent(fp); - - return 0; } #endif @@ -433,15 +431,17 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err) int ret = 0, i = 0; FsMountList mounts; struct FsMount *mount; + Error *local_err = NULL; int fd; char err_msg[512]; slog("guest-fsfreeze called"); QTAILQ_INIT(&mounts); - ret = build_fs_mount_list(&mounts); - if (ret < 0) { - return ret; + build_fs_mount_list(&mounts, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); + return -1; } /* cannot risk guest agent blocking itself on a write in this state */ @@ -498,12 +498,12 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err) FsMountList mounts; FsMount *mount; int fd, i = 0, logged; + Error *local_err = NULL; QTAILQ_INIT(&mounts); - ret = build_fs_mount_list(&mounts); - if (ret) { - error_set(err, QERR_QGA_COMMAND_FAILED, - "failed to enumerate filesystems"); + build_fs_mount_list(&mounts, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); return 0; } @@ -568,6 +568,7 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) FsMountList mounts; struct FsMount *mount; int fd; + Error *local_err = NULL; char err_msg[512]; struct fstrim_range r = { .start = 0, @@ -578,8 +579,9 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) slog("guest-fstrim called"); QTAILQ_INIT(&mounts); - ret = build_fs_mount_list(&mounts); - if (ret < 0) { + build_fs_mount_list(&mounts, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); return; } From 617fbbc13219d26dd71d100d83d617ec8acf5e2d Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:02:00 -0200 Subject: [PATCH 06/12] qemu-ga: qmp_guest_fsfreeze_*(): get rid of sprintf() + error_set() Convert them to error_setg_errno(). Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- qga/commands-posix.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index f7b85b284c..9ad2891d77 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -433,7 +433,6 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err) struct FsMount *mount; Error *local_err = NULL; int fd; - char err_msg[512]; slog("guest-fsfreeze called"); @@ -450,9 +449,7 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err) QTAILQ_FOREACH(mount, &mounts, next) { fd = qemu_open(mount->dirname, O_RDONLY); if (fd == -1) { - sprintf(err_msg, "failed to open %s, %s", mount->dirname, - strerror(errno)); - error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(err, errno, "failed to open %s", mount->dirname); goto error; } @@ -468,9 +465,8 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err) ret = ioctl(fd, FIFREEZE); if (ret == -1) { if (errno != EOPNOTSUPP) { - sprintf(err_msg, "failed to freeze %s, %s", - mount->dirname, strerror(errno)); - error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(err, errno, "failed to freeze %s", + mount->dirname); close(fd); goto error; } From 071673b09021b60eab268653c6bcfba92eea7603 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:02:01 -0200 Subject: [PATCH 07/12] qemu-ga: qmp_guest_fstrim(): get rid of sprintf() + error_set() Convert them to error_setg_errno(). Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- qga/commands-posix.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 9ad2891d77..fa786e53e9 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -565,7 +565,6 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) struct FsMount *mount; int fd; Error *local_err = NULL; - char err_msg[512]; struct fstrim_range r = { .start = 0, .len = -1, @@ -584,9 +583,7 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) QTAILQ_FOREACH(mount, &mounts, next) { fd = qemu_open(mount->dirname, O_RDONLY); if (fd == -1) { - sprintf(err_msg, "failed to open %s, %s", mount->dirname, - strerror(errno)); - error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(err, errno, "failed to open %s", mount->dirname); goto error; } @@ -599,9 +596,8 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) ret = ioctl(fd, FITRIM, &r); if (ret == -1) { if (errno != ENOTTY && errno != EOPNOTSUPP) { - sprintf(err_msg, "failed to trim %s, %s", - mount->dirname, strerror(errno)); - error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(err, errno, "failed to trim %s", + mount->dirname); close(fd); goto error; } From 878a0ae0ab3eb8428626e67995c9efad8eb1ba80 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:02:02 -0200 Subject: [PATCH 08/12] qemu-ga: qmp_guest_network_get_interfaces(): get rid of snprintf() + error_set() Convert them to error_setg_errno(). Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- qga/commands-posix.c | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index fa786e53e9..9b6ef17260 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -802,12 +802,9 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) { GuestNetworkInterfaceList *head = NULL, *cur_item = NULL; struct ifaddrs *ifap, *ifa; - char err_msg[512]; if (getifaddrs(&ifap) < 0) { - snprintf(err_msg, sizeof(err_msg), - "getifaddrs failed: %s", strerror(errno)); - error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(errp, errno, "getifaddrs failed"); goto error; } @@ -843,20 +840,16 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) /* we haven't obtained HW address yet */ sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) { - snprintf(err_msg, sizeof(err_msg), - "failed to create socket: %s", strerror(errno)); - error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(errp, errno, "failed to create socket"); goto error; } memset(&ifr, 0, sizeof(ifr)); pstrcpy(ifr.ifr_name, IF_NAMESIZE, info->value->name); if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) { - snprintf(err_msg, sizeof(err_msg), - "failed to get MAC address of %s: %s", - ifa->ifa_name, - strerror(errno)); - error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(errp, errno, + "failed to get MAC address of %s", + ifa->ifa_name); goto error; } @@ -867,9 +860,7 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) (int) mac_addr[0], (int) mac_addr[1], (int) mac_addr[2], (int) mac_addr[3], (int) mac_addr[4], (int) mac_addr[5]) == -1) { - snprintf(err_msg, sizeof(err_msg), - "failed to format MAC: %s", strerror(errno)); - error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(errp, errno, "failed to format MAC"); goto error; } @@ -884,9 +875,7 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) address_item->value = g_malloc0(sizeof(*address_item->value)); p = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; if (!inet_ntop(AF_INET, p, addr4, sizeof(addr4))) { - snprintf(err_msg, sizeof(err_msg), - "inet_ntop failed : %s", strerror(errno)); - error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(errp, errno, "inet_ntop failed"); goto error; } @@ -906,9 +895,7 @@ GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp) address_item->value = g_malloc0(sizeof(*address_item->value)); p = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; if (!inet_ntop(AF_INET6, p, addr6, sizeof(addr6))) { - snprintf(err_msg, sizeof(err_msg), - "inet_ntop failed : %s", strerror(errno)); - error_set(errp, QERR_QGA_COMMAND_FAILED, err_msg); + error_setg_errno(errp, errno, "inet_ntop failed"); goto error; } From 6b26e837a40a7bed14080fb9029ad6c22409f8b3 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:02:03 -0200 Subject: [PATCH 09/12] qemu-ga: bios_supports_mode(): improve error reporting Most errors are QERR_UNDEFINED_ERROR today. Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- qga/commands-posix.c | 54 +++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 9b6ef17260..f3ee492633 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -618,8 +618,9 @@ error: static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg, const char *sysfile_str, Error **err) { + Error *local_err = NULL; char *pmutils_path; - pid_t pid, rpid; + pid_t pid; int status; pmutils_path = g_find_program_in_path(pmutils_bin); @@ -664,31 +665,38 @@ static void bios_supports_mode(const char *pmutils_bin, const char *pmutils_arg, } _exit(SUSPEND_NOT_SUPPORTED); + } else if (pid < 0) { + error_setg_errno(err, errno, "failed to create child process"); + goto out; } + ga_wait_child(pid, &status, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); + goto out; + } + + if (!WIFEXITED(status)) { + error_setg(err, "child process has terminated abnormally"); + goto out; + } + + switch (WEXITSTATUS(status)) { + case SUSPEND_SUPPORTED: + goto out; + case SUSPEND_NOT_SUPPORTED: + error_setg(err, + "the requested suspend mode is not supported by the guest"); + goto out; + default: + error_setg(err, + "the helper program '%s' returned an unexpected exit status" + " code (%d)", pmutils_path, WEXITSTATUS(status)); + goto out; + } + +out: g_free(pmutils_path); - - if (pid < 0) { - goto undef_err; - } - - do { - rpid = waitpid(pid, &status, 0); - } while (rpid == -1 && errno == EINTR); - if (rpid == pid && WIFEXITED(status)) { - switch (WEXITSTATUS(status)) { - case SUSPEND_SUPPORTED: - return; - case SUSPEND_NOT_SUPPORTED: - error_set(err, QERR_UNSUPPORTED); - return; - default: - goto undef_err; - } - } - -undef_err: - error_set(err, QERR_UNDEFINED_ERROR); } static void guest_suspend(const char *pmutils_bin, const char *sysfile_str, From 7b3760879bf323a0d9654a5158d5b3ed51882505 Mon Sep 17 00:00:00 2001 From: Luiz Capitulino Date: Tue, 27 Nov 2012 11:02:04 -0200 Subject: [PATCH 10/12] qemu-ga: guest_suspend(): improve error reporting Most errors are QERR_UNDEFINED_ERROR today. Signed-off-by: Luiz Capitulino Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- qga/commands-posix.c | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index f3ee492633..614a421ffd 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -702,8 +702,9 @@ out: static void guest_suspend(const char *pmutils_bin, const char *sysfile_str, Error **err) { + Error *local_err = NULL; char *pmutils_path; - pid_t rpid, pid; + pid_t pid; int status; pmutils_path = g_find_program_in_path(pmutils_bin); @@ -741,23 +742,29 @@ static void guest_suspend(const char *pmutils_bin, const char *sysfile_str, } _exit(EXIT_SUCCESS); + } else if (pid < 0) { + error_setg_errno(err, errno, "failed to create child process"); + goto out; } + ga_wait_child(pid, &status, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); + goto out; + } + + if (!WIFEXITED(status)) { + error_setg(err, "child process has terminated abnormally"); + goto out; + } + + if (WEXITSTATUS(status)) { + error_setg(err, "child process has failed to suspend"); + goto out; + } + +out: g_free(pmutils_path); - - if (pid < 0) { - goto exit_err; - } - - do { - rpid = waitpid(pid, &status, 0); - } while (rpid == -1 && errno == EINTR); - if (rpid == pid && WIFEXITED(status) && !WEXITSTATUS(status)) { - return; - } - -exit_err: - error_set(err, QERR_UNDEFINED_ERROR); } void qmp_guest_suspend_disk(Error **err) From ec0f694c11e8e0958d727e40e0759ab99e5908d6 Mon Sep 17 00:00:00 2001 From: Tomoki Sekiyama Date: Wed, 12 Dec 2012 12:55:55 +0900 Subject: [PATCH 11/12] qemu-ga: execute hook to quiesce the guest on fsfreeze-freeze/thaw To use the online disk snapshot for online-backup, application-level consistency of the snapshot image is required. However, currently the guest agent can provide only filesystem-level consistency, and the snapshot may contain dirty data, for example, incomplete transactions. This patch provides the opportunity to quiesce applications before snapshot is taken. If --fsfreeze-hook option is specified, the hook is executed with "freeze" argument before the filesystem is frozen by fsfreeze-freeze command. As for fsfreeze-thaw command, the hook is executed with "thaw" argument after the filesystem is thawed. This patch depends on patchset to improve error reporting by Luiz Capitulino: http://lists.gnu.org/archive/html/qemu-devel/2012-11/msg03016.html Signed-off-by: Tomoki Sekiyama Reviewed-by: Luiz Capitulino *clarified usage in help output Signed-off-by: Michael Roth --- qga/commands-posix.c | 69 ++++++++++++++++++++++++++++++++++++++++++ qga/guest-agent-core.h | 1 + qga/main.c | 48 ++++++++++++++++++++++++++++- 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 614a421ffd..77f6ee7d5f 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -410,6 +410,66 @@ static void build_fs_mount_list(FsMountList *mounts, Error **err) #if defined(CONFIG_FSFREEZE) +typedef enum { + FSFREEZE_HOOK_THAW = 0, + FSFREEZE_HOOK_FREEZE, +} FsfreezeHookArg; + +const char *fsfreeze_hook_arg_string[] = { + "thaw", + "freeze", +}; + +static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **err) +{ + 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(err, 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); + + execle(hook, hook, arg_str, NULL, environ); + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(err, errno, "failed to create child process"); + return; + } + + ga_wait_child(pid, &status, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); + return; + } + + if (!WIFEXITED(status)) { + error_setg(err, "fsfreeze hook has terminated abnormally"); + return; + } + + status = WEXITSTATUS(status); + if (status) { + error_setg(err, "fsfreeze hook has failed with status %d", status); + return; + } +} + /* * Return status of freeze/thaw */ @@ -436,6 +496,12 @@ int64_t qmp_guest_fsfreeze_freeze(Error **err) slog("guest-fsfreeze called"); + execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err); + if (error_is_set(&local_err)) { + error_propagate(err, local_err); + return -1; + } + QTAILQ_INIT(&mounts); build_fs_mount_list(&mounts, &local_err); if (error_is_set(&local_err)) { @@ -537,6 +603,9 @@ int64_t qmp_guest_fsfreeze_thaw(Error **err) ga_unset_frozen(ga_state); free_fs_mount_list(&mounts); + + execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, err); + return i; } diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h index 8934163375..3354598362 100644 --- a/qga/guest-agent-core.h +++ b/qga/guest-agent-core.h @@ -34,6 +34,7 @@ void ga_set_response_delimited(GAState *s); bool ga_is_frozen(GAState *s); void ga_set_frozen(GAState *s); void ga_unset_frozen(GAState *s); +const char *ga_fsfreeze_hook(GAState *s); #ifndef _WIN32 void reopen_fd_to_null(int fd); diff --git a/qga/main.c b/qga/main.c index ba5fa1c778..a9b968c507 100644 --- a/qga/main.c +++ b/qga/main.c @@ -34,6 +34,12 @@ #include "qga/service-win32.h" #include #endif +#ifdef __linux__ +#include +#ifdef FIFREEZE +#define CONFIG_FSFREEZE +#endif +#endif #ifndef _WIN32 #define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0" @@ -42,6 +48,9 @@ #endif #define QGA_STATEDIR_DEFAULT CONFIG_QEMU_LOCALSTATEDIR "/run" #define QGA_PIDFILE_DEFAULT QGA_STATEDIR_DEFAULT "/qemu-ga.pid" +#ifdef CONFIG_FSFREEZE +#define QGA_FSFREEZE_HOOK_DEFAULT CONFIG_QEMU_CONFDIR "/fsfreeze-hook" +#endif #define QGA_SENTINEL_BYTE 0xFF struct GAState { @@ -64,6 +73,9 @@ struct GAState { const char *log_filepath; const char *pid_filepath; } deferred_options; +#ifdef CONFIG_FSFREEZE + const char *fsfreeze_hook; +#endif }; struct GAState *ga_state; @@ -153,6 +165,16 @@ static void usage(const char *cmd) " %s)\n" " -l, --logfile set logfile path, logs to stderr by default\n" " -f, --pidfile specify pidfile (default is %s)\n" +#ifdef CONFIG_FSFREEZE +" -F, --fsfreeze-hook\n" +" enable fsfreeze hook. Accepts an optional argument that\n" +" specifies script to run on freeze/thaw. Script will be\n" +" called with 'freeze'/'thaw' arguments accordingly.\n" +" (default is %s)\n" +" If using -F with an argument, do not follow -F with a\n" +" space.\n" +" (for example: -F/var/run/fsfreezehook.sh)\n" +#endif " -t, --statedir specify dir to store state information (absolute paths\n" " only, default is %s)\n" " -v, --verbose log extra debugging information\n" @@ -167,6 +189,9 @@ static void usage(const char *cmd) "\n" "Report bugs to \n" , cmd, QEMU_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT, +#ifdef CONFIG_FSFREEZE + QGA_FSFREEZE_HOOK_DEFAULT, +#endif QGA_STATEDIR_DEFAULT); } @@ -401,6 +426,13 @@ void ga_unset_frozen(GAState *s) } } +#ifdef CONFIG_FSFREEZE +const char *ga_fsfreeze_hook(GAState *s) +{ + return s->fsfreeze_hook; +} +#endif + static void become_daemon(const char *pidfile) { #ifndef _WIN32 @@ -678,10 +710,13 @@ VOID WINAPI service_main(DWORD argc, TCHAR *argv[]) int main(int argc, char **argv) { - const char *sopt = "hVvdm:p:l:f:b:s:t:"; + const char *sopt = "hVvdm:p:l:f:F::b:s:t:"; const char *method = NULL, *path = NULL; const char *log_filepath = NULL; const char *pid_filepath = QGA_PIDFILE_DEFAULT; +#ifdef CONFIG_FSFREEZE + const char *fsfreeze_hook = NULL; +#endif const char *state_dir = QGA_STATEDIR_DEFAULT; #ifdef _WIN32 const char *service = NULL; @@ -691,6 +726,9 @@ int main(int argc, char **argv) { "version", 0, NULL, 'V' }, { "logfile", 1, NULL, 'l' }, { "pidfile", 1, NULL, 'f' }, +#ifdef CONFIG_FSFREEZE + { "fsfreeze-hook", 2, NULL, 'F' }, +#endif { "verbose", 0, NULL, 'v' }, { "method", 1, NULL, 'm' }, { "path", 1, NULL, 'p' }, @@ -723,6 +761,11 @@ int main(int argc, char **argv) case 'f': pid_filepath = optarg; break; +#ifdef CONFIG_FSFREEZE + case 'F': + fsfreeze_hook = optarg ? optarg : QGA_FSFREEZE_HOOK_DEFAULT; + break; +#endif case 't': state_dir = optarg; break; @@ -786,6 +829,9 @@ int main(int argc, char **argv) s = g_malloc0(sizeof(GAState)); s->log_level = log_level; s->log_file = stderr; +#ifdef CONFIG_FSFREEZE + s->fsfreeze_hook = fsfreeze_hook; +#endif g_log_set_default_handler(ga_log, s); g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); ga_enable_logging(s); From 96610da210697a1f33669d8bec0cb7b944d3a516 Mon Sep 17 00:00:00 2001 From: Tomoki Sekiyama Date: Wed, 12 Dec 2012 12:55:57 +0900 Subject: [PATCH 12/12] qemu-ga: sample fsfreeze hooks Adds sample hook scripts for --fsfreeze-hook option of qemu-ga. - fsfreeze-hook : execute scripts in fsfreeze-hook.d/ - fsfreeze-hook.d/mysql-flush.sh.sample : quiesce MySQL before snapshot Signed-off-by: Tomoki Sekiyama Reviewed-by: Michael Roth Signed-off-by: Michael Roth --- .gitignore | 1 + Makefile | 2 +- scripts/qemu-guest-agent/fsfreeze-hook | 33 +++++++++++ .../fsfreeze-hook.d/mysql-flush.sh.sample | 56 +++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100755 scripts/qemu-guest-agent/fsfreeze-hook create mode 100755 scripts/qemu-guest-agent/fsfreeze-hook.d/mysql-flush.sh.sample diff --git a/.gitignore b/.gitignore index 0e3816918c..5fea65dc14 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ fsdev/virtfs-proxy-helper.pod *.tp *.vr *.d +!scripts/qemu-guest-agent/fsfreeze-hook.d *.o *.lo *.la diff --git a/Makefile b/Makefile index a7ac04b9ae..ee06448e28 100644 --- a/Makefile +++ b/Makefile @@ -232,7 +232,7 @@ clean: # avoid old build problems by removing potentially incorrect old files rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h rm -f qemu-options.def - find . -name '*.[od]' -exec rm -f {} + + find . -name '*.[od]' -type f -exec rm -f {} + rm -f *.a *.lo $(TOOLS) $(HELPERS-y) qemu-ga TAGS cscope.* *.pod *~ */*~ rm -Rf .libs rm -f qemu-img-cmds.h diff --git a/scripts/qemu-guest-agent/fsfreeze-hook b/scripts/qemu-guest-agent/fsfreeze-hook new file mode 100755 index 0000000000..c27b29f282 --- /dev/null +++ b/scripts/qemu-guest-agent/fsfreeze-hook @@ -0,0 +1,33 @@ +#!/bin/sh + +# This script is executed when a guest agent receives fsfreeze-freeze and +# fsfreeze-thaw command, if it is specified in --fsfreeze-hook (-F) +# option of qemu-ga or placed in default path (/etc/qemu/fsfreeze-hook). +# When the agent receives fsfreeze-freeze request, this script is issued with +# "freeze" argument before the filesystem is frozen. And for fsfreeze-thaw +# request, it is issued with "thaw" argument after filesystem is thawed. + +LOGFILE=/var/log/qga-fsfreeze-hook.log +FSFREEZE_D=$(dirname -- "$0")/fsfreeze-hook.d + +# Check whether file $1 is a backup or rpm-generated file and should be ignored +is_ignored_file() { + case "$1" in + *~ | *.bak | *.orig | *.rpmnew | *.rpmorig | *.rpmsave | *.sample) + return 0 ;; + esac + return 1 +} + +# Iterate executables in directory "fsfreeze-hook.d" with the specified args +[ ! -d "$FSFREEZE_D" ] && exit 0 +for file in "$FSFREEZE_D"/* ; do + is_ignored_file "$file" && continue + [ -x "$file" ] || continue + printf "$(date): execute $file $@\n" >>$LOGFILE + "$file" "$@" >>$LOGFILE 2>&1 + STATUS=$? + printf "$(date): $file finished with status=$STATUS\n" >>$LOGFILE +done + +exit 0 diff --git a/scripts/qemu-guest-agent/fsfreeze-hook.d/mysql-flush.sh.sample b/scripts/qemu-guest-agent/fsfreeze-hook.d/mysql-flush.sh.sample new file mode 100755 index 0000000000..2b4fa3aeb1 --- /dev/null +++ b/scripts/qemu-guest-agent/fsfreeze-hook.d/mysql-flush.sh.sample @@ -0,0 +1,56 @@ +#!/bin/sh + +# Flush MySQL tables to the disk before the filesystem is frozen. +# At the same time, this keeps a read lock in order to avoid write accesses +# from the other clients until the filesystem is thawed. + +MYSQL="/usr/bin/mysql" +MYSQL_OPTS="-uroot" #"-prootpassword" +FIFO=/var/run/mysql-flush.fifo + +# Check mysql is installed and the server running +[ -x "$MYSQL" ] && "$MYSQL" $MYSQL_OPTS < /dev/null || exit 0 + +flush_and_wait() { + printf "FLUSH TABLES WITH READ LOCK \\G\n" + trap 'printf "$(date): $0 is killed\n">&2' HUP INT QUIT ALRM TERM + read < $FIFO + printf "UNLOCK TABLES \\G\n" + rm -f $FIFO +} + +case "$1" in + freeze) + mkfifo $FIFO || exit 1 + flush_and_wait | "$MYSQL" $MYSQL_OPTS & + # wait until every block is flushed + while [ "$(echo 'SHOW STATUS LIKE "Key_blocks_not_flushed"' |\ + "$MYSQL" $MYSQL_OPTS | tail -1 | cut -f 2)" -gt 0 ]; do + sleep 1 + done + # for InnoDB, wait until every log is flushed + INNODB_STATUS=$(mktemp /tmp/mysql-flush.XXXXXX) + [ $? -ne 0 ] && exit 2 + trap "rm -f $INNODB_STATUS; exit 1" HUP INT QUIT ALRM TERM + while :; do + printf "SHOW ENGINE INNODB STATUS \\G" |\ + "$MYSQL" $MYSQL_OPTS > $INNODB_STATUS + LOG_CURRENT=$(grep 'Log sequence number' $INNODB_STATUS |\ + tr -s ' ' | cut -d' ' -f4) + LOG_FLUSHED=$(grep 'Log flushed up to' $INNODB_STATUS |\ + tr -s ' ' | cut -d' ' -f5) + [ "$LOG_CURRENT" = "$LOG_FLUSHED" ] && break + sleep 1 + done + rm -f $INNODB_STATUS + ;; + + thaw) + [ ! -p $FIFO ] && exit 1 + echo > $FIFO + ;; + + *) + exit 1 + ;; +esac