Revision e3d4d252
b/Makefile | ||
---|---|---|
164 | 164 |
|
165 | 165 |
$(qapi-obj-y): $(GENERATED_HEADERS) |
166 | 166 |
qapi-dir := qapi-generated |
167 |
test-visitor.o test-qmp-commands.o: QEMU_CFLAGS += -I $(qapi-dir) |
|
167 |
test-visitor.o test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS += -I $(qapi-dir)
|
|
168 | 168 |
|
169 | 169 |
$(qapi-dir)/test-qapi-types.c: $(qapi-dir)/test-qapi-types.h |
170 | 170 |
$(qapi-dir)/test-qapi-types.h: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py |
... | ... | |
176 | 176 |
$(qapi-dir)/test-qmp-marshal.c: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py |
177 | 177 |
$(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "test-" < $<, " GEN $@") |
178 | 178 |
|
179 |
$(qapi-dir)/qga-qapi-types.c: $(qapi-dir)/qga-qapi-types.h |
|
180 |
$(qapi-dir)/qga-qapi-types.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-types.py |
|
181 |
$(call quiet-command,python $(SRC_PATH)/scripts/qapi-types.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@") |
|
182 |
$(qapi-dir)/qga-qapi-visit.c: $(qapi-dir)/qga-qapi-visit.h |
|
183 |
$(qapi-dir)/qga-qapi-visit.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-visit.py |
|
184 |
$(call quiet-command,python $(SRC_PATH)/scripts/qapi-visit.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@") |
|
185 |
$(qapi-dir)/qga-qmp-marshal.c: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-commands.py |
|
186 |
$(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@") |
|
187 |
|
|
179 | 188 |
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) |
180 | 189 |
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 |
181 | 190 |
|
182 | 191 |
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) |
183 | 192 |
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 |
184 | 193 |
|
185 |
QGALIB=qga/guest-agent-command-state.o |
|
194 |
QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.o
|
|
186 | 195 |
|
187 |
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 |
|
196 |
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) |
|
197 |
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 |
|
188 | 198 |
|
189 | 199 |
QEMULIBS=libhw32 libhw64 libuser libdis libdis-user |
190 | 200 |
|
b/qapi-schema-guest.json | ||
---|---|---|
1 |
# *-*- Mode: Python -*-* |
|
2 |
|
|
3 |
## |
|
4 |
# @guest-sync: |
|
5 |
# |
|
6 |
# Echo back a unique integer value |
|
7 |
# |
|
8 |
# This is used by clients talking to the guest agent over the |
|
9 |
# wire to ensure the stream is in sync and doesn't contain stale |
|
10 |
# data from previous client. All guest agent responses should be |
|
11 |
# ignored until the provided unique integer value is returned, |
|
12 |
# and it is up to the client to handle stale whole or |
|
13 |
# partially-delivered JSON text in such a way that this response |
|
14 |
# can be obtained. |
|
15 |
# |
|
16 |
# Such clients should also preceed this command |
|
17 |
# with a 0xFF byte to make such the guest agent flushes any |
|
18 |
# partially read JSON data from a previous session. |
|
19 |
# |
|
20 |
# @id: randomly generated 64-bit integer |
|
21 |
# |
|
22 |
# Returns: The unique integer id passed in by the client |
|
23 |
# |
|
24 |
# Since: 0.15.0 |
|
25 |
## |
|
26 |
{ 'command': 'guest-sync' |
|
27 |
'data': { 'id': 'int' }, |
|
28 |
'returns': 'int' } |
|
29 |
|
|
30 |
## |
|
31 |
# @guest-ping: |
|
32 |
# |
|
33 |
# Ping the guest agent, a non-error return implies success |
|
34 |
# |
|
35 |
# Since: 0.15.0 |
|
36 |
## |
|
37 |
{ 'command': 'guest-ping' } |
|
38 |
|
|
39 |
## |
|
40 |
# @guest-info: |
|
41 |
# |
|
42 |
# Get some information about the guest agent. |
|
43 |
# |
|
44 |
# Since: 0.15.0 |
|
45 |
## |
|
46 |
{ 'type': 'GuestAgentInfo', 'data': {'version': 'str'} } |
|
47 |
{ 'command': 'guest-info', |
|
48 |
'returns': 'GuestAgentInfo' } |
|
49 |
|
|
50 |
## |
|
51 |
# @guest-shutdown: |
|
52 |
# |
|
53 |
# Initiate guest-activated shutdown. Note: this is an asynchronous |
|
54 |
# shutdown request, with no guaruntee of successful shutdown. Errors |
|
55 |
# will be logged to guest's syslog. |
|
56 |
# |
|
57 |
# @mode: #optional "halt", "powerdown" (default), or "reboot" |
|
58 |
# |
|
59 |
# Returns: Nothing on success |
|
60 |
# |
|
61 |
# Since: 0.15.0 |
|
62 |
## |
|
63 |
{ 'command': 'guest-shutdown', 'data': { '*mode': 'str' } } |
|
64 |
|
|
65 |
## |
|
66 |
# @guest-file-open: |
|
67 |
# |
|
68 |
# Open a file in the guest and retrieve a file handle for it |
|
69 |
# |
|
70 |
# @filepath: Full path to the file in the guest to open. |
|
71 |
# |
|
72 |
# @mode: #optional open mode, as per fopen(), "r" is the default. |
|
73 |
# |
|
74 |
# Returns: Guest file handle on success. |
|
75 |
# |
|
76 |
# Since: 0.15.0 |
|
77 |
## |
|
78 |
{ 'command': 'guest-file-open', |
|
79 |
'data': { 'path': 'str', '*mode': 'str' }, |
|
80 |
'returns': 'int' } |
|
81 |
|
|
82 |
## |
|
83 |
# @guest-file-close: |
|
84 |
# |
|
85 |
# Close an open file in the guest |
|
86 |
# |
|
87 |
# @handle: filehandle returned by guest-file-open |
|
88 |
# |
|
89 |
# Returns: Nothing on success. |
|
90 |
# |
|
91 |
# Since: 0.15.0 |
|
92 |
## |
|
93 |
{ 'command': 'guest-file-close', |
|
94 |
'data': { 'handle': 'int' } } |
|
95 |
|
|
96 |
## |
|
97 |
# @guest-file-read: |
|
98 |
# |
|
99 |
# Read from an open file in the guest. Data will be base64-encoded |
|
100 |
# |
|
101 |
# @handle: filehandle returned by guest-file-open |
|
102 |
# |
|
103 |
# @count: #optional maximum number of bytes to read (default is 4KB) |
|
104 |
# |
|
105 |
# Returns: GuestFileRead on success. Note: count is number of bytes read |
|
106 |
# *before* base64 encoding bytes read. |
|
107 |
# |
|
108 |
# Since: 0.15.0 |
|
109 |
## |
|
110 |
{ 'type': 'GuestFileRead', |
|
111 |
'data': { 'count': 'int', 'buf-b64': 'str', 'eof': 'bool' } } |
|
112 |
|
|
113 |
{ 'command': 'guest-file-read', |
|
114 |
'data': { 'handle': 'int', '*count': 'int' }, |
|
115 |
'returns': 'GuestFileRead' } |
|
116 |
|
|
117 |
## |
|
118 |
# @guest-file-write: |
|
119 |
# |
|
120 |
# Write to an open file in the guest. |
|
121 |
# |
|
122 |
# @handle: filehandle returned by guest-file-open |
|
123 |
# |
|
124 |
# @buf-b64: base64-encoded string representing data to be written |
|
125 |
# |
|
126 |
# @count: #optional bytes to write (actual bytes, after base64-decode), |
|
127 |
# default is all content in buf-b64 buffer after base64 decoding |
|
128 |
# |
|
129 |
# Returns: GuestFileWrite on success. Note: count is the number of bytes |
|
130 |
# base64-decoded bytes written |
|
131 |
# |
|
132 |
# Since: 0.15.0 |
|
133 |
## |
|
134 |
{ 'type': 'GuestFileWrite', |
|
135 |
'data': { 'count': 'int', 'eof': 'bool' } } |
|
136 |
{ 'command': 'guest-file-write', |
|
137 |
'data': { 'handle': 'int', 'buf-b64': 'str', '*count': 'int' }, |
|
138 |
'returns': 'GuestFileWrite' } |
|
139 |
|
|
140 |
## |
|
141 |
# @guest-file-seek: |
|
142 |
# |
|
143 |
# Seek to a position in the file, as with fseek(), and return the |
|
144 |
# current file position afterward. Also encapsulates ftell()'s |
|
145 |
# functionality, just Set offset=0, whence=SEEK_CUR. |
|
146 |
# |
|
147 |
# @handle: filehandle returned by guest-file-open |
|
148 |
# |
|
149 |
# @offset: bytes to skip over in the file stream |
|
150 |
# |
|
151 |
# @whence: SEEK_SET, SEEK_CUR, or SEEK_END, as with fseek() |
|
152 |
# |
|
153 |
# Returns: GuestFileSeek on success. |
|
154 |
# |
|
155 |
# Since: 0.15.0 |
|
156 |
## |
|
157 |
{ 'type': 'GuestFileSeek', |
|
158 |
'data': { 'position': 'int', 'eof': 'bool' } } |
|
159 |
|
|
160 |
{ 'command': 'guest-file-seek', |
|
161 |
'data': { 'handle': 'int', 'offset': 'int', 'whence': 'int' }, |
|
162 |
'returns': 'GuestFileSeek' } |
|
163 |
|
|
164 |
## |
|
165 |
# @guest-file-flush: |
|
166 |
# |
|
167 |
# Write file changes bufferred in userspace to disk/kernel buffers |
|
168 |
# |
|
169 |
# @handle: filehandle returned by guest-file-open |
|
170 |
# |
|
171 |
# Returns: Nothing on success. |
|
172 |
# |
|
173 |
# Since: 0.15.0 |
|
174 |
## |
|
175 |
{ 'command': 'guest-file-flush', |
|
176 |
'data': { 'handle': 'int' } } |
|
177 |
|
|
178 |
## |
|
179 |
# @guest-fsfreeze-status: |
|
180 |
# |
|
181 |
# Get guest fsfreeze state. error state indicates failure to thaw 1 or more |
|
182 |
# previously frozen filesystems, or failure to open a previously cached |
|
183 |
# filesytem (filesystem unmounted/directory changes, etc). |
|
184 |
# |
|
185 |
# Returns: GuestFsfreezeStatus ("thawed", "frozen", etc., as defined below) |
|
186 |
# |
|
187 |
# Since: 0.15.0 |
|
188 |
## |
|
189 |
{ 'enum': 'GuestFsfreezeStatus', |
|
190 |
'data': [ 'thawed', 'frozen', 'error' ] } |
|
191 |
{ 'command': 'guest-fsfreeze-status', |
|
192 |
'returns': 'GuestFsfreezeStatus' } |
|
193 |
|
|
194 |
## |
|
195 |
# @guest-fsfreeze-freeze: |
|
196 |
# |
|
197 |
# Sync and freeze all non-network guest filesystems |
|
198 |
# |
|
199 |
# Returns: Number of file systems frozen on success |
|
200 |
# |
|
201 |
# Since: 0.15.0 |
|
202 |
## |
|
203 |
{ 'command': 'guest-fsfreeze-freeze', |
|
204 |
'returns': 'int' } |
|
205 |
|
|
206 |
## |
|
207 |
# @guest-fsfreeze-thaw: |
|
208 |
# |
|
209 |
# Unfreeze frozen guest fileystems |
|
210 |
# |
|
211 |
# Returns: Number of file systems thawed |
|
212 |
# If error, -1 (unknown error) or -errno |
|
213 |
# |
|
214 |
# Since: 0.15.0 |
|
215 |
## |
|
216 |
{ 'command': 'guest-fsfreeze-thaw', |
|
217 |
'returns': 'int' } |
b/qemu-ga.c | ||
---|---|---|
636 | 636 |
g_log_set_default_handler(ga_log, s); |
637 | 637 |
g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); |
638 | 638 |
s->logging_enabled = true; |
639 |
s->command_state = ga_command_state_new(); |
|
640 |
ga_command_state_init(s, s->command_state); |
|
641 |
ga_command_state_init_all(s->command_state); |
|
639 | 642 |
ga_state = s; |
640 | 643 |
|
641 | 644 |
module_call_init(MODULE_INIT_QAPI); |
... | ... | |
644 | 647 |
|
645 | 648 |
g_main_loop_run(ga_state->main_loop); |
646 | 649 |
|
650 |
ga_command_state_cleanup_all(ga_state->command_state); |
|
647 | 651 |
unlink(pidfile); |
648 | 652 |
|
649 | 653 |
return 0; |
b/qerror.c | ||
---|---|---|
218 | 218 |
.error_fmt = QERR_VNC_SERVER_FAILED, |
219 | 219 |
.desc = "Could not start VNC server on %(target)", |
220 | 220 |
}, |
221 |
{ |
|
222 |
.error_fmt = QERR_QGA_LOGGING_FAILED, |
|
223 |
.desc = "Guest agent failed to log non-optional log statement", |
|
224 |
}, |
|
225 |
{ |
|
226 |
.error_fmt = QERR_QGA_COMMAND_FAILED, |
|
227 |
.desc = "Guest agent command failed, error was '%(message)'", |
|
228 |
}, |
|
221 | 229 |
{} |
222 | 230 |
}; |
223 | 231 |
|
b/qerror.h | ||
---|---|---|
184 | 184 |
#define QERR_FEATURE_DISABLED \ |
185 | 185 |
"{ 'class': 'FeatureDisabled', 'data': { 'name': %s } }" |
186 | 186 |
|
187 |
#define QERR_QGA_LOGGING_FAILED \ |
|
188 |
"{ 'class': 'QgaLoggingFailed', 'data': {} }" |
|
189 |
|
|
190 |
#define QERR_QGA_COMMAND_FAILED \ |
|
191 |
"{ 'class': 'QgaCommandFailed', 'data': { 'message': %s } }" |
|
192 |
|
|
187 | 193 |
#endif /* QERROR_H */ |
b/qga/guest-agent-commands.c | ||
---|---|---|
1 |
/* |
|
2 |
* QEMU Guest Agent commands |
|
3 |
* |
|
4 |
* Copyright IBM Corp. 2011 |
|
5 |
* |
|
6 |
* Authors: |
|
7 |
* Michael Roth <mdroth@linux.vnet.ibm.com> |
|
8 |
* |
|
9 |
* This work is licensed under the terms of the GNU GPL, version 2 or later. |
|
10 |
* See the COPYING file in the top-level directory. |
|
11 |
*/ |
|
12 |
|
|
13 |
#include <glib.h> |
|
14 |
#include <mntent.h> |
|
15 |
#include <sys/types.h> |
|
16 |
#include <sys/ioctl.h> |
|
17 |
#include <linux/fs.h> |
|
18 |
#include "qga/guest-agent-core.h" |
|
19 |
#include "qga-qmp-commands.h" |
|
20 |
#include "qerror.h" |
|
21 |
#include "qemu-queue.h" |
|
22 |
|
|
23 |
static GAState *ga_state; |
|
24 |
|
|
25 |
static void disable_logging(void) |
|
26 |
{ |
|
27 |
ga_disable_logging(ga_state); |
|
28 |
} |
|
29 |
|
|
30 |
static void enable_logging(void) |
|
31 |
{ |
|
32 |
ga_enable_logging(ga_state); |
|
33 |
} |
|
34 |
|
|
35 |
/* Note: in some situations, like with the fsfreeze, logging may be |
|
36 |
* temporarilly disabled. if it is necessary that a command be able |
|
37 |
* to log for accounting purposes, check ga_logging_enabled() beforehand, |
|
38 |
* and use the QERR_QGA_LOGGING_DISABLED to generate an error |
|
39 |
*/ |
|
40 |
static void slog(const char *fmt, ...) |
|
41 |
{ |
|
42 |
va_list ap; |
|
43 |
|
|
44 |
va_start(ap, fmt); |
|
45 |
g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap); |
|
46 |
va_end(ap); |
|
47 |
} |
|
48 |
|
|
49 |
int64_t qmp_guest_sync(int64_t id, Error **errp) |
|
50 |
{ |
|
51 |
return id; |
|
52 |
} |
|
53 |
|
|
54 |
void qmp_guest_ping(Error **err) |
|
55 |
{ |
|
56 |
slog("guest-ping called"); |
|
57 |
} |
|
58 |
|
|
59 |
struct GuestAgentInfo *qmp_guest_info(Error **err) |
|
60 |
{ |
|
61 |
GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo)); |
|
62 |
|
|
63 |
info->version = g_strdup(QGA_VERSION); |
|
64 |
|
|
65 |
return info; |
|
66 |
} |
|
67 |
|
|
68 |
void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) |
|
69 |
{ |
|
70 |
int ret; |
|
71 |
const char *shutdown_flag; |
|
72 |
|
|
73 |
slog("guest-shutdown called, mode: %s", mode); |
|
74 |
if (!has_mode || strcmp(mode, "powerdown") == 0) { |
|
75 |
shutdown_flag = "-P"; |
|
76 |
} else if (strcmp(mode, "halt") == 0) { |
|
77 |
shutdown_flag = "-H"; |
|
78 |
} else if (strcmp(mode, "reboot") == 0) { |
|
79 |
shutdown_flag = "-r"; |
|
80 |
} else { |
|
81 |
error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode", |
|
82 |
"halt|powerdown|reboot"); |
|
83 |
return; |
|
84 |
} |
|
85 |
|
|
86 |
ret = fork(); |
|
87 |
if (ret == 0) { |
|
88 |
/* child, start the shutdown */ |
|
89 |
setsid(); |
|
90 |
fclose(stdin); |
|
91 |
fclose(stdout); |
|
92 |
fclose(stderr); |
|
93 |
|
|
94 |
ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", |
|
95 |
"hypervisor initiated shutdown", (char*)NULL); |
|
96 |
if (ret) { |
|
97 |
slog("guest-shutdown failed: %s", strerror(errno)); |
|
98 |
} |
|
99 |
exit(!!ret); |
|
100 |
} else if (ret < 0) { |
|
101 |
error_set(err, QERR_UNDEFINED_ERROR); |
|
102 |
} |
|
103 |
} |
|
104 |
|
|
105 |
typedef struct GuestFileHandle { |
|
106 |
uint64_t id; |
|
107 |
FILE *fh; |
|
108 |
QTAILQ_ENTRY(GuestFileHandle) next; |
|
109 |
} GuestFileHandle; |
|
110 |
|
|
111 |
static struct { |
|
112 |
QTAILQ_HEAD(, GuestFileHandle) filehandles; |
|
113 |
} guest_file_state; |
|
114 |
|
|
115 |
static void guest_file_handle_add(FILE *fh) |
|
116 |
{ |
|
117 |
GuestFileHandle *gfh; |
|
118 |
|
|
119 |
gfh = qemu_mallocz(sizeof(GuestFileHandle)); |
|
120 |
gfh->id = fileno(fh); |
|
121 |
gfh->fh = fh; |
|
122 |
QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); |
|
123 |
} |
|
124 |
|
|
125 |
static GuestFileHandle *guest_file_handle_find(int64_t id) |
|
126 |
{ |
|
127 |
GuestFileHandle *gfh; |
|
128 |
|
|
129 |
QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) |
|
130 |
{ |
|
131 |
if (gfh->id == id) { |
|
132 |
return gfh; |
|
133 |
} |
|
134 |
} |
|
135 |
|
|
136 |
return NULL; |
|
137 |
} |
|
138 |
|
|
139 |
int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err) |
|
140 |
{ |
|
141 |
FILE *fh; |
|
142 |
int fd; |
|
143 |
int64_t ret = -1; |
|
144 |
|
|
145 |
if (!has_mode) { |
|
146 |
mode = "r"; |
|
147 |
} |
|
148 |
slog("guest-file-open called, filepath: %s, mode: %s", path, mode); |
|
149 |
fh = fopen(path, mode); |
|
150 |
if (!fh) { |
|
151 |
error_set(err, QERR_OPEN_FILE_FAILED, path); |
|
152 |
return -1; |
|
153 |
} |
|
154 |
|
|
155 |
/* set fd non-blocking to avoid common use cases (like reading from a |
|
156 |
* named pipe) from hanging the agent |
|
157 |
*/ |
|
158 |
fd = fileno(fh); |
|
159 |
ret = fcntl(fd, F_GETFL); |
|
160 |
ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK); |
|
161 |
if (ret == -1) { |
|
162 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed"); |
|
163 |
fclose(fh); |
|
164 |
return -1; |
|
165 |
} |
|
166 |
|
|
167 |
guest_file_handle_add(fh); |
|
168 |
slog("guest-file-open, handle: %d", fd); |
|
169 |
return fd; |
|
170 |
} |
|
171 |
|
|
172 |
void qmp_guest_file_close(int64_t handle, Error **err) |
|
173 |
{ |
|
174 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
|
175 |
int ret; |
|
176 |
|
|
177 |
slog("guest-file-close called, handle: %ld", handle); |
|
178 |
if (!gfh) { |
|
179 |
error_set(err, QERR_FD_NOT_FOUND, "handle"); |
|
180 |
return; |
|
181 |
} |
|
182 |
|
|
183 |
ret = fclose(gfh->fh); |
|
184 |
if (ret == -1) { |
|
185 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed"); |
|
186 |
return; |
|
187 |
} |
|
188 |
|
|
189 |
QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next); |
|
190 |
qemu_free(gfh); |
|
191 |
} |
|
192 |
|
|
193 |
struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, |
|
194 |
int64_t count, Error **err) |
|
195 |
{ |
|
196 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
|
197 |
GuestFileRead *read_data = NULL; |
|
198 |
guchar *buf; |
|
199 |
FILE *fh; |
|
200 |
size_t read_count; |
|
201 |
|
|
202 |
if (!gfh) { |
|
203 |
error_set(err, QERR_FD_NOT_FOUND, "handle"); |
|
204 |
return NULL; |
|
205 |
} |
|
206 |
|
|
207 |
if (!has_count) { |
|
208 |
count = QGA_READ_COUNT_DEFAULT; |
|
209 |
} else if (count < 0) { |
|
210 |
error_set(err, QERR_INVALID_PARAMETER, "count"); |
|
211 |
return NULL; |
|
212 |
} |
|
213 |
|
|
214 |
fh = gfh->fh; |
|
215 |
buf = qemu_mallocz(count+1); |
|
216 |
read_count = fread(buf, 1, count, fh); |
|
217 |
if (ferror(fh)) { |
|
218 |
slog("guest-file-read failed, handle: %ld", handle); |
|
219 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed"); |
|
220 |
} else { |
|
221 |
buf[read_count] = 0; |
|
222 |
read_data = qemu_mallocz(sizeof(GuestFileRead)); |
|
223 |
read_data->count = read_count; |
|
224 |
read_data->eof = feof(fh); |
|
225 |
if (read_count) { |
|
226 |
read_data->buf_b64 = g_base64_encode(buf, read_count); |
|
227 |
} |
|
228 |
} |
|
229 |
qemu_free(buf); |
|
230 |
clearerr(fh); |
|
231 |
|
|
232 |
return read_data; |
|
233 |
} |
|
234 |
|
|
235 |
GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, |
|
236 |
bool has_count, int64_t count, Error **err) |
|
237 |
{ |
|
238 |
GuestFileWrite *write_data = NULL; |
|
239 |
guchar *buf; |
|
240 |
gsize buf_len; |
|
241 |
int write_count; |
|
242 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
|
243 |
FILE *fh; |
|
244 |
|
|
245 |
if (!gfh) { |
|
246 |
error_set(err, QERR_FD_NOT_FOUND, "handle"); |
|
247 |
return NULL; |
|
248 |
} |
|
249 |
|
|
250 |
fh = gfh->fh; |
|
251 |
buf = g_base64_decode(buf_b64, &buf_len); |
|
252 |
|
|
253 |
if (!has_count) { |
|
254 |
count = buf_len; |
|
255 |
} else if (count < 0 || count > buf_len) { |
|
256 |
qemu_free(buf); |
|
257 |
error_set(err, QERR_INVALID_PARAMETER, "count"); |
|
258 |
return NULL; |
|
259 |
} |
|
260 |
|
|
261 |
write_count = fwrite(buf, 1, count, fh); |
|
262 |
if (ferror(fh)) { |
|
263 |
slog("guest-file-write failed, handle: %ld", handle); |
|
264 |
error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error"); |
|
265 |
} else { |
|
266 |
write_data = qemu_mallocz(sizeof(GuestFileWrite)); |
|
267 |
write_data->count = write_count; |
|
268 |
write_data->eof = feof(fh); |
|
269 |
} |
|
270 |
qemu_free(buf); |
|
271 |
clearerr(fh); |
|
272 |
|
|
273 |
return write_data; |
|
274 |
} |
|
275 |
|
|
276 |
struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, |
|
277 |
int64_t whence, Error **err) |
|
278 |
{ |
|
279 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
|
280 |
GuestFileSeek *seek_data = NULL; |
|
281 |
FILE *fh; |
|
282 |
int ret; |
|
283 |
|
|
284 |
if (!gfh) { |
|
285 |
error_set(err, QERR_FD_NOT_FOUND, "handle"); |
|
286 |
return NULL; |
|
287 |
} |
|
288 |
|
|
289 |
fh = gfh->fh; |
|
290 |
ret = fseek(fh, offset, whence); |
|
291 |
if (ret == -1) { |
|
292 |
error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); |
|
293 |
} else { |
|
294 |
seek_data = qemu_mallocz(sizeof(GuestFileRead)); |
|
295 |
seek_data->position = ftell(fh); |
|
296 |
seek_data->eof = feof(fh); |
|
297 |
} |
|
298 |
clearerr(fh); |
|
299 |
|
|
300 |
return seek_data; |
|
301 |
} |
|
302 |
|
|
303 |
void qmp_guest_file_flush(int64_t handle, Error **err) |
|
304 |
{ |
|
305 |
GuestFileHandle *gfh = guest_file_handle_find(handle); |
|
306 |
FILE *fh; |
|
307 |
int ret; |
|
308 |
|
|
309 |
if (!gfh) { |
|
310 |
error_set(err, QERR_FD_NOT_FOUND, "handle"); |
|
311 |
return; |
|
312 |
} |
|
313 |
|
|
314 |
fh = gfh->fh; |
|
315 |
ret = fflush(fh); |
|
316 |
if (ret == EOF) { |
|
317 |
error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); |
|
318 |
} |
|
319 |
} |
|
320 |
|
|
321 |
static void guest_file_init(void) |
|
322 |
{ |
|
323 |
QTAILQ_INIT(&guest_file_state.filehandles); |
|
324 |
} |
|
325 |
|
|
326 |
typedef struct GuestFsfreezeMount { |
|
327 |
char *dirname; |
|
328 |
char *devtype; |
|
329 |
QTAILQ_ENTRY(GuestFsfreezeMount) next; |
|
330 |
} GuestFsfreezeMount; |
|
331 |
|
|
332 |
struct { |
|
333 |
GuestFsfreezeStatus status; |
|
334 |
QTAILQ_HEAD(, GuestFsfreezeMount) mount_list; |
|
335 |
} guest_fsfreeze_state; |
|
336 |
|
|
337 |
/* |
|
338 |
* Walk the mount table and build a list of local file systems |
|
339 |
*/ |
|
340 |
static int guest_fsfreeze_build_mount_list(void) |
|
341 |
{ |
|
342 |
struct mntent *ment; |
|
343 |
GuestFsfreezeMount *mount, *temp; |
|
344 |
char const *mtab = MOUNTED; |
|
345 |
FILE *fp; |
|
346 |
|
|
347 |
QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { |
|
348 |
QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next); |
|
349 |
qemu_free(mount->dirname); |
|
350 |
qemu_free(mount->devtype); |
|
351 |
qemu_free(mount); |
|
352 |
} |
|
353 |
|
|
354 |
fp = setmntent(mtab, "r"); |
|
355 |
if (!fp) { |
|
356 |
g_warning("fsfreeze: unable to read mtab"); |
|
357 |
return -1; |
|
358 |
} |
|
359 |
|
|
360 |
while ((ment = getmntent(fp))) { |
|
361 |
/* |
|
362 |
* An entry which device name doesn't start with a '/' is |
|
363 |
* either a dummy file system or a network file system. |
|
364 |
* Add special handling for smbfs and cifs as is done by |
|
365 |
* coreutils as well. |
|
366 |
*/ |
|
367 |
if ((ment->mnt_fsname[0] != '/') || |
|
368 |
(strcmp(ment->mnt_type, "smbfs") == 0) || |
|
369 |
(strcmp(ment->mnt_type, "cifs") == 0)) { |
|
370 |
continue; |
|
371 |
} |
|
372 |
|
|
373 |
mount = qemu_mallocz(sizeof(GuestFsfreezeMount)); |
|
374 |
mount->dirname = qemu_strdup(ment->mnt_dir); |
|
375 |
mount->devtype = qemu_strdup(ment->mnt_type); |
|
376 |
|
|
377 |
QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next); |
|
378 |
} |
|
379 |
|
|
380 |
endmntent(fp); |
|
381 |
|
|
382 |
return 0; |
|
383 |
} |
|
384 |
|
|
385 |
/* |
|
386 |
* Return status of freeze/thaw |
|
387 |
*/ |
|
388 |
GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) |
|
389 |
{ |
|
390 |
return guest_fsfreeze_state.status; |
|
391 |
} |
|
392 |
|
|
393 |
/* |
|
394 |
* Walk list of mounted file systems in the guest, and freeze the ones which |
|
395 |
* are real local file systems. |
|
396 |
*/ |
|
397 |
int64_t qmp_guest_fsfreeze_freeze(Error **err) |
|
398 |
{ |
|
399 |
int ret = 0, i = 0; |
|
400 |
struct GuestFsfreezeMount *mount, *temp; |
|
401 |
int fd; |
|
402 |
char err_msg[512]; |
|
403 |
|
|
404 |
slog("guest-fsfreeze called"); |
|
405 |
|
|
406 |
if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) { |
|
407 |
return 0; |
|
408 |
} |
|
409 |
|
|
410 |
ret = guest_fsfreeze_build_mount_list(); |
|
411 |
if (ret < 0) { |
|
412 |
return ret; |
|
413 |
} |
|
414 |
|
|
415 |
/* cannot risk guest agent blocking itself on a write in this state */ |
|
416 |
disable_logging(); |
|
417 |
|
|
418 |
QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { |
|
419 |
fd = qemu_open(mount->dirname, O_RDONLY); |
|
420 |
if (fd == -1) { |
|
421 |
sprintf(err_msg, "failed to open %s, %s", mount->dirname, strerror(errno)); |
|
422 |
error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); |
|
423 |
goto error; |
|
424 |
} |
|
425 |
|
|
426 |
/* we try to cull filesytems we know won't work in advance, but other |
|
427 |
* filesytems may not implement fsfreeze for less obvious reasons. |
|
428 |
* these will report EOPNOTSUPP, so we simply ignore them. when |
|
429 |
* thawing, these filesystems will return an EINVAL instead, due to |
|
430 |
* not being in a frozen state. Other filesystem-specific |
|
431 |
* errors may result in EINVAL, however, so the user should check the |
|
432 |
* number * of filesystems returned here against those returned by the |
|
433 |
* thaw operation to determine whether everything completed |
|
434 |
* successfully |
|
435 |
*/ |
|
436 |
ret = ioctl(fd, FIFREEZE); |
|
437 |
if (ret < 0 && errno != EOPNOTSUPP) { |
|
438 |
sprintf(err_msg, "failed to freeze %s, %s", mount->dirname, strerror(errno)); |
|
439 |
error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); |
|
440 |
close(fd); |
|
441 |
goto error; |
|
442 |
} |
|
443 |
close(fd); |
|
444 |
|
|
445 |
i++; |
|
446 |
} |
|
447 |
|
|
448 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN; |
|
449 |
return i; |
|
450 |
|
|
451 |
error: |
|
452 |
if (i > 0) { |
|
453 |
qmp_guest_fsfreeze_thaw(NULL); |
|
454 |
} |
|
455 |
return 0; |
|
456 |
} |
|
457 |
|
|
458 |
/* |
|
459 |
* Walk list of frozen file systems in the guest, and thaw them. |
|
460 |
*/ |
|
461 |
int64_t qmp_guest_fsfreeze_thaw(Error **err) |
|
462 |
{ |
|
463 |
int ret; |
|
464 |
GuestFsfreezeMount *mount, *temp; |
|
465 |
int fd, i = 0; |
|
466 |
bool has_error = false; |
|
467 |
|
|
468 |
QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { |
|
469 |
fd = qemu_open(mount->dirname, O_RDONLY); |
|
470 |
if (fd == -1) { |
|
471 |
has_error = true; |
|
472 |
continue; |
|
473 |
} |
|
474 |
ret = ioctl(fd, FITHAW); |
|
475 |
if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) { |
|
476 |
has_error = true; |
|
477 |
close(fd); |
|
478 |
continue; |
|
479 |
} |
|
480 |
close(fd); |
|
481 |
i++; |
|
482 |
} |
|
483 |
|
|
484 |
if (has_error) { |
|
485 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR; |
|
486 |
} else { |
|
487 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; |
|
488 |
} |
|
489 |
enable_logging(); |
|
490 |
return i; |
|
491 |
} |
|
492 |
|
|
493 |
static void guest_fsfreeze_init(void) |
|
494 |
{ |
|
495 |
guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; |
|
496 |
QTAILQ_INIT(&guest_fsfreeze_state.mount_list); |
|
497 |
} |
|
498 |
|
|
499 |
static void guest_fsfreeze_cleanup(void) |
|
500 |
{ |
|
501 |
int64_t ret; |
|
502 |
Error *err = NULL; |
|
503 |
|
|
504 |
if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) { |
|
505 |
ret = qmp_guest_fsfreeze_thaw(&err); |
|
506 |
if (ret < 0 || err) { |
|
507 |
slog("failed to clean up frozen filesystems"); |
|
508 |
} |
|
509 |
} |
|
510 |
} |
|
511 |
|
|
512 |
/* register init/cleanup routines for stateful command groups */ |
|
513 |
void ga_command_state_init(GAState *s, GACommandState *cs) |
|
514 |
{ |
|
515 |
ga_state = s; |
|
516 |
ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup); |
|
517 |
ga_command_state_add(cs, guest_file_init, NULL); |
|
518 |
} |
b/qga/guest-agent-core.h | ||
---|---|---|
14 | 14 |
#include "qemu-common.h" |
15 | 15 |
|
16 | 16 |
#define QGA_VERSION "1.0" |
17 |
#define QGA_READ_COUNT_DEFAULT 4 << 10 |
|
17 | 18 |
|
18 | 19 |
typedef struct GAState GAState; |
19 | 20 |
typedef struct GACommandState GACommandState; |
20 | 21 |
|
22 |
void ga_command_state_init(GAState *s, GACommandState *cs); |
|
21 | 23 |
void ga_command_state_add(GACommandState *cs, |
22 | 24 |
void (*init)(void), |
23 | 25 |
void (*cleanup)(void)); |
Also available in: Unified diff