guest agent: add guest agent RPCs/commands
This adds the initial set of QMP/QAPI commands provided by the guest
agent:
guest-sync
guest-ping
guest-info
guest-shutdown
guest-file-open
guest-file-read
guest-file-write
guest-file-seek
guest-file-flush
guest-file-close
guest-fsfreeze-freeze
guest-fsfreeze-thaw
guest-fsfreeze-status
The input/output specification for these commands are documented in the
schema.
Example usage:
  host:
    qemu -device virtio-serial \
         -chardev socket,path=/tmp/vs0.sock,server,nowait,id=qga0 \
         -device virtserialport,chardev=qga0,name=org.qemu.quest_agent.0
         ...
    echo "{'execute':'guest-info'}" | socat stdio unix-connect:/tmp/qga0.sock
  guest:
    qemu-ga -m virtio-serial -p /dev/virtio-ports/org.qemu.guest_agent.0 \
            -p /var/run/qemu-guest-agent.pid -d
Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
Signed-off-by: Luiz Capitulino <lcapitulino@gmail.com>
			
			
This commit is contained in:
		
				
					committed by
					
						 Luiz Capitulino
						Luiz Capitulino
					
				
			
			
				
	
			
			
			
						parent
						
							48ff7a625b
						
					
				
				
					commit
					e3d4d25206
				
			
							
								
								
									
										16
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Makefile
									
									
									
									
									
								
							| @@ -164,7 +164,7 @@ check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjs | ||||
|  | ||||
| $(qapi-obj-y): $(GENERATED_HEADERS) | ||||
| qapi-dir := qapi-generated | ||||
| test-visitor.o test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir) | ||||
| test-visitor.o test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS += -I $(qapi-dir) | ||||
|  | ||||
| $(qapi-dir)/test-qapi-types.c: $(qapi-dir)/test-qapi-types.h | ||||
| $(qapi-dir)/test-qapi-types.h: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py | ||||
| @@ -176,15 +176,25 @@ $(qapi-dir)/test-qmp-commands.h: $(qapi-dir)/test-qmp-marshal.c | ||||
| $(qapi-dir)/test-qmp-marshal.c: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py | ||||
| 	    $(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "test-" < $<, "  GEN   $@") | ||||
|  | ||||
| $(qapi-dir)/qga-qapi-types.c: $(qapi-dir)/qga-qapi-types.h | ||||
| $(qapi-dir)/qga-qapi-types.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-types.py | ||||
| 	$(call quiet-command,python $(SRC_PATH)/scripts/qapi-types.py -o "$(qapi-dir)" -p "qga-" < $<, "  GEN   $@") | ||||
| $(qapi-dir)/qga-qapi-visit.c: $(qapi-dir)/qga-qapi-visit.h | ||||
| $(qapi-dir)/qga-qapi-visit.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-visit.py | ||||
| 	$(call quiet-command,python $(SRC_PATH)/scripts/qapi-visit.py -o "$(qapi-dir)" -p "qga-" < $<, "  GEN   $@") | ||||
| $(qapi-dir)/qga-qmp-marshal.c: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-commands.py | ||||
| 	$(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "qga-" < $<, "  GEN   $@") | ||||
|  | ||||
| test-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y) | ||||
| test-visitor: test-visitor.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o $(qapi-obj-y) error.o osdep.o qemu-malloc.o $(oslib-obj-y) qjson.o json-streamer.o json-lexer.o json-parser.o qerror.o qemu-error.o qemu-tool.o $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o | ||||
|  | ||||
| test-qmp-commands.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h test-qmp-marshal.c test-qmp-commands.h) $(qapi-obj-y) | ||||
| test-qmp-commands: test-qmp-commands.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o $(qapi-obj-y) error.o osdep.o qemu-malloc.o $(oslib-obj-y) qjson.o json-streamer.o json-lexer.o json-parser.o qerror.o qemu-error.o qemu-tool.o $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o $(qapi-dir)/test-qmp-marshal.o module.o | ||||
|  | ||||
| QGALIB=qga/guest-agent-command-state.o | ||||
| QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.o | ||||
|  | ||||
| qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) qemu-tool.o qemu-error.o error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) $(qapi-obj-y) qemu-timer-common.o qemu-sockets.o module.o qapi/qmp-dispatch.o qapi/qmp-registry.o | ||||
| qemu-ga.o: $(addprefix $(qapi-dir)/, qga-qapi-types.c qga-qapi-types.h qga-qapi-visit.c qga-qmp-marshal.c) $(qapi-obj-y) | ||||
| qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) qemu-tool.o qemu-error.o error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) $(qapi-obj-y) qemu-timer-common.o qemu-sockets.o module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qapi-types.o $(qapi-dir)/qga-qmp-marshal.o | ||||
|  | ||||
| QEMULIBS=libhw32 libhw64 libuser libdis libdis-user | ||||
|  | ||||
|   | ||||
							
								
								
									
										217
									
								
								qapi-schema-guest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								qapi-schema-guest.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | ||||
| # *-*- Mode: Python -*-* | ||||
|  | ||||
| ## | ||||
| # @guest-sync: | ||||
| # | ||||
| # Echo back a unique integer value | ||||
| # | ||||
| # This is used by clients talking to the guest agent over the | ||||
| # wire to ensure the stream is in sync and doesn't contain stale | ||||
| # data from previous client. All guest agent responses should be | ||||
| # ignored until the provided unique integer value is returned, | ||||
| # and it is up to the client to handle stale whole or | ||||
| # partially-delivered JSON text in such a way that this response | ||||
| # can be obtained. | ||||
| # | ||||
| # Such clients should also preceed this command | ||||
| # with a 0xFF byte to make such the guest agent flushes any | ||||
| # partially read JSON data from a previous session. | ||||
| # | ||||
| # @id: randomly generated 64-bit integer | ||||
| # | ||||
| # Returns: The unique integer id passed in by the client | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-sync' | ||||
|   'data':    { 'id': 'int' }, | ||||
|   'returns': 'int' } | ||||
|  | ||||
| ## | ||||
| # @guest-ping: | ||||
| # | ||||
| # Ping the guest agent, a non-error return implies success | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-ping' } | ||||
|  | ||||
| ## | ||||
| # @guest-info: | ||||
| # | ||||
| # Get some information about the guest agent. | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'type': 'GuestAgentInfo', 'data': {'version': 'str'} } | ||||
| { 'command': 'guest-info', | ||||
|   'returns': 'GuestAgentInfo' } | ||||
|  | ||||
| ## | ||||
| # @guest-shutdown: | ||||
| # | ||||
| # Initiate guest-activated shutdown. Note: this is an asynchronous | ||||
| # shutdown request, with no guaruntee of successful shutdown. Errors | ||||
| # will be logged to guest's syslog. | ||||
| # | ||||
| # @mode: #optional "halt", "powerdown" (default), or "reboot" | ||||
| # | ||||
| # Returns: Nothing on success | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-shutdown', 'data': { '*mode': 'str' } } | ||||
|  | ||||
| ## | ||||
| # @guest-file-open: | ||||
| # | ||||
| # Open a file in the guest and retrieve a file handle for it | ||||
| # | ||||
| # @filepath: Full path to the file in the guest to open. | ||||
| # | ||||
| # @mode: #optional open mode, as per fopen(), "r" is the default. | ||||
| # | ||||
| # Returns: Guest file handle on success. | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-file-open', | ||||
|   'data':    { 'path': 'str', '*mode': 'str' }, | ||||
|   'returns': 'int' } | ||||
|  | ||||
| ## | ||||
| # @guest-file-close: | ||||
| # | ||||
| # Close an open file in the guest | ||||
| # | ||||
| # @handle: filehandle returned by guest-file-open | ||||
| # | ||||
| # Returns: Nothing on success. | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-file-close', | ||||
|   'data': { 'handle': 'int' } } | ||||
|  | ||||
| ## | ||||
| # @guest-file-read: | ||||
| # | ||||
| # Read from an open file in the guest. Data will be base64-encoded | ||||
| # | ||||
| # @handle: filehandle returned by guest-file-open | ||||
| # | ||||
| # @count: #optional maximum number of bytes to read (default is 4KB) | ||||
| # | ||||
| # Returns: GuestFileRead on success. Note: count is number of bytes read | ||||
| #          *before* base64 encoding bytes read. | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'type': 'GuestFileRead', | ||||
|   'data': { 'count': 'int', 'buf-b64': 'str', 'eof': 'bool' } } | ||||
|  | ||||
| { 'command': 'guest-file-read', | ||||
|   'data':    { 'handle': 'int', '*count': 'int' }, | ||||
|   'returns': 'GuestFileRead' } | ||||
|  | ||||
| ## | ||||
| # @guest-file-write: | ||||
| # | ||||
| # Write to an open file in the guest. | ||||
| # | ||||
| # @handle: filehandle returned by guest-file-open | ||||
| # | ||||
| # @buf-b64: base64-encoded string representing data to be written | ||||
| # | ||||
| # @count: #optional bytes to write (actual bytes, after base64-decode), | ||||
| #         default is all content in buf-b64 buffer after base64 decoding | ||||
| # | ||||
| # Returns: GuestFileWrite on success. Note: count is the number of bytes | ||||
| #          base64-decoded bytes written | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'type': 'GuestFileWrite', | ||||
|   'data': { 'count': 'int', 'eof': 'bool' } } | ||||
| { 'command': 'guest-file-write', | ||||
|   'data':    { 'handle': 'int', 'buf-b64': 'str', '*count': 'int' }, | ||||
|   'returns': 'GuestFileWrite' } | ||||
|  | ||||
| ## | ||||
| # @guest-file-seek: | ||||
| # | ||||
| # Seek to a position in the file, as with fseek(), and return the | ||||
| # current file position afterward. Also encapsulates ftell()'s | ||||
| # functionality, just Set offset=0, whence=SEEK_CUR. | ||||
| # | ||||
| # @handle: filehandle returned by guest-file-open | ||||
| # | ||||
| # @offset: bytes to skip over in the file stream | ||||
| # | ||||
| # @whence: SEEK_SET, SEEK_CUR, or SEEK_END, as with fseek() | ||||
| # | ||||
| # Returns: GuestFileSeek on success. | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'type': 'GuestFileSeek', | ||||
|   'data': { 'position': 'int', 'eof': 'bool' } } | ||||
|  | ||||
| { 'command': 'guest-file-seek', | ||||
|   'data':    { 'handle': 'int', 'offset': 'int', 'whence': 'int' }, | ||||
|   'returns': 'GuestFileSeek' } | ||||
|  | ||||
| ## | ||||
| # @guest-file-flush: | ||||
| # | ||||
| # Write file changes bufferred in userspace to disk/kernel buffers | ||||
| # | ||||
| # @handle: filehandle returned by guest-file-open | ||||
| # | ||||
| # Returns: Nothing on success. | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-file-flush', | ||||
|   'data': { 'handle': 'int' } } | ||||
|  | ||||
| ## | ||||
| # @guest-fsfreeze-status: | ||||
| # | ||||
| # Get guest fsfreeze state. error state indicates failure to thaw 1 or more | ||||
| # previously frozen filesystems, or failure to open a previously cached | ||||
| # filesytem (filesystem unmounted/directory changes, etc). | ||||
| # | ||||
| # Returns: GuestFsfreezeStatus ("thawed", "frozen", etc., as defined below) | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'enum': 'GuestFsfreezeStatus', | ||||
|   'data': [ 'thawed', 'frozen', 'error' ] } | ||||
| { 'command': 'guest-fsfreeze-status', | ||||
|   'returns': 'GuestFsfreezeStatus' } | ||||
|  | ||||
| ## | ||||
| # @guest-fsfreeze-freeze: | ||||
| # | ||||
| # Sync and freeze all non-network guest filesystems | ||||
| # | ||||
| # Returns: Number of file systems frozen on success | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-fsfreeze-freeze', | ||||
|   'returns': 'int' } | ||||
|  | ||||
| ## | ||||
| # @guest-fsfreeze-thaw: | ||||
| # | ||||
| # Unfreeze frozen guest fileystems | ||||
| # | ||||
| # Returns: Number of file systems thawed | ||||
| #          If error, -1 (unknown error) or -errno | ||||
| # | ||||
| # Since: 0.15.0 | ||||
| ## | ||||
| { 'command': 'guest-fsfreeze-thaw', | ||||
|   'returns': 'int' } | ||||
| @@ -636,6 +636,9 @@ int main(int argc, char **argv) | ||||
|     g_log_set_default_handler(ga_log, s); | ||||
|     g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); | ||||
|     s->logging_enabled = true; | ||||
|     s->command_state = ga_command_state_new(); | ||||
|     ga_command_state_init(s, s->command_state); | ||||
|     ga_command_state_init_all(s->command_state); | ||||
|     ga_state = s; | ||||
|  | ||||
|     module_call_init(MODULE_INIT_QAPI); | ||||
| @@ -644,6 +647,7 @@ int main(int argc, char **argv) | ||||
|  | ||||
|     g_main_loop_run(ga_state->main_loop); | ||||
|  | ||||
|     ga_command_state_cleanup_all(ga_state->command_state); | ||||
|     unlink(pidfile); | ||||
|  | ||||
|     return 0; | ||||
|   | ||||
							
								
								
									
										8
									
								
								qerror.c
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								qerror.c
									
									
									
									
									
								
							| @@ -218,6 +218,14 @@ static const QErrorStringTable qerror_table[] = { | ||||
|         .error_fmt = QERR_VNC_SERVER_FAILED, | ||||
|         .desc      = "Could not start VNC server on %(target)", | ||||
|     }, | ||||
|     { | ||||
|         .error_fmt = QERR_QGA_LOGGING_FAILED, | ||||
|         .desc      = "Guest agent failed to log non-optional log statement", | ||||
|     }, | ||||
|     { | ||||
|         .error_fmt = QERR_QGA_COMMAND_FAILED, | ||||
|         .desc      = "Guest agent command failed, error was '%(message)'", | ||||
|     }, | ||||
|     {} | ||||
| }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										6
									
								
								qerror.h
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								qerror.h
									
									
									
									
									
								
							| @@ -184,4 +184,10 @@ QError *qobject_to_qerror(const QObject *obj); | ||||
| #define QERR_FEATURE_DISABLED \ | ||||
|     "{ 'class': 'FeatureDisabled', 'data': { 'name': %s } }" | ||||
|  | ||||
| #define QERR_QGA_LOGGING_FAILED \ | ||||
|     "{ 'class': 'QgaLoggingFailed', 'data': {} }" | ||||
|  | ||||
| #define QERR_QGA_COMMAND_FAILED \ | ||||
|     "{ 'class': 'QgaCommandFailed', 'data': { 'message': %s } }" | ||||
|  | ||||
| #endif /* QERROR_H */ | ||||
|   | ||||
							
								
								
									
										518
									
								
								qga/guest-agent-commands.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										518
									
								
								qga/guest-agent-commands.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,518 @@ | ||||
| /* | ||||
|  * QEMU Guest Agent commands | ||||
|  * | ||||
|  * Copyright IBM Corp. 2011 | ||||
|  * | ||||
|  * Authors: | ||||
|  *  Michael Roth      <mdroth@linux.vnet.ibm.com> | ||||
|  * | ||||
|  * 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 <glib.h> | ||||
| #include <mntent.h> | ||||
| #include <sys/types.h> | ||||
| #include <sys/ioctl.h> | ||||
| #include <linux/fs.h> | ||||
| #include "qga/guest-agent-core.h" | ||||
| #include "qga-qmp-commands.h" | ||||
| #include "qerror.h" | ||||
| #include "qemu-queue.h" | ||||
|  | ||||
| static GAState *ga_state; | ||||
|  | ||||
| static void disable_logging(void) | ||||
| { | ||||
|     ga_disable_logging(ga_state); | ||||
| } | ||||
|  | ||||
| static void enable_logging(void) | ||||
| { | ||||
|     ga_enable_logging(ga_state); | ||||
| } | ||||
|  | ||||
| /* Note: in some situations, like with the fsfreeze, logging may be | ||||
|  * temporarilly disabled. if it is necessary that a command be able | ||||
|  * to log for accounting purposes, check ga_logging_enabled() beforehand, | ||||
|  * and use the QERR_QGA_LOGGING_DISABLED to generate an error | ||||
|  */ | ||||
| static void slog(const char *fmt, ...) | ||||
| { | ||||
|     va_list ap; | ||||
|  | ||||
|     va_start(ap, fmt); | ||||
|     g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap); | ||||
|     va_end(ap); | ||||
| } | ||||
|  | ||||
| int64_t qmp_guest_sync(int64_t id, Error **errp) | ||||
| { | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| void qmp_guest_ping(Error **err) | ||||
| { | ||||
|     slog("guest-ping called"); | ||||
| } | ||||
|  | ||||
| struct GuestAgentInfo *qmp_guest_info(Error **err) | ||||
| { | ||||
|     GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo)); | ||||
|  | ||||
|     info->version = g_strdup(QGA_VERSION); | ||||
|  | ||||
|     return info; | ||||
| } | ||||
|  | ||||
| void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) | ||||
| { | ||||
|     int ret; | ||||
|     const char *shutdown_flag; | ||||
|  | ||||
|     slog("guest-shutdown called, mode: %s", mode); | ||||
|     if (!has_mode || strcmp(mode, "powerdown") == 0) { | ||||
|         shutdown_flag = "-P"; | ||||
|     } else if (strcmp(mode, "halt") == 0) { | ||||
|         shutdown_flag = "-H"; | ||||
|     } else if (strcmp(mode, "reboot") == 0) { | ||||
|         shutdown_flag = "-r"; | ||||
|     } else { | ||||
|         error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode", | ||||
|                   "halt|powerdown|reboot"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ret = fork(); | ||||
|     if (ret == 0) { | ||||
|         /* child, start the shutdown */ | ||||
|         setsid(); | ||||
|         fclose(stdin); | ||||
|         fclose(stdout); | ||||
|         fclose(stderr); | ||||
|  | ||||
|         ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", | ||||
|                     "hypervisor initiated shutdown", (char*)NULL); | ||||
|         if (ret) { | ||||
|             slog("guest-shutdown failed: %s", strerror(errno)); | ||||
|         } | ||||
|         exit(!!ret); | ||||
|     } else if (ret < 0) { | ||||
|         error_set(err, QERR_UNDEFINED_ERROR); | ||||
|     } | ||||
| } | ||||
|  | ||||
| typedef struct GuestFileHandle { | ||||
|     uint64_t id; | ||||
|     FILE *fh; | ||||
|     QTAILQ_ENTRY(GuestFileHandle) next; | ||||
| } GuestFileHandle; | ||||
|  | ||||
| static struct { | ||||
|     QTAILQ_HEAD(, GuestFileHandle) filehandles; | ||||
| } guest_file_state; | ||||
|  | ||||
| static void guest_file_handle_add(FILE *fh) | ||||
| { | ||||
|     GuestFileHandle *gfh; | ||||
|  | ||||
|     gfh = qemu_mallocz(sizeof(GuestFileHandle)); | ||||
|     gfh->id = fileno(fh); | ||||
|     gfh->fh = fh; | ||||
|     QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); | ||||
| } | ||||
|  | ||||
| static GuestFileHandle *guest_file_handle_find(int64_t id) | ||||
| { | ||||
|     GuestFileHandle *gfh; | ||||
|  | ||||
|     QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) | ||||
|     { | ||||
|         if (gfh->id == id) { | ||||
|             return gfh; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err) | ||||
| { | ||||
|     FILE *fh; | ||||
|     int fd; | ||||
|     int64_t ret = -1; | ||||
|  | ||||
|     if (!has_mode) { | ||||
|         mode = "r"; | ||||
|     } | ||||
|     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); | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     /* set fd non-blocking to avoid common use cases (like reading from a | ||||
|      * named pipe) from hanging the agent | ||||
|      */ | ||||
|     fd = fileno(fh); | ||||
|     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"); | ||||
|         fclose(fh); | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     guest_file_handle_add(fh); | ||||
|     slog("guest-file-open, handle: %d", fd); | ||||
|     return fd; | ||||
| } | ||||
|  | ||||
| void qmp_guest_file_close(int64_t handle, Error **err) | ||||
| { | ||||
|     GuestFileHandle *gfh = guest_file_handle_find(handle); | ||||
|     int ret; | ||||
|  | ||||
|     slog("guest-file-close called, handle: %ld", handle); | ||||
|     if (!gfh) { | ||||
|         error_set(err, QERR_FD_NOT_FOUND, "handle"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     ret = fclose(gfh->fh); | ||||
|     if (ret == -1) { | ||||
|         error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next); | ||||
|     qemu_free(gfh); | ||||
| } | ||||
|  | ||||
| struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, | ||||
|                                           int64_t count, Error **err) | ||||
| { | ||||
|     GuestFileHandle *gfh = guest_file_handle_find(handle); | ||||
|     GuestFileRead *read_data = NULL; | ||||
|     guchar *buf; | ||||
|     FILE *fh; | ||||
|     size_t read_count; | ||||
|  | ||||
|     if (!gfh) { | ||||
|         error_set(err, QERR_FD_NOT_FOUND, "handle"); | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     if (!has_count) { | ||||
|         count = QGA_READ_COUNT_DEFAULT; | ||||
|     } else if (count < 0) { | ||||
|         error_set(err, QERR_INVALID_PARAMETER, "count"); | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     fh = gfh->fh; | ||||
|     buf = qemu_mallocz(count+1); | ||||
|     read_count = fread(buf, 1, count, fh); | ||||
|     if (ferror(fh)) { | ||||
|         slog("guest-file-read failed, handle: %ld", handle); | ||||
|         error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed"); | ||||
|     } else { | ||||
|         buf[read_count] = 0; | ||||
|         read_data = qemu_mallocz(sizeof(GuestFileRead)); | ||||
|         read_data->count = read_count; | ||||
|         read_data->eof = feof(fh); | ||||
|         if (read_count) { | ||||
|             read_data->buf_b64 = g_base64_encode(buf, read_count); | ||||
|         } | ||||
|     } | ||||
|     qemu_free(buf); | ||||
|     clearerr(fh); | ||||
|  | ||||
|     return read_data; | ||||
| } | ||||
|  | ||||
| GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, | ||||
|                                      bool has_count, int64_t count, Error **err) | ||||
| { | ||||
|     GuestFileWrite *write_data = NULL; | ||||
|     guchar *buf; | ||||
|     gsize buf_len; | ||||
|     int write_count; | ||||
|     GuestFileHandle *gfh = guest_file_handle_find(handle); | ||||
|     FILE *fh; | ||||
|  | ||||
|     if (!gfh) { | ||||
|         error_set(err, QERR_FD_NOT_FOUND, "handle"); | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     fh = gfh->fh; | ||||
|     buf = g_base64_decode(buf_b64, &buf_len); | ||||
|  | ||||
|     if (!has_count) { | ||||
|         count = buf_len; | ||||
|     } else if (count < 0 || count > buf_len) { | ||||
|         qemu_free(buf); | ||||
|         error_set(err, QERR_INVALID_PARAMETER, "count"); | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     write_count = fwrite(buf, 1, count, fh); | ||||
|     if (ferror(fh)) { | ||||
|         slog("guest-file-write failed, handle: %ld", handle); | ||||
|         error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error"); | ||||
|     } else { | ||||
|         write_data = qemu_mallocz(sizeof(GuestFileWrite)); | ||||
|         write_data->count = write_count; | ||||
|         write_data->eof = feof(fh); | ||||
|     } | ||||
|     qemu_free(buf); | ||||
|     clearerr(fh); | ||||
|  | ||||
|     return write_data; | ||||
| } | ||||
|  | ||||
| struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, | ||||
|                                           int64_t whence, Error **err) | ||||
| { | ||||
|     GuestFileHandle *gfh = guest_file_handle_find(handle); | ||||
|     GuestFileSeek *seek_data = NULL; | ||||
|     FILE *fh; | ||||
|     int ret; | ||||
|  | ||||
|     if (!gfh) { | ||||
|         error_set(err, QERR_FD_NOT_FOUND, "handle"); | ||||
|         return NULL; | ||||
|     } | ||||
|  | ||||
|     fh = gfh->fh; | ||||
|     ret = fseek(fh, offset, whence); | ||||
|     if (ret == -1) { | ||||
|         error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); | ||||
|     } else { | ||||
|         seek_data = qemu_mallocz(sizeof(GuestFileRead)); | ||||
|         seek_data->position = ftell(fh); | ||||
|         seek_data->eof = feof(fh); | ||||
|     } | ||||
|     clearerr(fh); | ||||
|  | ||||
|     return seek_data; | ||||
| } | ||||
|  | ||||
| void qmp_guest_file_flush(int64_t handle, Error **err) | ||||
| { | ||||
|     GuestFileHandle *gfh = guest_file_handle_find(handle); | ||||
|     FILE *fh; | ||||
|     int ret; | ||||
|  | ||||
|     if (!gfh) { | ||||
|         error_set(err, QERR_FD_NOT_FOUND, "handle"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     fh = gfh->fh; | ||||
|     ret = fflush(fh); | ||||
|     if (ret == EOF) { | ||||
|         error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| static void guest_file_init(void) | ||||
| { | ||||
|     QTAILQ_INIT(&guest_file_state.filehandles); | ||||
| } | ||||
|  | ||||
| typedef struct GuestFsfreezeMount { | ||||
|     char *dirname; | ||||
|     char *devtype; | ||||
|     QTAILQ_ENTRY(GuestFsfreezeMount) next; | ||||
| } GuestFsfreezeMount; | ||||
|  | ||||
| struct { | ||||
|     GuestFsfreezeStatus status; | ||||
|     QTAILQ_HEAD(, GuestFsfreezeMount) mount_list; | ||||
| } guest_fsfreeze_state; | ||||
|  | ||||
| /* | ||||
|  * Walk the mount table and build a list of local file systems | ||||
|  */ | ||||
| static int guest_fsfreeze_build_mount_list(void) | ||||
| { | ||||
|     struct mntent *ment; | ||||
|     GuestFsfreezeMount *mount, *temp; | ||||
|     char const *mtab = MOUNTED; | ||||
|     FILE *fp; | ||||
|  | ||||
|     QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { | ||||
|         QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next); | ||||
|         qemu_free(mount->dirname); | ||||
|         qemu_free(mount->devtype); | ||||
|         qemu_free(mount); | ||||
|     } | ||||
|  | ||||
|     fp = setmntent(mtab, "r"); | ||||
|     if (!fp) { | ||||
|         g_warning("fsfreeze: unable to read mtab"); | ||||
|         return -1; | ||||
|     } | ||||
|  | ||||
|     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; | ||||
|         } | ||||
|  | ||||
|         mount = qemu_mallocz(sizeof(GuestFsfreezeMount)); | ||||
|         mount->dirname = qemu_strdup(ment->mnt_dir); | ||||
|         mount->devtype = qemu_strdup(ment->mnt_type); | ||||
|  | ||||
|         QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next); | ||||
|     } | ||||
|  | ||||
|     endmntent(fp); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Return status of freeze/thaw | ||||
|  */ | ||||
| GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) | ||||
| { | ||||
|     return guest_fsfreeze_state.status; | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * 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(Error **err) | ||||
| { | ||||
|     int ret = 0, i = 0; | ||||
|     struct GuestFsfreezeMount *mount, *temp; | ||||
|     int fd; | ||||
|     char err_msg[512]; | ||||
|  | ||||
|     slog("guest-fsfreeze called"); | ||||
|  | ||||
|     if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) { | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     ret = guest_fsfreeze_build_mount_list(); | ||||
|     if (ret < 0) { | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     /* cannot risk guest agent blocking itself on a write in this state */ | ||||
|     disable_logging(); | ||||
|  | ||||
|     QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { | ||||
|         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); | ||||
|             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. | ||||
|          * these will report EOPNOTSUPP, so we simply ignore them. when | ||||
|          * thawing, these filesystems will return an EINVAL instead, due to | ||||
|          * not being in a frozen state. Other filesystem-specific | ||||
|          * errors may result in EINVAL, however, so the user should check the | ||||
|          * number * of filesystems returned here against those returned by the | ||||
|          * thaw operation to determine whether everything completed | ||||
|          * successfully | ||||
|          */ | ||||
|         ret = ioctl(fd, FIFREEZE); | ||||
|         if (ret < 0 && errno != EOPNOTSUPP) { | ||||
|             sprintf(err_msg, "failed to freeze %s, %s", mount->dirname, strerror(errno)); | ||||
|             error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); | ||||
|             close(fd); | ||||
|             goto error; | ||||
|         } | ||||
|         close(fd); | ||||
|  | ||||
|         i++; | ||||
|     } | ||||
|  | ||||
|     guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN; | ||||
|     return i; | ||||
|  | ||||
| error: | ||||
|     if (i > 0) { | ||||
|         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 **err) | ||||
| { | ||||
|     int ret; | ||||
|     GuestFsfreezeMount *mount, *temp; | ||||
|     int fd, i = 0; | ||||
|     bool has_error = false; | ||||
|  | ||||
|     QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { | ||||
|         fd = qemu_open(mount->dirname, O_RDONLY); | ||||
|         if (fd == -1) { | ||||
|             has_error = true; | ||||
|             continue; | ||||
|         } | ||||
|         ret = ioctl(fd, FITHAW); | ||||
|         if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) { | ||||
|             has_error = true; | ||||
|             close(fd); | ||||
|             continue; | ||||
|         } | ||||
|         close(fd); | ||||
|         i++; | ||||
|     } | ||||
|  | ||||
|     if (has_error) { | ||||
|         guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR; | ||||
|     } else { | ||||
|         guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; | ||||
|     } | ||||
|     enable_logging(); | ||||
|     return i; | ||||
| } | ||||
|  | ||||
| static void guest_fsfreeze_init(void) | ||||
| { | ||||
|     guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; | ||||
|     QTAILQ_INIT(&guest_fsfreeze_state.mount_list); | ||||
| } | ||||
|  | ||||
| static void guest_fsfreeze_cleanup(void) | ||||
| { | ||||
|     int64_t ret; | ||||
|     Error *err = NULL; | ||||
|  | ||||
|     if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) { | ||||
|         ret = qmp_guest_fsfreeze_thaw(&err); | ||||
|         if (ret < 0 || err) { | ||||
|             slog("failed to clean up frozen filesystems"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* register init/cleanup routines for stateful command groups */ | ||||
| void ga_command_state_init(GAState *s, GACommandState *cs) | ||||
| { | ||||
|     ga_state = s; | ||||
|     ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup); | ||||
|     ga_command_state_add(cs, guest_file_init, NULL); | ||||
| } | ||||
| @@ -14,10 +14,12 @@ | ||||
| #include "qemu-common.h" | ||||
|  | ||||
| #define QGA_VERSION "1.0" | ||||
| #define QGA_READ_COUNT_DEFAULT 4 << 10 | ||||
|  | ||||
| typedef struct GAState GAState; | ||||
| typedef struct GACommandState GACommandState; | ||||
|  | ||||
| void ga_command_state_init(GAState *s, GACommandState *cs); | ||||
| void ga_command_state_add(GACommandState *cs, | ||||
|                           void (*init)(void), | ||||
|                           void (*cleanup)(void)); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user