xref: /openbmc/qemu/qga/commands-posix.c (revision 518b0d80)
1c216e5adSMichael Roth /*
2c216e5adSMichael Roth  * QEMU Guest Agent POSIX-specific command implementations
3c216e5adSMichael Roth  *
4c216e5adSMichael Roth  * Copyright IBM Corp. 2011
5c216e5adSMichael Roth  *
6c216e5adSMichael Roth  * Authors:
7c216e5adSMichael Roth  *  Michael Roth      <mdroth@linux.vnet.ibm.com>
83424fc9fSMichal Privoznik  *  Michal Privoznik  <mprivozn@redhat.com>
9c216e5adSMichael Roth  *
10c216e5adSMichael Roth  * This work is licensed under the terms of the GNU GPL, version 2 or later.
11c216e5adSMichael Roth  * See the COPYING file in the top-level directory.
12c216e5adSMichael Roth  */
13c216e5adSMichael Roth 
144459bf38SPeter Maydell #include "qemu/osdep.h"
15c216e5adSMichael Roth #include <sys/ioctl.h>
169848f797STomáš Golembiovský #include <sys/utsname.h>
172c02cbf6SLuiz Capitulino #include <sys/wait.h>
1846d4c572STomoki Sekiyama #include <dirent.h>
19eb815e24SMarkus Armbruster #include "qga-qapi-commands.h"
20e688df6bSMarkus Armbruster #include "qapi/error.h"
217b1b5d19SPaolo Bonzini #include "qapi/qmp/qerror.h"
221de7afc9SPaolo Bonzini #include "qemu/host-utils.h"
2312505396SDenis V. Lunev #include "qemu/sockets.h"
24920639caSDaniel P. Berrange #include "qemu/base64.h"
25f348b6d1SVeronia Bahaa #include "qemu/cutils.h"
265d3586b8SPhilippe Mathieu-Daudé #include "commands-common.h"
2722668881Szhenwei pi #include "block/nvme.h"
281a89a17bSMarc-André Lureau #include "cutils.h"
29c216e5adSMichael Roth 
30e674605fSTomáš Golembiovský #ifdef HAVE_UTMPX
31e674605fSTomáš Golembiovský #include <utmpx.h>
32e674605fSTomáš Golembiovský #endif
33e674605fSTomáš Golembiovský 
34e72c3f2eSMichael Roth #if defined(__linux__)
35e72c3f2eSMichael Roth #include <mntent.h>
36e72c3f2eSMichael Roth #include <linux/fs.h>
3725b5ff1aSChen Hanxiao #include <sys/statvfs.h>
3822668881Szhenwei pi #include <linux/nvme_ioctl.h>
39e72c3f2eSMichael Roth 
40b616105aSTomáš Golembiovský #ifdef CONFIG_LIBUDEV
41b616105aSTomáš Golembiovský #include <libudev.h>
42b616105aSTomáš Golembiovský #endif
43b616105aSTomáš Golembiovský 
44eab5fd59SPaolo Bonzini #ifdef FIFREEZE
45e72c3f2eSMichael Roth #define CONFIG_FSFREEZE
46e72c3f2eSMichael Roth #endif
47eab5fd59SPaolo Bonzini #ifdef FITRIM
48eab5fd59SPaolo Bonzini #define CONFIG_FSTRIM
49eab5fd59SPaolo Bonzini #endif
50e72c3f2eSMichael Roth #endif
51e72c3f2eSMichael Roth 
52c6cd588bSAlexander Ivanov #ifdef __FreeBSD__
53c6cd588bSAlexander Ivanov /*
54c6cd588bSAlexander Ivanov  * The code under HAVE_GETIFADDRS condition can't be compiled in FreeBSD.
55c6cd588bSAlexander Ivanov  * Fix it in one of the following patches.
56c6cd588bSAlexander Ivanov  */
57c6cd588bSAlexander Ivanov #undef HAVE_GETIFADDRS
58c6cd588bSAlexander Ivanov #endif
59c6cd588bSAlexander Ivanov 
6059e35c7bSAndrew Deason #ifdef HAVE_GETIFADDRS
6159e35c7bSAndrew Deason #include <arpa/inet.h>
6259e35c7bSAndrew Deason #include <sys/socket.h>
6359e35c7bSAndrew Deason #include <net/if.h>
6459e35c7bSAndrew Deason #include <sys/types.h>
6559e35c7bSAndrew Deason #include <ifaddrs.h>
6659e35c7bSAndrew Deason #ifdef CONFIG_SOLARIS
6759e35c7bSAndrew Deason #include <sys/sockio.h>
6859e35c7bSAndrew Deason #endif
6959e35c7bSAndrew Deason #endif
7059e35c7bSAndrew Deason 
7177dbc81bSMarkus Armbruster static void ga_wait_child(pid_t pid, int *status, Error **errp)
72d220a6dfSLuiz Capitulino {
73d220a6dfSLuiz Capitulino     pid_t rpid;
74d220a6dfSLuiz Capitulino 
75d220a6dfSLuiz Capitulino     *status = 0;
76d220a6dfSLuiz Capitulino 
77d220a6dfSLuiz Capitulino     do {
78d220a6dfSLuiz Capitulino         rpid = waitpid(pid, status, 0);
79d220a6dfSLuiz Capitulino     } while (rpid == -1 && errno == EINTR);
80d220a6dfSLuiz Capitulino 
81d220a6dfSLuiz Capitulino     if (rpid == -1) {
8277dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to wait for child (pid: %d)",
8377dbc81bSMarkus Armbruster                          pid);
84d220a6dfSLuiz Capitulino         return;
85d220a6dfSLuiz Capitulino     }
86d220a6dfSLuiz Capitulino 
87d220a6dfSLuiz Capitulino     g_assert(rpid == pid);
88d220a6dfSLuiz Capitulino }
89d220a6dfSLuiz Capitulino 
9077dbc81bSMarkus Armbruster void qmp_guest_shutdown(bool has_mode, const char *mode, Error **errp)
91c216e5adSMichael Roth {
92c216e5adSMichael Roth     const char *shutdown_flag;
93d220a6dfSLuiz Capitulino     Error *local_err = NULL;
94d220a6dfSLuiz Capitulino     pid_t pid;
953674838cSLuiz Capitulino     int status;
96c216e5adSMichael Roth 
97c8ec041dSAndrew Deason #ifdef CONFIG_SOLARIS
98c8ec041dSAndrew Deason     const char *powerdown_flag = "-i5";
99c8ec041dSAndrew Deason     const char *halt_flag = "-i0";
100c8ec041dSAndrew Deason     const char *reboot_flag = "-i6";
101c8ec041dSAndrew Deason #else
102c8ec041dSAndrew Deason     const char *powerdown_flag = "-P";
103c8ec041dSAndrew Deason     const char *halt_flag = "-H";
104c8ec041dSAndrew Deason     const char *reboot_flag = "-r";
105c8ec041dSAndrew Deason #endif
106c8ec041dSAndrew Deason 
107c216e5adSMichael Roth     slog("guest-shutdown called, mode: %s", mode);
108c216e5adSMichael Roth     if (!has_mode || strcmp(mode, "powerdown") == 0) {
109c8ec041dSAndrew Deason         shutdown_flag = powerdown_flag;
110c216e5adSMichael Roth     } else if (strcmp(mode, "halt") == 0) {
111c8ec041dSAndrew Deason         shutdown_flag = halt_flag;
112c216e5adSMichael Roth     } else if (strcmp(mode, "reboot") == 0) {
113c8ec041dSAndrew Deason         shutdown_flag = reboot_flag;
114c216e5adSMichael Roth     } else {
11577dbc81bSMarkus Armbruster         error_setg(errp,
116d220a6dfSLuiz Capitulino                    "mode is invalid (valid values are: halt|powerdown|reboot");
117c216e5adSMichael Roth         return;
118c216e5adSMichael Roth     }
119c216e5adSMichael Roth 
120d5dd3498SLuiz Capitulino     pid = fork();
121d5dd3498SLuiz Capitulino     if (pid == 0) {
122c216e5adSMichael Roth         /* child, start the shutdown */
123c216e5adSMichael Roth         setsid();
1243674838cSLuiz Capitulino         reopen_fd_to_null(0);
1253674838cSLuiz Capitulino         reopen_fd_to_null(1);
1263674838cSLuiz Capitulino         reopen_fd_to_null(2);
127c216e5adSMichael Roth 
128c8ec041dSAndrew Deason #ifdef CONFIG_SOLARIS
129c8ec041dSAndrew Deason         execl("/sbin/shutdown", "shutdown", shutdown_flag, "-g0", "-y",
130c8ec041dSAndrew Deason               "hypervisor initiated shutdown", (char *)NULL);
131c8ec041dSAndrew Deason #else
132fcc41961SMarc-André Lureau         execl("/sbin/shutdown", "shutdown", "-h", shutdown_flag, "+0",
133fcc41961SMarc-André Lureau                "hypervisor initiated shutdown", (char *)NULL);
134c8ec041dSAndrew Deason #endif
1353674838cSLuiz Capitulino         _exit(EXIT_FAILURE);
136d5dd3498SLuiz Capitulino     } else if (pid < 0) {
13777dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to create child process");
138d5dd3498SLuiz Capitulino         return;
139d5dd3498SLuiz Capitulino     }
140d5dd3498SLuiz Capitulino 
141d220a6dfSLuiz Capitulino     ga_wait_child(pid, &status, &local_err);
14284d18f06SMarkus Armbruster     if (local_err) {
14377dbc81bSMarkus Armbruster         error_propagate(errp, local_err);
144d220a6dfSLuiz Capitulino         return;
145d220a6dfSLuiz Capitulino     }
146d220a6dfSLuiz Capitulino 
147d220a6dfSLuiz Capitulino     if (!WIFEXITED(status)) {
14877dbc81bSMarkus Armbruster         error_setg(errp, "child process has terminated abnormally");
149d220a6dfSLuiz Capitulino         return;
150d220a6dfSLuiz Capitulino     }
151d220a6dfSLuiz Capitulino 
152d220a6dfSLuiz Capitulino     if (WEXITSTATUS(status)) {
15377dbc81bSMarkus Armbruster         error_setg(errp, "child process has failed to shutdown");
154d220a6dfSLuiz Capitulino         return;
155d220a6dfSLuiz Capitulino     }
156d220a6dfSLuiz Capitulino 
157085d8134SPeter Maydell     /* succeeded */
158c216e5adSMichael Roth }
159c216e5adSMichael Roth 
1602c958923SMichal Privoznik void qmp_guest_set_time(bool has_time, int64_t time_ns, Error **errp)
161a1bca57fSLei Li {
162a1bca57fSLei Li     int ret;
163a1bca57fSLei Li     int status;
164a1bca57fSLei Li     pid_t pid;
165a1bca57fSLei Li     Error *local_err = NULL;
166a1bca57fSLei Li     struct timeval tv;
1675c6096e5SCornelia Huck     static const char hwclock_path[] = "/sbin/hwclock";
1685c6096e5SCornelia Huck     static int hwclock_available = -1;
1695c6096e5SCornelia Huck 
1705c6096e5SCornelia Huck     if (hwclock_available < 0) {
1715c6096e5SCornelia Huck         hwclock_available = (access(hwclock_path, X_OK) == 0);
1725c6096e5SCornelia Huck     }
1735c6096e5SCornelia Huck 
1745c6096e5SCornelia Huck     if (!hwclock_available) {
1755c6096e5SCornelia Huck         error_setg(errp, QERR_UNSUPPORTED);
1765c6096e5SCornelia Huck         return;
1775c6096e5SCornelia Huck     }
178a1bca57fSLei Li 
1792c958923SMichal Privoznik     /* If user has passed a time, validate and set it. */
1802c958923SMichal Privoznik     if (has_time) {
18100d2f370SMarc-André Lureau         GDate date = { 0, };
18200d2f370SMarc-André Lureau 
183a1bca57fSLei Li         /* year-2038 will overflow in case time_t is 32bit */
184a1bca57fSLei Li         if (time_ns / 1000000000 != (time_t)(time_ns / 1000000000)) {
185a1bca57fSLei Li             error_setg(errp, "Time %" PRId64 " is too large", time_ns);
186a1bca57fSLei Li             return;
187a1bca57fSLei Li         }
188a1bca57fSLei Li 
189a1bca57fSLei Li         tv.tv_sec = time_ns / 1000000000;
190a1bca57fSLei Li         tv.tv_usec = (time_ns % 1000000000) / 1000;
19100d2f370SMarc-André Lureau         g_date_set_time_t(&date, tv.tv_sec);
19200d2f370SMarc-André Lureau         if (date.year < 1970 || date.year >= 2070) {
19300d2f370SMarc-André Lureau             error_setg_errno(errp, errno, "Invalid time");
19400d2f370SMarc-André Lureau             return;
19500d2f370SMarc-André Lureau         }
196a1bca57fSLei Li 
197a1bca57fSLei Li         ret = settimeofday(&tv, NULL);
198a1bca57fSLei Li         if (ret < 0) {
199a1bca57fSLei Li             error_setg_errno(errp, errno, "Failed to set time to guest");
200a1bca57fSLei Li             return;
201a1bca57fSLei Li         }
2022c958923SMichal Privoznik     }
203a1bca57fSLei Li 
2042c958923SMichal Privoznik     /* Now, if user has passed a time to set and the system time is set, we
2052c958923SMichal Privoznik      * just need to synchronize the hardware clock. However, if no time was
2062c958923SMichal Privoznik      * passed, user is requesting the opposite: set the system time from the
2071634df56SAmos Kong      * hardware clock (RTC). */
208a1bca57fSLei Li     pid = fork();
209a1bca57fSLei Li     if (pid == 0) {
210a1bca57fSLei Li         setsid();
211a1bca57fSLei Li         reopen_fd_to_null(0);
212a1bca57fSLei Li         reopen_fd_to_null(1);
213a1bca57fSLei Li         reopen_fd_to_null(2);
214a1bca57fSLei Li 
2152c958923SMichal Privoznik         /* Use '/sbin/hwclock -w' to set RTC from the system time,
2162c958923SMichal Privoznik          * or '/sbin/hwclock -s' to set the system time from RTC. */
217fcc41961SMarc-André Lureau         execl(hwclock_path, "hwclock", has_time ? "-w" : "-s", NULL);
218a1bca57fSLei Li         _exit(EXIT_FAILURE);
219a1bca57fSLei Li     } else if (pid < 0) {
220a1bca57fSLei Li         error_setg_errno(errp, errno, "failed to create child process");
221a1bca57fSLei Li         return;
222a1bca57fSLei Li     }
223a1bca57fSLei Li 
224a1bca57fSLei Li     ga_wait_child(pid, &status, &local_err);
22584d18f06SMarkus Armbruster     if (local_err) {
226a1bca57fSLei Li         error_propagate(errp, local_err);
227a1bca57fSLei Li         return;
228a1bca57fSLei Li     }
229a1bca57fSLei Li 
230a1bca57fSLei Li     if (!WIFEXITED(status)) {
231a1bca57fSLei Li         error_setg(errp, "child process has terminated abnormally");
232a1bca57fSLei Li         return;
233a1bca57fSLei Li     }
234a1bca57fSLei Li 
235a1bca57fSLei Li     if (WEXITSTATUS(status)) {
236a1bca57fSLei Li         error_setg(errp, "hwclock failed to set hardware clock to system time");
237a1bca57fSLei Li         return;
238a1bca57fSLei Li     }
239a1bca57fSLei Li }
240a1bca57fSLei Li 
241895b00f6SMarc-André Lureau typedef enum {
242895b00f6SMarc-André Lureau     RW_STATE_NEW,
243895b00f6SMarc-André Lureau     RW_STATE_READING,
244895b00f6SMarc-André Lureau     RW_STATE_WRITING,
245895b00f6SMarc-André Lureau } RwState;
246895b00f6SMarc-André Lureau 
2475d3586b8SPhilippe Mathieu-Daudé struct GuestFileHandle {
248c216e5adSMichael Roth     uint64_t id;
249c216e5adSMichael Roth     FILE *fh;
250895b00f6SMarc-André Lureau     RwState state;
251c216e5adSMichael Roth     QTAILQ_ENTRY(GuestFileHandle) next;
2525d3586b8SPhilippe Mathieu-Daudé };
253c216e5adSMichael Roth 
254c216e5adSMichael Roth static struct {
255c216e5adSMichael Roth     QTAILQ_HEAD(, GuestFileHandle) filehandles;
256b4fe97c8SDenis V. Lunev } guest_file_state = {
257b4fe97c8SDenis V. Lunev     .filehandles = QTAILQ_HEAD_INITIALIZER(guest_file_state.filehandles),
258b4fe97c8SDenis V. Lunev };
259c216e5adSMichael Roth 
26039097dafSMichael Roth static int64_t guest_file_handle_add(FILE *fh, Error **errp)
261c216e5adSMichael Roth {
262c216e5adSMichael Roth     GuestFileHandle *gfh;
26339097dafSMichael Roth     int64_t handle;
26439097dafSMichael Roth 
26539097dafSMichael Roth     handle = ga_get_fd_handle(ga_state, errp);
266a903f40cSMarkus Armbruster     if (handle < 0) {
267a903f40cSMarkus Armbruster         return -1;
26839097dafSMichael Roth     }
269c216e5adSMichael Roth 
270f3a06403SMarkus Armbruster     gfh = g_new0(GuestFileHandle, 1);
27139097dafSMichael Roth     gfh->id = handle;
272c216e5adSMichael Roth     gfh->fh = fh;
273c216e5adSMichael Roth     QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next);
27439097dafSMichael Roth 
27539097dafSMichael Roth     return handle;
276c216e5adSMichael Roth }
277c216e5adSMichael Roth 
2785d3586b8SPhilippe Mathieu-Daudé GuestFileHandle *guest_file_handle_find(int64_t id, Error **errp)
279c216e5adSMichael Roth {
280c216e5adSMichael Roth     GuestFileHandle *gfh;
281c216e5adSMichael Roth 
282c216e5adSMichael Roth     QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next)
283c216e5adSMichael Roth     {
284c216e5adSMichael Roth         if (gfh->id == id) {
285c216e5adSMichael Roth             return gfh;
286c216e5adSMichael Roth         }
287c216e5adSMichael Roth     }
288c216e5adSMichael Roth 
28977dbc81bSMarkus Armbruster     error_setg(errp, "handle '%" PRId64 "' has not been found", id);
290c216e5adSMichael Roth     return NULL;
291c216e5adSMichael Roth }
292c216e5adSMichael Roth 
293c689b4f1SLaszlo Ersek typedef const char * const ccpc;
294c689b4f1SLaszlo Ersek 
2958fe6bbcaSLaszlo Ersek #ifndef O_BINARY
2968fe6bbcaSLaszlo Ersek #define O_BINARY 0
2978fe6bbcaSLaszlo Ersek #endif
2988fe6bbcaSLaszlo Ersek 
299c689b4f1SLaszlo Ersek /* http://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html */
300c689b4f1SLaszlo Ersek static const struct {
301c689b4f1SLaszlo Ersek     ccpc *forms;
302c689b4f1SLaszlo Ersek     int oflag_base;
303c689b4f1SLaszlo Ersek } guest_file_open_modes[] = {
3048fe6bbcaSLaszlo Ersek     { (ccpc[]){ "r",          NULL }, O_RDONLY                                 },
3058fe6bbcaSLaszlo Ersek     { (ccpc[]){ "rb",         NULL }, O_RDONLY                      | O_BINARY },
3068fe6bbcaSLaszlo Ersek     { (ccpc[]){ "w",          NULL }, O_WRONLY | O_CREAT | O_TRUNC             },
3078fe6bbcaSLaszlo Ersek     { (ccpc[]){ "wb",         NULL }, O_WRONLY | O_CREAT | O_TRUNC  | O_BINARY },
3088fe6bbcaSLaszlo Ersek     { (ccpc[]){ "a",          NULL }, O_WRONLY | O_CREAT | O_APPEND            },
3098fe6bbcaSLaszlo Ersek     { (ccpc[]){ "ab",         NULL }, O_WRONLY | O_CREAT | O_APPEND | O_BINARY },
3108fe6bbcaSLaszlo Ersek     { (ccpc[]){ "r+",         NULL }, O_RDWR                                   },
3118fe6bbcaSLaszlo Ersek     { (ccpc[]){ "rb+", "r+b", NULL }, O_RDWR                        | O_BINARY },
3128fe6bbcaSLaszlo Ersek     { (ccpc[]){ "w+",         NULL }, O_RDWR   | O_CREAT | O_TRUNC             },
3138fe6bbcaSLaszlo Ersek     { (ccpc[]){ "wb+", "w+b", NULL }, O_RDWR   | O_CREAT | O_TRUNC  | O_BINARY },
3148fe6bbcaSLaszlo Ersek     { (ccpc[]){ "a+",         NULL }, O_RDWR   | O_CREAT | O_APPEND            },
3158fe6bbcaSLaszlo Ersek     { (ccpc[]){ "ab+", "a+b", NULL }, O_RDWR   | O_CREAT | O_APPEND | O_BINARY }
316c689b4f1SLaszlo Ersek };
317c689b4f1SLaszlo Ersek 
318c689b4f1SLaszlo Ersek static int
31977dbc81bSMarkus Armbruster find_open_flag(const char *mode_str, Error **errp)
320c689b4f1SLaszlo Ersek {
321c689b4f1SLaszlo Ersek     unsigned mode;
322c689b4f1SLaszlo Ersek 
323c689b4f1SLaszlo Ersek     for (mode = 0; mode < ARRAY_SIZE(guest_file_open_modes); ++mode) {
324c689b4f1SLaszlo Ersek         ccpc *form;
325c689b4f1SLaszlo Ersek 
326c689b4f1SLaszlo Ersek         form = guest_file_open_modes[mode].forms;
327c689b4f1SLaszlo Ersek         while (*form != NULL && strcmp(*form, mode_str) != 0) {
328c689b4f1SLaszlo Ersek             ++form;
329c689b4f1SLaszlo Ersek         }
330c689b4f1SLaszlo Ersek         if (*form != NULL) {
331c689b4f1SLaszlo Ersek             break;
332c689b4f1SLaszlo Ersek         }
333c689b4f1SLaszlo Ersek     }
334c689b4f1SLaszlo Ersek 
335c689b4f1SLaszlo Ersek     if (mode == ARRAY_SIZE(guest_file_open_modes)) {
33677dbc81bSMarkus Armbruster         error_setg(errp, "invalid file open mode '%s'", mode_str);
337c689b4f1SLaszlo Ersek         return -1;
338c689b4f1SLaszlo Ersek     }
339c689b4f1SLaszlo Ersek     return guest_file_open_modes[mode].oflag_base | O_NOCTTY | O_NONBLOCK;
340c689b4f1SLaszlo Ersek }
341c689b4f1SLaszlo Ersek 
342c689b4f1SLaszlo Ersek #define DEFAULT_NEW_FILE_MODE (S_IRUSR | S_IWUSR | \
343c689b4f1SLaszlo Ersek                                S_IRGRP | S_IWGRP | \
344c689b4f1SLaszlo Ersek                                S_IROTH | S_IWOTH)
345c689b4f1SLaszlo Ersek 
346c689b4f1SLaszlo Ersek static FILE *
34777dbc81bSMarkus Armbruster safe_open_or_create(const char *path, const char *mode, Error **errp)
348c689b4f1SLaszlo Ersek {
349c689b4f1SLaszlo Ersek     int oflag;
35069f56c14SMarc-André Lureau     int fd = -1;
35169f56c14SMarc-André Lureau     FILE *f = NULL;
352c689b4f1SLaszlo Ersek 
35369f56c14SMarc-André Lureau     oflag = find_open_flag(mode, errp);
35469f56c14SMarc-André Lureau     if (oflag < 0) {
35569f56c14SMarc-André Lureau         goto end;
35669f56c14SMarc-André Lureau     }
357c689b4f1SLaszlo Ersek 
358c689b4f1SLaszlo Ersek     /* If the caller wants / allows creation of a new file, we implement it
359c689b4f1SLaszlo Ersek      * with a two step process: open() + (open() / fchmod()).
360c689b4f1SLaszlo Ersek      *
361c689b4f1SLaszlo Ersek      * First we insist on creating the file exclusively as a new file. If
362c689b4f1SLaszlo Ersek      * that succeeds, we're free to set any file-mode bits on it. (The
363c689b4f1SLaszlo Ersek      * motivation is that we want to set those file-mode bits independently
364c689b4f1SLaszlo Ersek      * of the current umask.)
365c689b4f1SLaszlo Ersek      *
366c689b4f1SLaszlo Ersek      * If the exclusive creation fails because the file already exists
367c689b4f1SLaszlo Ersek      * (EEXIST is not possible for any other reason), we just attempt to
368c689b4f1SLaszlo Ersek      * open the file, but in this case we won't be allowed to change the
369c689b4f1SLaszlo Ersek      * file-mode bits on the preexistent file.
370c689b4f1SLaszlo Ersek      *
371c689b4f1SLaszlo Ersek      * The pathname should never disappear between the two open()s in
372c689b4f1SLaszlo Ersek      * practice. If it happens, then someone very likely tried to race us.
373c689b4f1SLaszlo Ersek      * In this case just go ahead and report the ENOENT from the second
374c689b4f1SLaszlo Ersek      * open() to the caller.
375c689b4f1SLaszlo Ersek      *
376c689b4f1SLaszlo Ersek      * If the caller wants to open a preexistent file, then the first
377c689b4f1SLaszlo Ersek      * open() is decisive and its third argument is ignored, and the second
378c689b4f1SLaszlo Ersek      * open() and the fchmod() are never called.
379c689b4f1SLaszlo Ersek      */
3801a89a17bSMarc-André Lureau     fd = qga_open_cloexec(path, oflag | ((oflag & O_CREAT) ? O_EXCL : 0), 0);
381c689b4f1SLaszlo Ersek     if (fd == -1 && errno == EEXIST) {
382c689b4f1SLaszlo Ersek         oflag &= ~(unsigned)O_CREAT;
3831a89a17bSMarc-André Lureau         fd = qga_open_cloexec(path, oflag, 0);
384c689b4f1SLaszlo Ersek     }
385c689b4f1SLaszlo Ersek     if (fd == -1) {
38669f56c14SMarc-André Lureau         error_setg_errno(errp, errno,
38769f56c14SMarc-André Lureau                          "failed to open file '%s' (mode: '%s')",
38869f56c14SMarc-André Lureau                          path, mode);
38969f56c14SMarc-André Lureau         goto end;
39069f56c14SMarc-André Lureau     }
39169f56c14SMarc-André Lureau 
392c689b4f1SLaszlo Ersek     if ((oflag & O_CREAT) && fchmod(fd, DEFAULT_NEW_FILE_MODE) == -1) {
39369f56c14SMarc-André Lureau         error_setg_errno(errp, errno, "failed to set permission "
394c689b4f1SLaszlo Ersek                          "0%03o on new file '%s' (mode: '%s')",
395c689b4f1SLaszlo Ersek                          (unsigned)DEFAULT_NEW_FILE_MODE, path, mode);
39669f56c14SMarc-André Lureau         goto end;
39769f56c14SMarc-André Lureau     }
398c689b4f1SLaszlo Ersek 
399c689b4f1SLaszlo Ersek     f = fdopen(fd, mode);
400c689b4f1SLaszlo Ersek     if (f == NULL) {
40169f56c14SMarc-André Lureau         error_setg_errno(errp, errno, "failed to associate stdio stream with "
40269f56c14SMarc-André Lureau                          "file descriptor %d, file '%s' (mode: '%s')",
40369f56c14SMarc-André Lureau                          fd, path, mode);
404c689b4f1SLaszlo Ersek     }
405c689b4f1SLaszlo Ersek 
40669f56c14SMarc-André Lureau end:
40769f56c14SMarc-André Lureau     if (f == NULL && fd != -1) {
408c689b4f1SLaszlo Ersek         close(fd);
4092b720018SLaszlo Ersek         if (oflag & O_CREAT) {
4102b720018SLaszlo Ersek             unlink(path);
4112b720018SLaszlo Ersek         }
412c689b4f1SLaszlo Ersek     }
41369f56c14SMarc-André Lureau     return f;
414c689b4f1SLaszlo Ersek }
415c689b4f1SLaszlo Ersek 
41677dbc81bSMarkus Armbruster int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode,
41777dbc81bSMarkus Armbruster                             Error **errp)
418c216e5adSMichael Roth {
419c216e5adSMichael Roth     FILE *fh;
420c689b4f1SLaszlo Ersek     Error *local_err = NULL;
42185b6f6f5SSimon Zolin     int64_t handle;
422c216e5adSMichael Roth 
423c216e5adSMichael Roth     if (!has_mode) {
424c216e5adSMichael Roth         mode = "r";
425c216e5adSMichael Roth     }
426c216e5adSMichael Roth     slog("guest-file-open called, filepath: %s, mode: %s", path, mode);
427c689b4f1SLaszlo Ersek     fh = safe_open_or_create(path, mode, &local_err);
428c689b4f1SLaszlo Ersek     if (local_err != NULL) {
42977dbc81bSMarkus Armbruster         error_propagate(errp, local_err);
430c216e5adSMichael Roth         return -1;
431c216e5adSMichael Roth     }
432c216e5adSMichael Roth 
433c216e5adSMichael Roth     /* set fd non-blocking to avoid common use cases (like reading from a
434c216e5adSMichael Roth      * named pipe) from hanging the agent
435c216e5adSMichael Roth      */
436b0a8f9adSMarc-André Lureau     if (!g_unix_set_fd_nonblocking(fileno(fh), true, NULL)) {
437b0a8f9adSMarc-André Lureau         fclose(fh);
438b0a8f9adSMarc-André Lureau         error_setg_errno(errp, errno, "Failed to set FD nonblocking");
439b0a8f9adSMarc-André Lureau         return -1;
440b0a8f9adSMarc-André Lureau     }
441c216e5adSMichael Roth 
44277dbc81bSMarkus Armbruster     handle = guest_file_handle_add(fh, errp);
443a903f40cSMarkus Armbruster     if (handle < 0) {
44439097dafSMichael Roth         fclose(fh);
44539097dafSMichael Roth         return -1;
44639097dafSMichael Roth     }
44739097dafSMichael Roth 
448d607a523SStefan Weil     slog("guest-file-open, handle: %" PRId64, handle);
44939097dafSMichael Roth     return handle;
450c216e5adSMichael Roth }
451c216e5adSMichael Roth 
45277dbc81bSMarkus Armbruster void qmp_guest_file_close(int64_t handle, Error **errp)
453c216e5adSMichael Roth {
45477dbc81bSMarkus Armbruster     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
455c216e5adSMichael Roth     int ret;
456c216e5adSMichael Roth 
457d607a523SStefan Weil     slog("guest-file-close called, handle: %" PRId64, handle);
458c216e5adSMichael Roth     if (!gfh) {
459c216e5adSMichael Roth         return;
460c216e5adSMichael Roth     }
461c216e5adSMichael Roth 
462c216e5adSMichael Roth     ret = fclose(gfh->fh);
4633ac4b7c5SLuiz Capitulino     if (ret == EOF) {
46477dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to close handle");
465c216e5adSMichael Roth         return;
466c216e5adSMichael Roth     }
467c216e5adSMichael Roth 
468c216e5adSMichael Roth     QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next);
469c216e5adSMichael Roth     g_free(gfh);
470c216e5adSMichael Roth }
471c216e5adSMichael Roth 
472ead83a13SPhilippe Mathieu-Daudé GuestFileRead *guest_file_read_unsafe(GuestFileHandle *gfh,
47377dbc81bSMarkus Armbruster                                       int64_t count, Error **errp)
474c216e5adSMichael Roth {
475c216e5adSMichael Roth     GuestFileRead *read_data = NULL;
476c216e5adSMichael Roth     guchar *buf;
477ead83a13SPhilippe Mathieu-Daudé     FILE *fh = gfh->fh;
478c216e5adSMichael Roth     size_t read_count;
479c216e5adSMichael Roth 
480895b00f6SMarc-André Lureau     /* explicitly flush when switching from writing to reading */
481895b00f6SMarc-André Lureau     if (gfh->state == RW_STATE_WRITING) {
482895b00f6SMarc-André Lureau         int ret = fflush(fh);
483895b00f6SMarc-André Lureau         if (ret == EOF) {
484895b00f6SMarc-André Lureau             error_setg_errno(errp, errno, "failed to flush file");
485895b00f6SMarc-André Lureau             return NULL;
486895b00f6SMarc-André Lureau         }
487895b00f6SMarc-André Lureau         gfh->state = RW_STATE_NEW;
488895b00f6SMarc-André Lureau     }
489895b00f6SMarc-André Lureau 
490c216e5adSMichael Roth     buf = g_malloc0(count + 1);
491c216e5adSMichael Roth     read_count = fread(buf, 1, count, fh);
492c216e5adSMichael Roth     if (ferror(fh)) {
49377dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to read file");
494c216e5adSMichael Roth     } else {
495c216e5adSMichael Roth         buf[read_count] = 0;
496f3a06403SMarkus Armbruster         read_data = g_new0(GuestFileRead, 1);
497c216e5adSMichael Roth         read_data->count = read_count;
498c216e5adSMichael Roth         read_data->eof = feof(fh);
499c216e5adSMichael Roth         if (read_count) {
500c216e5adSMichael Roth             read_data->buf_b64 = g_base64_encode(buf, read_count);
501c216e5adSMichael Roth         }
502895b00f6SMarc-André Lureau         gfh->state = RW_STATE_READING;
503c216e5adSMichael Roth     }
504c216e5adSMichael Roth     g_free(buf);
505c216e5adSMichael Roth     clearerr(fh);
506c216e5adSMichael Roth 
507c216e5adSMichael Roth     return read_data;
508c216e5adSMichael Roth }
509c216e5adSMichael Roth 
510c216e5adSMichael Roth GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64,
51177dbc81bSMarkus Armbruster                                      bool has_count, int64_t count,
51277dbc81bSMarkus Armbruster                                      Error **errp)
513c216e5adSMichael Roth {
514c216e5adSMichael Roth     GuestFileWrite *write_data = NULL;
515c216e5adSMichael Roth     guchar *buf;
516c216e5adSMichael Roth     gsize buf_len;
517c216e5adSMichael Roth     int write_count;
51877dbc81bSMarkus Armbruster     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
519c216e5adSMichael Roth     FILE *fh;
520c216e5adSMichael Roth 
521c216e5adSMichael Roth     if (!gfh) {
522c216e5adSMichael Roth         return NULL;
523c216e5adSMichael Roth     }
524c216e5adSMichael Roth 
525c216e5adSMichael Roth     fh = gfh->fh;
526895b00f6SMarc-André Lureau 
527895b00f6SMarc-André Lureau     if (gfh->state == RW_STATE_READING) {
528895b00f6SMarc-André Lureau         int ret = fseek(fh, 0, SEEK_CUR);
529895b00f6SMarc-André Lureau         if (ret == -1) {
530895b00f6SMarc-André Lureau             error_setg_errno(errp, errno, "failed to seek file");
531895b00f6SMarc-André Lureau             return NULL;
532895b00f6SMarc-André Lureau         }
533895b00f6SMarc-André Lureau         gfh->state = RW_STATE_NEW;
534895b00f6SMarc-André Lureau     }
535895b00f6SMarc-André Lureau 
536920639caSDaniel P. Berrange     buf = qbase64_decode(buf_b64, -1, &buf_len, errp);
537920639caSDaniel P. Berrange     if (!buf) {
538920639caSDaniel P. Berrange         return NULL;
539920639caSDaniel P. Berrange     }
540c216e5adSMichael Roth 
541c216e5adSMichael Roth     if (!has_count) {
542c216e5adSMichael Roth         count = buf_len;
543c216e5adSMichael Roth     } else if (count < 0 || count > buf_len) {
54477dbc81bSMarkus Armbruster         error_setg(errp, "value '%" PRId64 "' is invalid for argument count",
545db3edb66SLuiz Capitulino                    count);
546c216e5adSMichael Roth         g_free(buf);
547c216e5adSMichael Roth         return NULL;
548c216e5adSMichael Roth     }
549c216e5adSMichael Roth 
550c216e5adSMichael Roth     write_count = fwrite(buf, 1, count, fh);
551c216e5adSMichael Roth     if (ferror(fh)) {
55277dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to write to file");
553d607a523SStefan Weil         slog("guest-file-write failed, handle: %" PRId64, handle);
554c216e5adSMichael Roth     } else {
555f3a06403SMarkus Armbruster         write_data = g_new0(GuestFileWrite, 1);
556c216e5adSMichael Roth         write_data->count = write_count;
557c216e5adSMichael Roth         write_data->eof = feof(fh);
558895b00f6SMarc-André Lureau         gfh->state = RW_STATE_WRITING;
559c216e5adSMichael Roth     }
560c216e5adSMichael Roth     g_free(buf);
561c216e5adSMichael Roth     clearerr(fh);
562c216e5adSMichael Roth 
563c216e5adSMichael Roth     return write_data;
564c216e5adSMichael Roth }
565c216e5adSMichael Roth 
566c216e5adSMichael Roth struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset,
5670b4b4938SEric Blake                                           GuestFileWhence *whence_code,
5680b4b4938SEric Blake                                           Error **errp)
569c216e5adSMichael Roth {
57077dbc81bSMarkus Armbruster     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
571c216e5adSMichael Roth     GuestFileSeek *seek_data = NULL;
572c216e5adSMichael Roth     FILE *fh;
573c216e5adSMichael Roth     int ret;
5740a982b1bSEric Blake     int whence;
5750b4b4938SEric Blake     Error *err = NULL;
576c216e5adSMichael Roth 
577c216e5adSMichael Roth     if (!gfh) {
578c216e5adSMichael Roth         return NULL;
579c216e5adSMichael Roth     }
580c216e5adSMichael Roth 
5810a982b1bSEric Blake     /* We stupidly exposed 'whence':'int' in our qapi */
5820b4b4938SEric Blake     whence = ga_parse_whence(whence_code, &err);
5830b4b4938SEric Blake     if (err) {
5840b4b4938SEric Blake         error_propagate(errp, err);
5850a982b1bSEric Blake         return NULL;
5860a982b1bSEric Blake     }
5870a982b1bSEric Blake 
588c216e5adSMichael Roth     fh = gfh->fh;
589c216e5adSMichael Roth     ret = fseek(fh, offset, whence);
590c216e5adSMichael Roth     if (ret == -1) {
59177dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to seek file");
592895b00f6SMarc-André Lureau         if (errno == ESPIPE) {
593895b00f6SMarc-André Lureau             /* file is non-seekable, stdio shouldn't be buffering anyways */
594895b00f6SMarc-André Lureau             gfh->state = RW_STATE_NEW;
595895b00f6SMarc-André Lureau         }
596c216e5adSMichael Roth     } else {
59710b7c5ddSMarkus Armbruster         seek_data = g_new0(GuestFileSeek, 1);
598c216e5adSMichael Roth         seek_data->position = ftell(fh);
599c216e5adSMichael Roth         seek_data->eof = feof(fh);
600895b00f6SMarc-André Lureau         gfh->state = RW_STATE_NEW;
601c216e5adSMichael Roth     }
602c216e5adSMichael Roth     clearerr(fh);
603c216e5adSMichael Roth 
604c216e5adSMichael Roth     return seek_data;
605c216e5adSMichael Roth }
606c216e5adSMichael Roth 
60777dbc81bSMarkus Armbruster void qmp_guest_file_flush(int64_t handle, Error **errp)
608c216e5adSMichael Roth {
60977dbc81bSMarkus Armbruster     GuestFileHandle *gfh = guest_file_handle_find(handle, errp);
610c216e5adSMichael Roth     FILE *fh;
611c216e5adSMichael Roth     int ret;
612c216e5adSMichael Roth 
613c216e5adSMichael Roth     if (!gfh) {
614c216e5adSMichael Roth         return;
615c216e5adSMichael Roth     }
616c216e5adSMichael Roth 
617c216e5adSMichael Roth     fh = gfh->fh;
618c216e5adSMichael Roth     ret = fflush(fh);
619c216e5adSMichael Roth     if (ret == EOF) {
62077dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to flush file");
621895b00f6SMarc-André Lureau     } else {
622895b00f6SMarc-André Lureau         gfh->state = RW_STATE_NEW;
623c216e5adSMichael Roth     }
624c216e5adSMichael Roth }
625c216e5adSMichael Roth 
626e72c3f2eSMichael Roth /* linux-specific implementations. avoid this if at all possible. */
627e72c3f2eSMichael Roth #if defined(__linux__)
628e72c3f2eSMichael Roth 
629eab5fd59SPaolo Bonzini #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM)
630*518b0d80SAlexander Ivanov void free_fs_mount_list(FsMountList *mounts)
631c216e5adSMichael Roth {
632af02203fSPaolo Bonzini      FsMount *mount, *temp;
633c216e5adSMichael Roth 
6349e8aded4SMichael Roth      if (!mounts) {
6359e8aded4SMichael Roth          return;
6369e8aded4SMichael Roth      }
6379e8aded4SMichael Roth 
6389e8aded4SMichael Roth      QTAILQ_FOREACH_SAFE(mount, mounts, next, temp) {
6399e8aded4SMichael Roth          QTAILQ_REMOVE(mounts, mount, next);
640c216e5adSMichael Roth          g_free(mount->dirname);
641c216e5adSMichael Roth          g_free(mount->devtype);
642c216e5adSMichael Roth          g_free(mount);
643c216e5adSMichael Roth      }
6449e8aded4SMichael Roth }
645eab5fd59SPaolo Bonzini #endif
646eab5fd59SPaolo Bonzini 
647eab5fd59SPaolo Bonzini #if defined(CONFIG_FSFREEZE)
648c216e5adSMichael Roth 
64946d4c572STomoki Sekiyama static char *get_pci_driver(char const *syspath, int pathlen, Error **errp)
65046d4c572STomoki Sekiyama {
65146d4c572STomoki Sekiyama     char *path;
65246d4c572STomoki Sekiyama     char *dpath;
65346d4c572STomoki Sekiyama     char *driver = NULL;
65446d4c572STomoki Sekiyama     char buf[PATH_MAX];
65546d4c572STomoki Sekiyama     ssize_t len;
65646d4c572STomoki Sekiyama 
65746d4c572STomoki Sekiyama     path = g_strndup(syspath, pathlen);
65846d4c572STomoki Sekiyama     dpath = g_strdup_printf("%s/driver", path);
65946d4c572STomoki Sekiyama     len = readlink(dpath, buf, sizeof(buf) - 1);
66046d4c572STomoki Sekiyama     if (len != -1) {
66146d4c572STomoki Sekiyama         buf[len] = 0;
6623e015d81SJulia Suvorova         driver = g_path_get_basename(buf);
66346d4c572STomoki Sekiyama     }
66446d4c572STomoki Sekiyama     g_free(dpath);
66546d4c572STomoki Sekiyama     g_free(path);
66646d4c572STomoki Sekiyama     return driver;
66746d4c572STomoki Sekiyama }
66846d4c572STomoki Sekiyama 
66946d4c572STomoki Sekiyama static int compare_uint(const void *_a, const void *_b)
67046d4c572STomoki Sekiyama {
67146d4c572STomoki Sekiyama     unsigned int a = *(unsigned int *)_a;
67246d4c572STomoki Sekiyama     unsigned int b = *(unsigned int *)_b;
67346d4c572STomoki Sekiyama 
67446d4c572STomoki Sekiyama     return a < b ? -1 : a > b ? 1 : 0;
67546d4c572STomoki Sekiyama }
67646d4c572STomoki Sekiyama 
67746d4c572STomoki Sekiyama /* Walk the specified sysfs and build a sorted list of host or ata numbers */
67846d4c572STomoki Sekiyama static int build_hosts(char const *syspath, char const *host, bool ata,
67946d4c572STomoki Sekiyama                        unsigned int *hosts, int hosts_max, Error **errp)
68046d4c572STomoki Sekiyama {
68146d4c572STomoki Sekiyama     char *path;
68246d4c572STomoki Sekiyama     DIR *dir;
68346d4c572STomoki Sekiyama     struct dirent *entry;
68446d4c572STomoki Sekiyama     int i = 0;
68546d4c572STomoki Sekiyama 
68646d4c572STomoki Sekiyama     path = g_strndup(syspath, host - syspath);
68746d4c572STomoki Sekiyama     dir = opendir(path);
68846d4c572STomoki Sekiyama     if (!dir) {
68946d4c572STomoki Sekiyama         error_setg_errno(errp, errno, "opendir(\"%s\")", path);
69046d4c572STomoki Sekiyama         g_free(path);
69146d4c572STomoki Sekiyama         return -1;
69246d4c572STomoki Sekiyama     }
69346d4c572STomoki Sekiyama 
69446d4c572STomoki Sekiyama     while (i < hosts_max) {
69546d4c572STomoki Sekiyama         entry = readdir(dir);
69646d4c572STomoki Sekiyama         if (!entry) {
69746d4c572STomoki Sekiyama             break;
69846d4c572STomoki Sekiyama         }
69946d4c572STomoki Sekiyama         if (ata && sscanf(entry->d_name, "ata%d", hosts + i) == 1) {
70046d4c572STomoki Sekiyama             ++i;
70146d4c572STomoki Sekiyama         } else if (!ata && sscanf(entry->d_name, "host%d", hosts + i) == 1) {
70246d4c572STomoki Sekiyama             ++i;
70346d4c572STomoki Sekiyama         }
70446d4c572STomoki Sekiyama     }
70546d4c572STomoki Sekiyama 
70646d4c572STomoki Sekiyama     qsort(hosts, i, sizeof(hosts[0]), compare_uint);
70746d4c572STomoki Sekiyama 
70846d4c572STomoki Sekiyama     g_free(path);
70946d4c572STomoki Sekiyama     closedir(dir);
71046d4c572STomoki Sekiyama     return i;
71146d4c572STomoki Sekiyama }
71246d4c572STomoki Sekiyama 
713d9fe4f0fSThomas Huth /*
714d9fe4f0fSThomas Huth  * Store disk device info for devices on the PCI bus.
715d9fe4f0fSThomas Huth  * Returns true if information has been stored, or false for failure.
716d9fe4f0fSThomas Huth  */
717d9fe4f0fSThomas Huth static bool build_guest_fsinfo_for_pci_dev(char const *syspath,
718d9fe4f0fSThomas Huth                                            GuestDiskAddress *disk,
71946d4c572STomoki Sekiyama                                            Error **errp)
72046d4c572STomoki Sekiyama {
72146d4c572STomoki Sekiyama     unsigned int pci[4], host, hosts[8], tgt[3];
72246d4c572STomoki Sekiyama     int i, nhosts = 0, pcilen;
723d9fe4f0fSThomas Huth     GuestPCIAddress *pciaddr = disk->pci_controller;
72446d4c572STomoki Sekiyama     bool has_ata = false, has_host = false, has_tgt = false;
72546d4c572STomoki Sekiyama     char *p, *q, *driver = NULL;
726d9fe4f0fSThomas Huth     bool ret = false;
72746d4c572STomoki Sekiyama 
72846d4c572STomoki Sekiyama     p = strstr(syspath, "/devices/pci");
72946d4c572STomoki Sekiyama     if (!p || sscanf(p + 12, "%*x:%*x/%x:%x:%x.%x%n",
73046d4c572STomoki Sekiyama                      pci, pci + 1, pci + 2, pci + 3, &pcilen) < 4) {
731743c71d0SMarc-André Lureau         g_debug("only pci device is supported: sysfs path '%s'", syspath);
732d9fe4f0fSThomas Huth         return false;
73346d4c572STomoki Sekiyama     }
73446d4c572STomoki Sekiyama 
735743c71d0SMarc-André Lureau     p += 12 + pcilen;
736743c71d0SMarc-André Lureau     while (true) {
737743c71d0SMarc-André Lureau         driver = get_pci_driver(syspath, p - syspath, errp);
738743c71d0SMarc-André Lureau         if (driver && (g_str_equal(driver, "ata_piix") ||
739743c71d0SMarc-André Lureau                        g_str_equal(driver, "sym53c8xx") ||
740743c71d0SMarc-André Lureau                        g_str_equal(driver, "virtio-pci") ||
741d48f61c8Szhenwei pi                        g_str_equal(driver, "ahci") ||
742d48f61c8Szhenwei pi                        g_str_equal(driver, "nvme"))) {
743743c71d0SMarc-André Lureau             break;
744743c71d0SMarc-André Lureau         }
745743c71d0SMarc-André Lureau 
746bb23a736SMarc-André Lureau         g_free(driver);
747743c71d0SMarc-André Lureau         if (sscanf(p, "/%x:%x:%x.%x%n",
748743c71d0SMarc-André Lureau                           pci, pci + 1, pci + 2, pci + 3, &pcilen) == 4) {
749743c71d0SMarc-André Lureau             p += pcilen;
750743c71d0SMarc-André Lureau             continue;
751743c71d0SMarc-André Lureau         }
752743c71d0SMarc-André Lureau 
753743c71d0SMarc-André Lureau         g_debug("unsupported driver or sysfs path '%s'", syspath);
754d9fe4f0fSThomas Huth         return false;
75546d4c572STomoki Sekiyama     }
75646d4c572STomoki Sekiyama 
75746d4c572STomoki Sekiyama     p = strstr(syspath, "/target");
75846d4c572STomoki Sekiyama     if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
75946d4c572STomoki Sekiyama                     tgt, tgt + 1, tgt + 2) == 3) {
76046d4c572STomoki Sekiyama         has_tgt = true;
76146d4c572STomoki Sekiyama     }
76246d4c572STomoki Sekiyama 
76346d4c572STomoki Sekiyama     p = strstr(syspath, "/ata");
76446d4c572STomoki Sekiyama     if (p) {
76546d4c572STomoki Sekiyama         q = p + 4;
76646d4c572STomoki Sekiyama         has_ata = true;
76746d4c572STomoki Sekiyama     } else {
76846d4c572STomoki Sekiyama         p = strstr(syspath, "/host");
76946d4c572STomoki Sekiyama         q = p + 5;
77046d4c572STomoki Sekiyama     }
77146d4c572STomoki Sekiyama     if (p && sscanf(q, "%u", &host) == 1) {
77246d4c572STomoki Sekiyama         has_host = true;
77346d4c572STomoki Sekiyama         nhosts = build_hosts(syspath, p, has_ata, hosts,
77401a6df1bSPhilippe Mathieu-Daudé                              ARRAY_SIZE(hosts), errp);
77546d4c572STomoki Sekiyama         if (nhosts < 0) {
77646d4c572STomoki Sekiyama             goto cleanup;
77746d4c572STomoki Sekiyama         }
77846d4c572STomoki Sekiyama     }
77946d4c572STomoki Sekiyama 
78046d4c572STomoki Sekiyama     pciaddr->domain = pci[0];
78146d4c572STomoki Sekiyama     pciaddr->bus = pci[1];
78246d4c572STomoki Sekiyama     pciaddr->slot = pci[2];
78346d4c572STomoki Sekiyama     pciaddr->function = pci[3];
78446d4c572STomoki Sekiyama 
78546d4c572STomoki Sekiyama     if (strcmp(driver, "ata_piix") == 0) {
78646d4c572STomoki Sekiyama         /* a host per ide bus, target*:0:<unit>:0 */
78746d4c572STomoki Sekiyama         if (!has_host || !has_tgt) {
78846d4c572STomoki Sekiyama             g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
78946d4c572STomoki Sekiyama             goto cleanup;
79046d4c572STomoki Sekiyama         }
79146d4c572STomoki Sekiyama         for (i = 0; i < nhosts; i++) {
79246d4c572STomoki Sekiyama             if (host == hosts[i]) {
79346d4c572STomoki Sekiyama                 disk->bus_type = GUEST_DISK_BUS_TYPE_IDE;
79446d4c572STomoki Sekiyama                 disk->bus = i;
79546d4c572STomoki Sekiyama                 disk->unit = tgt[1];
79646d4c572STomoki Sekiyama                 break;
79746d4c572STomoki Sekiyama             }
79846d4c572STomoki Sekiyama         }
79946d4c572STomoki Sekiyama         if (i >= nhosts) {
80046d4c572STomoki Sekiyama             g_debug("no host for '%s' (driver '%s')", syspath, driver);
80146d4c572STomoki Sekiyama             goto cleanup;
80246d4c572STomoki Sekiyama         }
80346d4c572STomoki Sekiyama     } else if (strcmp(driver, "sym53c8xx") == 0) {
80446d4c572STomoki Sekiyama         /* scsi(LSI Logic): target*:0:<unit>:0 */
80546d4c572STomoki Sekiyama         if (!has_tgt) {
80646d4c572STomoki Sekiyama             g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
80746d4c572STomoki Sekiyama             goto cleanup;
80846d4c572STomoki Sekiyama         }
80946d4c572STomoki Sekiyama         disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
81046d4c572STomoki Sekiyama         disk->unit = tgt[1];
81146d4c572STomoki Sekiyama     } else if (strcmp(driver, "virtio-pci") == 0) {
81246d4c572STomoki Sekiyama         if (has_tgt) {
81346d4c572STomoki Sekiyama             /* virtio-scsi: target*:0:0:<unit> */
81446d4c572STomoki Sekiyama             disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
81546d4c572STomoki Sekiyama             disk->unit = tgt[2];
81646d4c572STomoki Sekiyama         } else {
81746d4c572STomoki Sekiyama             /* virtio-blk: 1 disk per 1 device */
81846d4c572STomoki Sekiyama             disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
81946d4c572STomoki Sekiyama         }
82046d4c572STomoki Sekiyama     } else if (strcmp(driver, "ahci") == 0) {
82146d4c572STomoki Sekiyama         /* ahci: 1 host per 1 unit */
82246d4c572STomoki Sekiyama         if (!has_host || !has_tgt) {
82346d4c572STomoki Sekiyama             g_debug("invalid sysfs path '%s' (driver '%s')", syspath, driver);
82446d4c572STomoki Sekiyama             goto cleanup;
82546d4c572STomoki Sekiyama         }
82646d4c572STomoki Sekiyama         for (i = 0; i < nhosts; i++) {
82746d4c572STomoki Sekiyama             if (host == hosts[i]) {
82846d4c572STomoki Sekiyama                 disk->unit = i;
82946d4c572STomoki Sekiyama                 disk->bus_type = GUEST_DISK_BUS_TYPE_SATA;
83046d4c572STomoki Sekiyama                 break;
83146d4c572STomoki Sekiyama             }
83246d4c572STomoki Sekiyama         }
83346d4c572STomoki Sekiyama         if (i >= nhosts) {
83446d4c572STomoki Sekiyama             g_debug("no host for '%s' (driver '%s')", syspath, driver);
83546d4c572STomoki Sekiyama             goto cleanup;
83646d4c572STomoki Sekiyama         }
837d48f61c8Szhenwei pi     } else if (strcmp(driver, "nvme") == 0) {
838d48f61c8Szhenwei pi         disk->bus_type = GUEST_DISK_BUS_TYPE_NVME;
83946d4c572STomoki Sekiyama     } else {
84046d4c572STomoki Sekiyama         g_debug("unknown driver '%s' (sysfs path '%s')", driver, syspath);
84146d4c572STomoki Sekiyama         goto cleanup;
84246d4c572STomoki Sekiyama     }
84346d4c572STomoki Sekiyama 
844d9fe4f0fSThomas Huth     ret = true;
84546d4c572STomoki Sekiyama 
84646d4c572STomoki Sekiyama cleanup:
84746d4c572STomoki Sekiyama     g_free(driver);
848d9fe4f0fSThomas Huth     return ret;
849d9fe4f0fSThomas Huth }
850d9fe4f0fSThomas Huth 
85123843c12SThomas Huth /*
85223843c12SThomas Huth  * Store disk device info for non-PCI virtio devices (for example s390x
85323843c12SThomas Huth  * channel I/O devices). Returns true if information has been stored, or
85423843c12SThomas Huth  * false for failure.
85523843c12SThomas Huth  */
85623843c12SThomas Huth static bool build_guest_fsinfo_for_nonpci_virtio(char const *syspath,
85723843c12SThomas Huth                                                  GuestDiskAddress *disk,
85823843c12SThomas Huth                                                  Error **errp)
85923843c12SThomas Huth {
86023843c12SThomas Huth     unsigned int tgt[3];
86123843c12SThomas Huth     char *p;
86223843c12SThomas Huth 
86323843c12SThomas Huth     if (!strstr(syspath, "/virtio") || !strstr(syspath, "/block")) {
86423843c12SThomas Huth         g_debug("Unsupported virtio device '%s'", syspath);
86523843c12SThomas Huth         return false;
86623843c12SThomas Huth     }
86723843c12SThomas Huth 
86823843c12SThomas Huth     p = strstr(syspath, "/target");
86923843c12SThomas Huth     if (p && sscanf(p + 7, "%*u:%*u:%*u/%*u:%u:%u:%u",
87023843c12SThomas Huth                     &tgt[0], &tgt[1], &tgt[2]) == 3) {
87123843c12SThomas Huth         /* virtio-scsi: target*:0:<target>:<unit> */
87223843c12SThomas Huth         disk->bus_type = GUEST_DISK_BUS_TYPE_SCSI;
87323843c12SThomas Huth         disk->bus = tgt[0];
87423843c12SThomas Huth         disk->target = tgt[1];
87523843c12SThomas Huth         disk->unit = tgt[2];
87623843c12SThomas Huth     } else {
87723843c12SThomas Huth         /* virtio-blk: 1 disk per 1 device */
87823843c12SThomas Huth         disk->bus_type = GUEST_DISK_BUS_TYPE_VIRTIO;
87923843c12SThomas Huth     }
88023843c12SThomas Huth 
88123843c12SThomas Huth     return true;
88223843c12SThomas Huth }
88323843c12SThomas Huth 
8845b723a5dSThomas Huth /*
8855b723a5dSThomas Huth  * Store disk device info for CCW devices (s390x channel I/O devices).
8865b723a5dSThomas Huth  * Returns true if information has been stored, or false for failure.
8875b723a5dSThomas Huth  */
8885b723a5dSThomas Huth static bool build_guest_fsinfo_for_ccw_dev(char const *syspath,
8895b723a5dSThomas Huth                                            GuestDiskAddress *disk,
8905b723a5dSThomas Huth                                            Error **errp)
8915b723a5dSThomas Huth {
8925b723a5dSThomas Huth     unsigned int cssid, ssid, subchno, devno;
8935b723a5dSThomas Huth     char *p;
8945b723a5dSThomas Huth 
8955b723a5dSThomas Huth     p = strstr(syspath, "/devices/css");
8965b723a5dSThomas Huth     if (!p || sscanf(p + 12, "%*x/%x.%x.%x/%*x.%*x.%x/",
8975b723a5dSThomas Huth                      &cssid, &ssid, &subchno, &devno) < 4) {
8985b723a5dSThomas Huth         g_debug("could not parse ccw device sysfs path: %s", syspath);
8995b723a5dSThomas Huth         return false;
9005b723a5dSThomas Huth     }
9015b723a5dSThomas Huth 
9025b723a5dSThomas Huth     disk->has_ccw_address = true;
9035b723a5dSThomas Huth     disk->ccw_address = g_new0(GuestCCWAddress, 1);
9045b723a5dSThomas Huth     disk->ccw_address->cssid = cssid;
9055b723a5dSThomas Huth     disk->ccw_address->ssid = ssid;
9065b723a5dSThomas Huth     disk->ccw_address->subchno = subchno;
9075b723a5dSThomas Huth     disk->ccw_address->devno = devno;
9085b723a5dSThomas Huth 
9095b723a5dSThomas Huth     if (strstr(p, "/virtio")) {
9105b723a5dSThomas Huth         build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
9115b723a5dSThomas Huth     }
9125b723a5dSThomas Huth 
9135b723a5dSThomas Huth     return true;
9145b723a5dSThomas Huth }
9155b723a5dSThomas Huth 
916d9fe4f0fSThomas Huth /* Store disk device info specified by @sysfs into @fs */
917d9fe4f0fSThomas Huth static void build_guest_fsinfo_for_real_device(char const *syspath,
918d9fe4f0fSThomas Huth                                                GuestFilesystemInfo *fs,
919d9fe4f0fSThomas Huth                                                Error **errp)
920d9fe4f0fSThomas Huth {
921d9fe4f0fSThomas Huth     GuestDiskAddress *disk;
922d9fe4f0fSThomas Huth     GuestPCIAddress *pciaddr;
923d9fe4f0fSThomas Huth     bool has_hwinf;
92443dadc43SThomas Huth #ifdef CONFIG_LIBUDEV
92543dadc43SThomas Huth     struct udev *udev = NULL;
92643dadc43SThomas Huth     struct udev_device *udevice = NULL;
92743dadc43SThomas Huth #endif
928d9fe4f0fSThomas Huth 
929d9fe4f0fSThomas Huth     pciaddr = g_new0(GuestPCIAddress, 1);
93043dadc43SThomas Huth     pciaddr->domain = -1;                       /* -1 means field is invalid */
93143dadc43SThomas Huth     pciaddr->bus = -1;
93243dadc43SThomas Huth     pciaddr->slot = -1;
93343dadc43SThomas Huth     pciaddr->function = -1;
934d9fe4f0fSThomas Huth 
935d9fe4f0fSThomas Huth     disk = g_new0(GuestDiskAddress, 1);
936d9fe4f0fSThomas Huth     disk->pci_controller = pciaddr;
93743dadc43SThomas Huth     disk->bus_type = GUEST_DISK_BUS_TYPE_UNKNOWN;
938d9fe4f0fSThomas Huth 
93943dadc43SThomas Huth #ifdef CONFIG_LIBUDEV
94043dadc43SThomas Huth     udev = udev_new();
94143dadc43SThomas Huth     udevice = udev_device_new_from_syspath(udev, syspath);
94243dadc43SThomas Huth     if (udev == NULL || udevice == NULL) {
94343dadc43SThomas Huth         g_debug("failed to query udev");
94443dadc43SThomas Huth     } else {
94543dadc43SThomas Huth         const char *devnode, *serial;
94643dadc43SThomas Huth         devnode = udev_device_get_devnode(udevice);
94743dadc43SThomas Huth         if (devnode != NULL) {
94843dadc43SThomas Huth             disk->dev = g_strdup(devnode);
94943dadc43SThomas Huth             disk->has_dev = true;
95043dadc43SThomas Huth         }
95143dadc43SThomas Huth         serial = udev_device_get_property_value(udevice, "ID_SERIAL");
95243dadc43SThomas Huth         if (serial != NULL && *serial != 0) {
95343dadc43SThomas Huth             disk->serial = g_strdup(serial);
95443dadc43SThomas Huth             disk->has_serial = true;
95543dadc43SThomas Huth         }
95643dadc43SThomas Huth     }
95743dadc43SThomas Huth 
95843dadc43SThomas Huth     udev_unref(udev);
95943dadc43SThomas Huth     udev_device_unref(udevice);
96043dadc43SThomas Huth #endif
96143dadc43SThomas Huth 
96223843c12SThomas Huth     if (strstr(syspath, "/devices/pci")) {
963d9fe4f0fSThomas Huth         has_hwinf = build_guest_fsinfo_for_pci_dev(syspath, disk, errp);
9645b723a5dSThomas Huth     } else if (strstr(syspath, "/devices/css")) {
9655b723a5dSThomas Huth         has_hwinf = build_guest_fsinfo_for_ccw_dev(syspath, disk, errp);
96623843c12SThomas Huth     } else if (strstr(syspath, "/virtio")) {
96723843c12SThomas Huth         has_hwinf = build_guest_fsinfo_for_nonpci_virtio(syspath, disk, errp);
96823843c12SThomas Huth     } else {
96923843c12SThomas Huth         g_debug("Unsupported device type for '%s'", syspath);
97023843c12SThomas Huth         has_hwinf = false;
97123843c12SThomas Huth     }
972d9fe4f0fSThomas Huth 
97343dadc43SThomas Huth     if (has_hwinf || disk->has_dev || disk->has_serial) {
97454aa3de7SEric Blake         QAPI_LIST_PREPEND(fs->disk, disk);
975d9fe4f0fSThomas Huth     } else {
97654aa3de7SEric Blake         qapi_free_GuestDiskAddress(disk);
977d9fe4f0fSThomas Huth     }
97846d4c572STomoki Sekiyama }
97946d4c572STomoki Sekiyama 
98046d4c572STomoki Sekiyama static void build_guest_fsinfo_for_device(char const *devpath,
98146d4c572STomoki Sekiyama                                           GuestFilesystemInfo *fs,
98246d4c572STomoki Sekiyama                                           Error **errp);
98346d4c572STomoki Sekiyama 
98446d4c572STomoki Sekiyama /* Store a list of slave devices of virtual volume specified by @syspath into
98546d4c572STomoki Sekiyama  * @fs */
98646d4c572STomoki Sekiyama static void build_guest_fsinfo_for_virtual_device(char const *syspath,
98746d4c572STomoki Sekiyama                                                   GuestFilesystemInfo *fs,
98846d4c572STomoki Sekiyama                                                   Error **errp)
98946d4c572STomoki Sekiyama {
990292743d9SMarkus Armbruster     Error *err = NULL;
99146d4c572STomoki Sekiyama     DIR *dir;
99246d4c572STomoki Sekiyama     char *dirpath;
993e668d1b8Szhanghailiang     struct dirent *entry;
99446d4c572STomoki Sekiyama 
99546d4c572STomoki Sekiyama     dirpath = g_strdup_printf("%s/slaves", syspath);
99646d4c572STomoki Sekiyama     dir = opendir(dirpath);
99746d4c572STomoki Sekiyama     if (!dir) {
9988251a72fSMichael Roth         if (errno != ENOENT) {
99946d4c572STomoki Sekiyama             error_setg_errno(errp, errno, "opendir(\"%s\")", dirpath);
10008251a72fSMichael Roth         }
100146d4c572STomoki Sekiyama         g_free(dirpath);
100246d4c572STomoki Sekiyama         return;
100346d4c572STomoki Sekiyama     }
100446d4c572STomoki Sekiyama 
100546d4c572STomoki Sekiyama     for (;;) {
1006e668d1b8Szhanghailiang         errno = 0;
1007e668d1b8Szhanghailiang         entry = readdir(dir);
1008e668d1b8Szhanghailiang         if (entry == NULL) {
1009e668d1b8Szhanghailiang             if (errno) {
1010e668d1b8Szhanghailiang                 error_setg_errno(errp, errno, "readdir(\"%s\")", dirpath);
101146d4c572STomoki Sekiyama             }
101246d4c572STomoki Sekiyama             break;
101346d4c572STomoki Sekiyama         }
101446d4c572STomoki Sekiyama 
1015e668d1b8Szhanghailiang         if (entry->d_type == DT_LNK) {
1016e668d1b8Szhanghailiang             char *path;
1017e668d1b8Szhanghailiang 
1018e668d1b8Szhanghailiang             g_debug(" slave device '%s'", entry->d_name);
1019e668d1b8Szhanghailiang             path = g_strdup_printf("%s/slaves/%s", syspath, entry->d_name);
1020292743d9SMarkus Armbruster             build_guest_fsinfo_for_device(path, fs, &err);
1021e668d1b8Szhanghailiang             g_free(path);
102246d4c572STomoki Sekiyama 
1023292743d9SMarkus Armbruster             if (err) {
1024292743d9SMarkus Armbruster                 error_propagate(errp, err);
102546d4c572STomoki Sekiyama                 break;
102646d4c572STomoki Sekiyama             }
102746d4c572STomoki Sekiyama         }
102846d4c572STomoki Sekiyama     }
102946d4c572STomoki Sekiyama 
1030e668d1b8Szhanghailiang     g_free(dirpath);
103146d4c572STomoki Sekiyama     closedir(dir);
103246d4c572STomoki Sekiyama }
103346d4c572STomoki Sekiyama 
1034fed39564STomáš Golembiovský static bool is_disk_virtual(const char *devpath, Error **errp)
1035fed39564STomáš Golembiovský {
1036fed39564STomáš Golembiovský     g_autofree char *syspath = realpath(devpath, NULL);
1037fed39564STomáš Golembiovský 
1038fed39564STomáš Golembiovský     if (!syspath) {
1039fed39564STomáš Golembiovský         error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
1040fed39564STomáš Golembiovský         return false;
1041fed39564STomáš Golembiovský     }
1042fed39564STomáš Golembiovský     return strstr(syspath, "/devices/virtual/block/") != NULL;
1043fed39564STomáš Golembiovský }
1044fed39564STomáš Golembiovský 
104546d4c572STomoki Sekiyama /* Dispatch to functions for virtual/real device */
104646d4c572STomoki Sekiyama static void build_guest_fsinfo_for_device(char const *devpath,
104746d4c572STomoki Sekiyama                                           GuestFilesystemInfo *fs,
104846d4c572STomoki Sekiyama                                           Error **errp)
104946d4c572STomoki Sekiyama {
1050fed39564STomáš Golembiovský     ERRP_GUARD();
1051fed39564STomáš Golembiovský     g_autofree char *syspath = NULL;
1052fed39564STomáš Golembiovský     bool is_virtual = false;
105346d4c572STomoki Sekiyama 
1054fed39564STomáš Golembiovský     syspath = realpath(devpath, NULL);
105546d4c572STomoki Sekiyama     if (!syspath) {
1056bbb0151cSJohn Snow         if (errno != ENOENT) {
105746d4c572STomoki Sekiyama             error_setg_errno(errp, errno, "realpath(\"%s\")", devpath);
105846d4c572STomoki Sekiyama             return;
105946d4c572STomoki Sekiyama         }
106046d4c572STomoki Sekiyama 
1061bbb0151cSJohn Snow         /* ENOENT: This devpath may not exist because of container config */
1062bbb0151cSJohn Snow         if (!fs->name) {
1063bbb0151cSJohn Snow             fs->name = g_path_get_basename(devpath);
1064bbb0151cSJohn Snow         }
1065bbb0151cSJohn Snow         return;
1066bbb0151cSJohn Snow     }
1067bbb0151cSJohn Snow 
106846d4c572STomoki Sekiyama     if (!fs->name) {
10693e015d81SJulia Suvorova         fs->name = g_path_get_basename(syspath);
107046d4c572STomoki Sekiyama     }
107146d4c572STomoki Sekiyama 
107246d4c572STomoki Sekiyama     g_debug("  parse sysfs path '%s'", syspath);
1073fed39564STomáš Golembiovský     is_virtual = is_disk_virtual(syspath, errp);
1074fed39564STomáš Golembiovský     if (*errp != NULL) {
1075fed39564STomáš Golembiovský         return;
1076fed39564STomáš Golembiovský     }
1077fed39564STomáš Golembiovský     if (is_virtual) {
107846d4c572STomoki Sekiyama         build_guest_fsinfo_for_virtual_device(syspath, fs, errp);
107946d4c572STomoki Sekiyama     } else {
108046d4c572STomoki Sekiyama         build_guest_fsinfo_for_real_device(syspath, fs, errp);
108146d4c572STomoki Sekiyama     }
108246d4c572STomoki Sekiyama }
108346d4c572STomoki Sekiyama 
1084fed39564STomáš Golembiovský #ifdef CONFIG_LIBUDEV
1085fed39564STomáš Golembiovský 
1086fed39564STomáš Golembiovský /*
1087fed39564STomáš Golembiovský  * Wrapper around build_guest_fsinfo_for_device() for getting just
1088fed39564STomáš Golembiovský  * the disk address.
1089fed39564STomáš Golembiovský  */
1090fed39564STomáš Golembiovský static GuestDiskAddress *get_disk_address(const char *syspath, Error **errp)
1091fed39564STomáš Golembiovský {
1092fed39564STomáš Golembiovský     g_autoptr(GuestFilesystemInfo) fs = NULL;
1093fed39564STomáš Golembiovský 
1094fed39564STomáš Golembiovský     fs = g_new0(GuestFilesystemInfo, 1);
1095fed39564STomáš Golembiovský     build_guest_fsinfo_for_device(syspath, fs, errp);
1096fed39564STomáš Golembiovský     if (fs->disk != NULL) {
1097fed39564STomáš Golembiovský         return g_steal_pointer(&fs->disk->value);
1098fed39564STomáš Golembiovský     }
1099fed39564STomáš Golembiovský     return NULL;
1100fed39564STomáš Golembiovský }
1101fed39564STomáš Golembiovský 
1102fed39564STomáš Golembiovský static char *get_alias_for_syspath(const char *syspath)
1103fed39564STomáš Golembiovský {
1104fed39564STomáš Golembiovský     struct udev *udev = NULL;
1105fed39564STomáš Golembiovský     struct udev_device *udevice = NULL;
1106fed39564STomáš Golembiovský     char *ret = NULL;
1107fed39564STomáš Golembiovský 
1108fed39564STomáš Golembiovský     udev = udev_new();
1109fed39564STomáš Golembiovský     if (udev == NULL) {
1110fed39564STomáš Golembiovský         g_debug("failed to query udev");
1111fed39564STomáš Golembiovský         goto out;
1112fed39564STomáš Golembiovský     }
1113fed39564STomáš Golembiovský     udevice = udev_device_new_from_syspath(udev, syspath);
1114fed39564STomáš Golembiovský     if (udevice == NULL) {
1115fed39564STomáš Golembiovský         g_debug("failed to query udev for path: %s", syspath);
1116fed39564STomáš Golembiovský         goto out;
1117fed39564STomáš Golembiovský     } else {
1118fed39564STomáš Golembiovský         const char *alias = udev_device_get_property_value(
1119fed39564STomáš Golembiovský             udevice, "DM_NAME");
1120fed39564STomáš Golembiovský         /*
1121fed39564STomáš Golembiovský          * NULL means there was an error and empty string means there is no
1122fed39564STomáš Golembiovský          * alias. In case of no alias we return NULL instead of empty string.
1123fed39564STomáš Golembiovský          */
1124fed39564STomáš Golembiovský         if (alias == NULL) {
1125fed39564STomáš Golembiovský             g_debug("failed to query udev for device alias for: %s",
1126fed39564STomáš Golembiovský                 syspath);
1127fed39564STomáš Golembiovský         } else if (*alias != 0) {
1128fed39564STomáš Golembiovský             ret = g_strdup(alias);
1129fed39564STomáš Golembiovský         }
1130fed39564STomáš Golembiovský     }
1131fed39564STomáš Golembiovský 
1132fed39564STomáš Golembiovský out:
1133fed39564STomáš Golembiovský     udev_unref(udev);
1134fed39564STomáš Golembiovský     udev_device_unref(udevice);
1135fed39564STomáš Golembiovský     return ret;
1136fed39564STomáš Golembiovský }
1137fed39564STomáš Golembiovský 
1138fed39564STomáš Golembiovský static char *get_device_for_syspath(const char *syspath)
1139fed39564STomáš Golembiovský {
1140fed39564STomáš Golembiovský     struct udev *udev = NULL;
1141fed39564STomáš Golembiovský     struct udev_device *udevice = NULL;
1142fed39564STomáš Golembiovský     char *ret = NULL;
1143fed39564STomáš Golembiovský 
1144fed39564STomáš Golembiovský     udev = udev_new();
1145fed39564STomáš Golembiovský     if (udev == NULL) {
1146fed39564STomáš Golembiovský         g_debug("failed to query udev");
1147fed39564STomáš Golembiovský         goto out;
1148fed39564STomáš Golembiovský     }
1149fed39564STomáš Golembiovský     udevice = udev_device_new_from_syspath(udev, syspath);
1150fed39564STomáš Golembiovský     if (udevice == NULL) {
1151fed39564STomáš Golembiovský         g_debug("failed to query udev for path: %s", syspath);
1152fed39564STomáš Golembiovský         goto out;
1153fed39564STomáš Golembiovský     } else {
1154fed39564STomáš Golembiovský         ret = g_strdup(udev_device_get_devnode(udevice));
1155fed39564STomáš Golembiovský     }
1156fed39564STomáš Golembiovský 
1157fed39564STomáš Golembiovský out:
1158fed39564STomáš Golembiovský     udev_unref(udev);
1159fed39564STomáš Golembiovský     udev_device_unref(udevice);
1160fed39564STomáš Golembiovský     return ret;
1161fed39564STomáš Golembiovský }
1162fed39564STomáš Golembiovský 
1163fed39564STomáš Golembiovský static void get_disk_deps(const char *disk_dir, GuestDiskInfo *disk)
1164fed39564STomáš Golembiovský {
1165fed39564STomáš Golembiovský     g_autofree char *deps_dir = NULL;
1166fed39564STomáš Golembiovský     const gchar *dep;
1167fed39564STomáš Golembiovský     GDir *dp_deps = NULL;
1168fed39564STomáš Golembiovský 
1169fed39564STomáš Golembiovský     /* List dependent disks */
1170fed39564STomáš Golembiovský     deps_dir = g_strdup_printf("%s/slaves", disk_dir);
1171fed39564STomáš Golembiovský     g_debug("  listing entries in: %s", deps_dir);
1172fed39564STomáš Golembiovský     dp_deps = g_dir_open(deps_dir, 0, NULL);
1173fed39564STomáš Golembiovský     if (dp_deps == NULL) {
1174fed39564STomáš Golembiovský         g_debug("failed to list entries in %s", deps_dir);
1175fed39564STomáš Golembiovský         return;
1176fed39564STomáš Golembiovský     }
1177a8aa94b5SMichael Roth     disk->has_dependencies = true;
1178fed39564STomáš Golembiovský     while ((dep = g_dir_read_name(dp_deps)) != NULL) {
1179fed39564STomáš Golembiovský         g_autofree char *dep_dir = NULL;
1180fed39564STomáš Golembiovský         char *dev_name;
1181fed39564STomáš Golembiovský 
1182fed39564STomáš Golembiovský         /* Add dependent disks */
1183fed39564STomáš Golembiovský         dep_dir = g_strdup_printf("%s/%s", deps_dir, dep);
1184fed39564STomáš Golembiovský         dev_name = get_device_for_syspath(dep_dir);
1185fed39564STomáš Golembiovský         if (dev_name != NULL) {
1186fed39564STomáš Golembiovský             g_debug("  adding dependent device: %s", dev_name);
118754aa3de7SEric Blake             QAPI_LIST_PREPEND(disk->dependencies, dev_name);
1188fed39564STomáš Golembiovský         }
1189fed39564STomáš Golembiovský     }
1190fed39564STomáš Golembiovský     g_dir_close(dp_deps);
1191fed39564STomáš Golembiovský }
1192fed39564STomáš Golembiovský 
1193fed39564STomáš Golembiovský /*
1194fed39564STomáš Golembiovský  * Detect partitions subdirectory, name is "<disk_name><number>" or
1195fed39564STomáš Golembiovský  * "<disk_name>p<number>"
1196fed39564STomáš Golembiovský  *
1197fed39564STomáš Golembiovský  * @disk_name -- last component of /sys path (e.g. sda)
1198fed39564STomáš Golembiovský  * @disk_dir -- sys path of the disk (e.g. /sys/block/sda)
1199fed39564STomáš Golembiovský  * @disk_dev -- device node of the disk (e.g. /dev/sda)
1200fed39564STomáš Golembiovský  */
1201fed39564STomáš Golembiovský static GuestDiskInfoList *get_disk_partitions(
1202fed39564STomáš Golembiovský     GuestDiskInfoList *list,
1203fed39564STomáš Golembiovský     const char *disk_name, const char *disk_dir,
1204fed39564STomáš Golembiovský     const char *disk_dev)
1205fed39564STomáš Golembiovský {
120654aa3de7SEric Blake     GuestDiskInfoList *ret = list;
1207fed39564STomáš Golembiovský     struct dirent *de_disk;
1208fed39564STomáš Golembiovský     DIR *dp_disk = NULL;
1209fed39564STomáš Golembiovský     size_t len = strlen(disk_name);
1210fed39564STomáš Golembiovský 
1211fed39564STomáš Golembiovský     dp_disk = opendir(disk_dir);
1212fed39564STomáš Golembiovský     while ((de_disk = readdir(dp_disk)) != NULL) {
1213fed39564STomáš Golembiovský         g_autofree char *partition_dir = NULL;
1214fed39564STomáš Golembiovský         char *dev_name;
1215fed39564STomáš Golembiovský         GuestDiskInfo *partition;
1216fed39564STomáš Golembiovský 
1217fed39564STomáš Golembiovský         if (!(de_disk->d_type & DT_DIR)) {
1218fed39564STomáš Golembiovský             continue;
1219fed39564STomáš Golembiovský         }
1220fed39564STomáš Golembiovský 
1221fed39564STomáš Golembiovský         if (!(strncmp(disk_name, de_disk->d_name, len) == 0 &&
1222fed39564STomáš Golembiovský             ((*(de_disk->d_name + len) == 'p' &&
1223fed39564STomáš Golembiovský             isdigit(*(de_disk->d_name + len + 1))) ||
1224fed39564STomáš Golembiovský                 isdigit(*(de_disk->d_name + len))))) {
1225fed39564STomáš Golembiovský             continue;
1226fed39564STomáš Golembiovský         }
1227fed39564STomáš Golembiovský 
1228fed39564STomáš Golembiovský         partition_dir = g_strdup_printf("%s/%s",
1229fed39564STomáš Golembiovský             disk_dir, de_disk->d_name);
1230fed39564STomáš Golembiovský         dev_name = get_device_for_syspath(partition_dir);
1231fed39564STomáš Golembiovský         if (dev_name == NULL) {
1232fed39564STomáš Golembiovský             g_debug("Failed to get device name for syspath: %s",
1233fed39564STomáš Golembiovský                 disk_dir);
1234fed39564STomáš Golembiovský             continue;
1235fed39564STomáš Golembiovský         }
1236fed39564STomáš Golembiovský         partition = g_new0(GuestDiskInfo, 1);
1237fed39564STomáš Golembiovský         partition->name = dev_name;
1238fed39564STomáš Golembiovský         partition->partition = true;
1239bac9b87bSMarc-André Lureau         partition->has_dependencies = true;
1240fed39564STomáš Golembiovský         /* Add parent disk as dependent for easier tracking of hierarchy */
124154aa3de7SEric Blake         QAPI_LIST_PREPEND(partition->dependencies, g_strdup(disk_dev));
1242fed39564STomáš Golembiovský 
124354aa3de7SEric Blake         QAPI_LIST_PREPEND(ret, partition);
1244fed39564STomáš Golembiovský     }
1245fed39564STomáš Golembiovský     closedir(dp_disk);
1246fed39564STomáš Golembiovský 
1247fed39564STomáš Golembiovský     return ret;
1248fed39564STomáš Golembiovský }
1249fed39564STomáš Golembiovský 
125022668881Szhenwei pi static void get_nvme_smart(GuestDiskInfo *disk)
125122668881Szhenwei pi {
125222668881Szhenwei pi     int fd;
125322668881Szhenwei pi     GuestNVMeSmart *smart;
125422668881Szhenwei pi     NvmeSmartLog log = {0};
125522668881Szhenwei pi     struct nvme_admin_cmd cmd = {
125622668881Szhenwei pi         .opcode = NVME_ADM_CMD_GET_LOG_PAGE,
125722668881Szhenwei pi         .nsid = NVME_NSID_BROADCAST,
125822668881Szhenwei pi         .addr = (uintptr_t)&log,
125922668881Szhenwei pi         .data_len = sizeof(log),
126022668881Szhenwei pi         .cdw10 = NVME_LOG_SMART_INFO | (1 << 15) /* RAE bit */
126122668881Szhenwei pi                  | (((sizeof(log) >> 2) - 1) << 16)
126222668881Szhenwei pi     };
126322668881Szhenwei pi 
1264b9947c9cSMarc-André Lureau     fd = qga_open_cloexec(disk->name, O_RDONLY, 0);
126522668881Szhenwei pi     if (fd == -1) {
126622668881Szhenwei pi         g_debug("Failed to open device: %s: %s", disk->name, g_strerror(errno));
126722668881Szhenwei pi         return;
126822668881Szhenwei pi     }
126922668881Szhenwei pi 
127022668881Szhenwei pi     if (ioctl(fd, NVME_IOCTL_ADMIN_CMD, &cmd)) {
127122668881Szhenwei pi         g_debug("Failed to get smart: %s: %s", disk->name, g_strerror(errno));
127222668881Szhenwei pi         close(fd);
127322668881Szhenwei pi         return;
127422668881Szhenwei pi     }
127522668881Szhenwei pi 
127622668881Szhenwei pi     disk->has_smart = true;
127722668881Szhenwei pi     disk->smart = g_new0(GuestDiskSmart, 1);
127822668881Szhenwei pi     disk->smart->type = GUEST_DISK_BUS_TYPE_NVME;
127922668881Szhenwei pi 
128022668881Szhenwei pi     smart = &disk->smart->u.nvme;
128122668881Szhenwei pi     smart->critical_warning = log.critical_warning;
128222668881Szhenwei pi     smart->temperature = lduw_le_p(&log.temperature); /* unaligned field */
128322668881Szhenwei pi     smart->available_spare = log.available_spare;
128422668881Szhenwei pi     smart->available_spare_threshold = log.available_spare_threshold;
128522668881Szhenwei pi     smart->percentage_used = log.percentage_used;
128622668881Szhenwei pi     smart->data_units_read_lo = le64_to_cpu(log.data_units_read[0]);
128722668881Szhenwei pi     smart->data_units_read_hi = le64_to_cpu(log.data_units_read[1]);
128822668881Szhenwei pi     smart->data_units_written_lo = le64_to_cpu(log.data_units_written[0]);
128922668881Szhenwei pi     smart->data_units_written_hi = le64_to_cpu(log.data_units_written[1]);
129022668881Szhenwei pi     smart->host_read_commands_lo = le64_to_cpu(log.host_read_commands[0]);
129122668881Szhenwei pi     smart->host_read_commands_hi = le64_to_cpu(log.host_read_commands[1]);
129222668881Szhenwei pi     smart->host_write_commands_lo = le64_to_cpu(log.host_write_commands[0]);
129322668881Szhenwei pi     smart->host_write_commands_hi = le64_to_cpu(log.host_write_commands[1]);
129422668881Szhenwei pi     smart->controller_busy_time_lo = le64_to_cpu(log.controller_busy_time[0]);
129522668881Szhenwei pi     smart->controller_busy_time_hi = le64_to_cpu(log.controller_busy_time[1]);
129622668881Szhenwei pi     smart->power_cycles_lo = le64_to_cpu(log.power_cycles[0]);
129722668881Szhenwei pi     smart->power_cycles_hi = le64_to_cpu(log.power_cycles[1]);
129822668881Szhenwei pi     smart->power_on_hours_lo = le64_to_cpu(log.power_on_hours[0]);
129922668881Szhenwei pi     smart->power_on_hours_hi = le64_to_cpu(log.power_on_hours[1]);
130022668881Szhenwei pi     smart->unsafe_shutdowns_lo = le64_to_cpu(log.unsafe_shutdowns[0]);
130122668881Szhenwei pi     smart->unsafe_shutdowns_hi = le64_to_cpu(log.unsafe_shutdowns[1]);
130222668881Szhenwei pi     smart->media_errors_lo = le64_to_cpu(log.media_errors[0]);
130322668881Szhenwei pi     smart->media_errors_hi = le64_to_cpu(log.media_errors[1]);
130422668881Szhenwei pi     smart->number_of_error_log_entries_lo =
130522668881Szhenwei pi         le64_to_cpu(log.number_of_error_log_entries[0]);
130622668881Szhenwei pi     smart->number_of_error_log_entries_hi =
130722668881Szhenwei pi         le64_to_cpu(log.number_of_error_log_entries[1]);
130822668881Szhenwei pi 
130922668881Szhenwei pi     close(fd);
131022668881Szhenwei pi }
131122668881Szhenwei pi 
131222668881Szhenwei pi static void get_disk_smart(GuestDiskInfo *disk)
131322668881Szhenwei pi {
131422668881Szhenwei pi     if (disk->has_address
131522668881Szhenwei pi         && (disk->address->bus_type == GUEST_DISK_BUS_TYPE_NVME)) {
131622668881Szhenwei pi         get_nvme_smart(disk);
131722668881Szhenwei pi     }
131822668881Szhenwei pi }
131922668881Szhenwei pi 
1320fed39564STomáš Golembiovský GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
1321fed39564STomáš Golembiovský {
132254aa3de7SEric Blake     GuestDiskInfoList *ret = NULL;
1323fed39564STomáš Golembiovský     GuestDiskInfo *disk;
1324fed39564STomáš Golembiovský     DIR *dp = NULL;
1325fed39564STomáš Golembiovský     struct dirent *de = NULL;
1326fed39564STomáš Golembiovský 
1327fed39564STomáš Golembiovský     g_debug("listing /sys/block directory");
1328fed39564STomáš Golembiovský     dp = opendir("/sys/block");
1329fed39564STomáš Golembiovský     if (dp == NULL) {
1330fed39564STomáš Golembiovský         error_setg_errno(errp, errno, "Can't open directory \"/sys/block\"");
1331fed39564STomáš Golembiovský         return NULL;
1332fed39564STomáš Golembiovský     }
1333fed39564STomáš Golembiovský     while ((de = readdir(dp)) != NULL) {
1334fed39564STomáš Golembiovský         g_autofree char *disk_dir = NULL, *line = NULL,
1335fed39564STomáš Golembiovský             *size_path = NULL;
1336fed39564STomáš Golembiovský         char *dev_name;
1337fed39564STomáš Golembiovský         Error *local_err = NULL;
1338fed39564STomáš Golembiovský         if (de->d_type != DT_LNK) {
1339fed39564STomáš Golembiovský             g_debug("  skipping entry: %s", de->d_name);
1340fed39564STomáš Golembiovský             continue;
1341fed39564STomáš Golembiovský         }
1342fed39564STomáš Golembiovský 
1343fed39564STomáš Golembiovský         /* Check size and skip zero-sized disks */
1344fed39564STomáš Golembiovský         g_debug("  checking disk size");
1345fed39564STomáš Golembiovský         size_path = g_strdup_printf("/sys/block/%s/size", de->d_name);
1346fed39564STomáš Golembiovský         if (!g_file_get_contents(size_path, &line, NULL, NULL)) {
1347fed39564STomáš Golembiovský             g_debug("  failed to read disk size");
1348fed39564STomáš Golembiovský             continue;
1349fed39564STomáš Golembiovský         }
1350fed39564STomáš Golembiovský         if (g_strcmp0(line, "0\n") == 0) {
1351fed39564STomáš Golembiovský             g_debug("  skipping zero-sized disk");
1352fed39564STomáš Golembiovský             continue;
1353fed39564STomáš Golembiovský         }
1354fed39564STomáš Golembiovský 
1355fed39564STomáš Golembiovský         g_debug("  adding %s", de->d_name);
1356fed39564STomáš Golembiovský         disk_dir = g_strdup_printf("/sys/block/%s", de->d_name);
1357fed39564STomáš Golembiovský         dev_name = get_device_for_syspath(disk_dir);
1358fed39564STomáš Golembiovský         if (dev_name == NULL) {
1359fed39564STomáš Golembiovský             g_debug("Failed to get device name for syspath: %s",
1360fed39564STomáš Golembiovský                 disk_dir);
1361fed39564STomáš Golembiovský             continue;
1362fed39564STomáš Golembiovský         }
1363fed39564STomáš Golembiovský         disk = g_new0(GuestDiskInfo, 1);
1364fed39564STomáš Golembiovský         disk->name = dev_name;
1365fed39564STomáš Golembiovský         disk->partition = false;
1366fed39564STomáš Golembiovský         disk->alias = get_alias_for_syspath(disk_dir);
1367fed39564STomáš Golembiovský         disk->has_alias = (disk->alias != NULL);
136854aa3de7SEric Blake         QAPI_LIST_PREPEND(ret, disk);
1369fed39564STomáš Golembiovský 
1370fed39564STomáš Golembiovský         /* Get address for non-virtual devices */
1371fed39564STomáš Golembiovský         bool is_virtual = is_disk_virtual(disk_dir, &local_err);
1372fed39564STomáš Golembiovský         if (local_err != NULL) {
1373fed39564STomáš Golembiovský             g_debug("  failed to check disk path, ignoring error: %s",
1374fed39564STomáš Golembiovský                 error_get_pretty(local_err));
1375fed39564STomáš Golembiovský             error_free(local_err);
1376fed39564STomáš Golembiovský             local_err = NULL;
1377fed39564STomáš Golembiovský             /* Don't try to get the address */
1378fed39564STomáš Golembiovský             is_virtual = true;
1379fed39564STomáš Golembiovský         }
1380fed39564STomáš Golembiovský         if (!is_virtual) {
1381fed39564STomáš Golembiovský             disk->address = get_disk_address(disk_dir, &local_err);
1382fed39564STomáš Golembiovský             if (local_err != NULL) {
1383fed39564STomáš Golembiovský                 g_debug("  failed to get device info, ignoring error: %s",
1384fed39564STomáš Golembiovský                     error_get_pretty(local_err));
1385fed39564STomáš Golembiovský                 error_free(local_err);
1386fed39564STomáš Golembiovský                 local_err = NULL;
1387fed39564STomáš Golembiovský             } else if (disk->address != NULL) {
1388fed39564STomáš Golembiovský                 disk->has_address = true;
1389fed39564STomáš Golembiovský             }
1390fed39564STomáš Golembiovský         }
1391fed39564STomáš Golembiovský 
1392fed39564STomáš Golembiovský         get_disk_deps(disk_dir, disk);
139322668881Szhenwei pi         get_disk_smart(disk);
1394fed39564STomáš Golembiovský         ret = get_disk_partitions(ret, de->d_name, disk_dir, dev_name);
1395fed39564STomáš Golembiovský     }
1396b1b9ab1cSMichael Roth 
1397b1b9ab1cSMichael Roth     closedir(dp);
1398b1b9ab1cSMichael Roth 
1399fed39564STomáš Golembiovský     return ret;
1400fed39564STomáš Golembiovský }
1401fed39564STomáš Golembiovský 
1402fed39564STomáš Golembiovský #else
1403fed39564STomáš Golembiovský 
1404fed39564STomáš Golembiovský GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
1405fed39564STomáš Golembiovský {
1406fed39564STomáš Golembiovský     error_setg(errp, QERR_UNSUPPORTED);
1407fed39564STomáš Golembiovský     return NULL;
1408fed39564STomáš Golembiovský }
1409fed39564STomáš Golembiovský 
1410fed39564STomáš Golembiovský #endif
1411fed39564STomáš Golembiovský 
141246d4c572STomoki Sekiyama /* Return a list of the disk device(s)' info which @mount lies on */
141346d4c572STomoki Sekiyama static GuestFilesystemInfo *build_guest_fsinfo(struct FsMount *mount,
141446d4c572STomoki Sekiyama                                                Error **errp)
141546d4c572STomoki Sekiyama {
141646d4c572STomoki Sekiyama     GuestFilesystemInfo *fs = g_malloc0(sizeof(*fs));
141725b5ff1aSChen Hanxiao     struct statvfs buf;
141825b5ff1aSChen Hanxiao     unsigned long used, nonroot_total, fr_size;
141946d4c572STomoki Sekiyama     char *devpath = g_strdup_printf("/sys/dev/block/%u:%u",
142046d4c572STomoki Sekiyama                                     mount->devmajor, mount->devminor);
142146d4c572STomoki Sekiyama 
142246d4c572STomoki Sekiyama     fs->mountpoint = g_strdup(mount->dirname);
142346d4c572STomoki Sekiyama     fs->type = g_strdup(mount->devtype);
142446d4c572STomoki Sekiyama     build_guest_fsinfo_for_device(devpath, fs, errp);
142546d4c572STomoki Sekiyama 
142625b5ff1aSChen Hanxiao     if (statvfs(fs->mountpoint, &buf) == 0) {
142725b5ff1aSChen Hanxiao         fr_size = buf.f_frsize;
142825b5ff1aSChen Hanxiao         used = buf.f_blocks - buf.f_bfree;
142925b5ff1aSChen Hanxiao         nonroot_total = used + buf.f_bavail;
143025b5ff1aSChen Hanxiao         fs->used_bytes = used * fr_size;
143125b5ff1aSChen Hanxiao         fs->total_bytes = nonroot_total * fr_size;
143225b5ff1aSChen Hanxiao 
143325b5ff1aSChen Hanxiao         fs->has_total_bytes = true;
143425b5ff1aSChen Hanxiao         fs->has_used_bytes = true;
143525b5ff1aSChen Hanxiao     }
143625b5ff1aSChen Hanxiao 
143746d4c572STomoki Sekiyama     g_free(devpath);
143825b5ff1aSChen Hanxiao 
143946d4c572STomoki Sekiyama     return fs;
144046d4c572STomoki Sekiyama }
144146d4c572STomoki Sekiyama 
144246d4c572STomoki Sekiyama GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
144346d4c572STomoki Sekiyama {
144446d4c572STomoki Sekiyama     FsMountList mounts;
144546d4c572STomoki Sekiyama     struct FsMount *mount;
144654aa3de7SEric Blake     GuestFilesystemInfoList *ret = NULL;
144746d4c572STomoki Sekiyama     Error *local_err = NULL;
144846d4c572STomoki Sekiyama 
144946d4c572STomoki Sekiyama     QTAILQ_INIT(&mounts);
1450561bfcb6SMarc-André Lureau     if (!build_fs_mount_list(&mounts, &local_err)) {
145146d4c572STomoki Sekiyama         error_propagate(errp, local_err);
145246d4c572STomoki Sekiyama         return NULL;
145346d4c572STomoki Sekiyama     }
145446d4c572STomoki Sekiyama 
145546d4c572STomoki Sekiyama     QTAILQ_FOREACH(mount, &mounts, next) {
145646d4c572STomoki Sekiyama         g_debug("Building guest fsinfo for '%s'", mount->dirname);
145746d4c572STomoki Sekiyama 
145854aa3de7SEric Blake         QAPI_LIST_PREPEND(ret, build_guest_fsinfo(mount, &local_err));
145946d4c572STomoki Sekiyama         if (local_err) {
146046d4c572STomoki Sekiyama             error_propagate(errp, local_err);
146146d4c572STomoki Sekiyama             qapi_free_GuestFilesystemInfoList(ret);
146246d4c572STomoki Sekiyama             ret = NULL;
146346d4c572STomoki Sekiyama             break;
146446d4c572STomoki Sekiyama         }
146546d4c572STomoki Sekiyama     }
146646d4c572STomoki Sekiyama 
146746d4c572STomoki Sekiyama     free_fs_mount_list(&mounts);
146846d4c572STomoki Sekiyama     return ret;
146946d4c572STomoki Sekiyama }
147046d4c572STomoki Sekiyama 
147146d4c572STomoki Sekiyama 
1472ec0f694cSTomoki Sekiyama typedef enum {
1473ec0f694cSTomoki Sekiyama     FSFREEZE_HOOK_THAW = 0,
1474ec0f694cSTomoki Sekiyama     FSFREEZE_HOOK_FREEZE,
1475ec0f694cSTomoki Sekiyama } FsfreezeHookArg;
1476ec0f694cSTomoki Sekiyama 
147713a439ecSStefan Weil static const char *fsfreeze_hook_arg_string[] = {
1478ec0f694cSTomoki Sekiyama     "thaw",
1479ec0f694cSTomoki Sekiyama     "freeze",
1480ec0f694cSTomoki Sekiyama };
1481ec0f694cSTomoki Sekiyama 
148277dbc81bSMarkus Armbruster static void execute_fsfreeze_hook(FsfreezeHookArg arg, Error **errp)
1483ec0f694cSTomoki Sekiyama {
1484ec0f694cSTomoki Sekiyama     int status;
1485ec0f694cSTomoki Sekiyama     pid_t pid;
1486ec0f694cSTomoki Sekiyama     const char *hook;
1487ec0f694cSTomoki Sekiyama     const char *arg_str = fsfreeze_hook_arg_string[arg];
1488ec0f694cSTomoki Sekiyama     Error *local_err = NULL;
1489ec0f694cSTomoki Sekiyama 
1490ec0f694cSTomoki Sekiyama     hook = ga_fsfreeze_hook(ga_state);
1491ec0f694cSTomoki Sekiyama     if (!hook) {
1492ec0f694cSTomoki Sekiyama         return;
1493ec0f694cSTomoki Sekiyama     }
1494ec0f694cSTomoki Sekiyama     if (access(hook, X_OK) != 0) {
149577dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "can't access fsfreeze hook '%s'", hook);
1496ec0f694cSTomoki Sekiyama         return;
1497ec0f694cSTomoki Sekiyama     }
1498ec0f694cSTomoki Sekiyama 
1499ec0f694cSTomoki Sekiyama     slog("executing fsfreeze hook with arg '%s'", arg_str);
1500ec0f694cSTomoki Sekiyama     pid = fork();
1501ec0f694cSTomoki Sekiyama     if (pid == 0) {
1502ec0f694cSTomoki Sekiyama         setsid();
1503ec0f694cSTomoki Sekiyama         reopen_fd_to_null(0);
1504ec0f694cSTomoki Sekiyama         reopen_fd_to_null(1);
1505ec0f694cSTomoki Sekiyama         reopen_fd_to_null(2);
1506ec0f694cSTomoki Sekiyama 
1507fcc41961SMarc-André Lureau         execl(hook, hook, arg_str, NULL);
1508ec0f694cSTomoki Sekiyama         _exit(EXIT_FAILURE);
1509ec0f694cSTomoki Sekiyama     } else if (pid < 0) {
151077dbc81bSMarkus Armbruster         error_setg_errno(errp, errno, "failed to create child process");
1511ec0f694cSTomoki Sekiyama         return;
1512ec0f694cSTomoki Sekiyama     }
1513ec0f694cSTomoki Sekiyama 
1514ec0f694cSTomoki Sekiyama     ga_wait_child(pid, &status, &local_err);
151584d18f06SMarkus Armbruster     if (local_err) {
151677dbc81bSMarkus Armbruster         error_propagate(errp, local_err);
1517ec0f694cSTomoki Sekiyama         return;
1518ec0f694cSTomoki Sekiyama     }
1519ec0f694cSTomoki Sekiyama 
1520ec0f694cSTomoki Sekiyama     if (!WIFEXITED(status)) {
152177dbc81bSMarkus Armbruster         error_setg(errp, "fsfreeze hook has terminated abnormally");
1522ec0f694cSTomoki Sekiyama         return;
1523ec0f694cSTomoki Sekiyama     }
1524ec0f694cSTomoki Sekiyama 
1525ec0f694cSTomoki Sekiyama     status = WEXITSTATUS(status);
1526ec0f694cSTomoki Sekiyama     if (status) {
152777dbc81bSMarkus Armbruster         error_setg(errp, "fsfreeze hook has failed with status %d", status);
1528ec0f694cSTomoki Sekiyama         return;
1529ec0f694cSTomoki Sekiyama     }
1530ec0f694cSTomoki Sekiyama }
1531ec0f694cSTomoki Sekiyama 
1532c216e5adSMichael Roth /*
1533c216e5adSMichael Roth  * Return status of freeze/thaw
1534c216e5adSMichael Roth  */
153577dbc81bSMarkus Armbruster GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
1536c216e5adSMichael Roth {
1537f22d85e9SMichael Roth     if (ga_is_frozen(ga_state)) {
1538f22d85e9SMichael Roth         return GUEST_FSFREEZE_STATUS_FROZEN;
1539f22d85e9SMichael Roth     }
1540f22d85e9SMichael Roth 
1541f22d85e9SMichael Roth     return GUEST_FSFREEZE_STATUS_THAWED;
1542c216e5adSMichael Roth }
1543c216e5adSMichael Roth 
1544e99bce20STomoki Sekiyama int64_t qmp_guest_fsfreeze_freeze(Error **errp)
1545e99bce20STomoki Sekiyama {
1546e99bce20STomoki Sekiyama     return qmp_guest_fsfreeze_freeze_list(false, NULL, errp);
1547e99bce20STomoki Sekiyama }
1548e99bce20STomoki Sekiyama 
1549e99bce20STomoki Sekiyama int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
1550e99bce20STomoki Sekiyama                                        strList *mountpoints,
1551e99bce20STomoki Sekiyama                                        Error **errp)
1552c216e5adSMichael Roth {
1553*518b0d80SAlexander Ivanov     int ret;
1554af02203fSPaolo Bonzini     FsMountList mounts;
1555261551d1SLuiz Capitulino     Error *local_err = NULL;
1556c216e5adSMichael Roth 
1557c216e5adSMichael Roth     slog("guest-fsfreeze called");
1558c216e5adSMichael Roth 
1559ec0f694cSTomoki Sekiyama     execute_fsfreeze_hook(FSFREEZE_HOOK_FREEZE, &local_err);
156084d18f06SMarkus Armbruster     if (local_err) {
156177dbc81bSMarkus Armbruster         error_propagate(errp, local_err);
1562ec0f694cSTomoki Sekiyama         return -1;
1563ec0f694cSTomoki Sekiyama     }
1564ec0f694cSTomoki Sekiyama 
15659e8aded4SMichael Roth     QTAILQ_INIT(&mounts);
1566561bfcb6SMarc-André Lureau     if (!build_fs_mount_list(&mounts, &local_err)) {
156777dbc81bSMarkus Armbruster         error_propagate(errp, local_err);
1568261551d1SLuiz Capitulino         return -1;
1569c216e5adSMichael Roth     }
1570c216e5adSMichael Roth 
1571c216e5adSMichael Roth     /* cannot risk guest agent blocking itself on a write in this state */
1572f22d85e9SMichael Roth     ga_set_frozen(ga_state);
1573c216e5adSMichael Roth 
1574*518b0d80SAlexander Ivanov     ret = qmp_guest_fsfreeze_do_freeze_list(has_mountpoints, mountpoints,
1575*518b0d80SAlexander Ivanov                                             mounts, errp);
1576c216e5adSMichael Roth 
1577af02203fSPaolo Bonzini     free_fs_mount_list(&mounts);
157865650f01SChen Hanxiao     /* We may not issue any FIFREEZE here.
157965650f01SChen Hanxiao      * Just unset ga_state here and ready for the next call.
158065650f01SChen Hanxiao      */
1581*518b0d80SAlexander Ivanov     if (ret == 0) {
158265650f01SChen Hanxiao         ga_unset_frozen(ga_state);
1583*518b0d80SAlexander Ivanov     } else if (ret < 0) {
1584c216e5adSMichael Roth         qmp_guest_fsfreeze_thaw(NULL);
1585*518b0d80SAlexander Ivanov     }
1586*518b0d80SAlexander Ivanov     return ret;
1587c216e5adSMichael Roth }
1588c216e5adSMichael Roth 
158977dbc81bSMarkus Armbruster int64_t qmp_guest_fsfreeze_thaw(Error **errp)
1590c216e5adSMichael Roth {
1591c216e5adSMichael Roth     int ret;
1592c216e5adSMichael Roth 
1593*518b0d80SAlexander Ivanov     ret = qmp_guest_fsfreeze_do_thaw(errp);
1594*518b0d80SAlexander Ivanov     if (ret >= 0) {
1595f22d85e9SMichael Roth         ga_unset_frozen(ga_state);
159677dbc81bSMarkus Armbruster         execute_fsfreeze_hook(FSFREEZE_HOOK_THAW, errp);
1597*518b0d80SAlexander Ivanov     } else {
1598*518b0d80SAlexander Ivanov         ret = 0;
1599*518b0d80SAlexander Ivanov     }
1600ec0f694cSTomoki Sekiyama 
1601*518b0d80SAlexander Ivanov     return ret;
1602c216e5adSMichael Roth }
1603c216e5adSMichael Roth 
1604c216e5adSMichael Roth static void guest_fsfreeze_cleanup(void)
1605c216e5adSMichael Roth {
1606c216e5adSMichael Roth     Error *err = NULL;
1607c216e5adSMichael Roth 
1608f22d85e9SMichael Roth     if (ga_is_frozen(ga_state) == GUEST_FSFREEZE_STATUS_FROZEN) {
16096f686749SMarkus Armbruster         qmp_guest_fsfreeze_thaw(&err);
16106f686749SMarkus Armbruster         if (err) {
16116f686749SMarkus Armbruster             slog("failed to clean up frozen filesystems: %s",
16126f686749SMarkus Armbruster                  error_get_pretty(err));
16136f686749SMarkus Armbruster             error_free(err);
1614c216e5adSMichael Roth         }
1615c216e5adSMichael Roth     }
1616c216e5adSMichael Roth }
1617e72c3f2eSMichael Roth #endif /* CONFIG_FSFREEZE */
1618c216e5adSMichael Roth 
1619eab5fd59SPaolo Bonzini #if defined(CONFIG_FSTRIM)
1620eab5fd59SPaolo Bonzini /*
1621eab5fd59SPaolo Bonzini  * Walk list of mounted file systems in the guest, and trim them.
1622eab5fd59SPaolo Bonzini  */
1623e82855d9SJustin Ossevoort GuestFilesystemTrimResponse *
1624e82855d9SJustin Ossevoort qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
1625eab5fd59SPaolo Bonzini {
1626e82855d9SJustin Ossevoort     GuestFilesystemTrimResponse *response;
1627e82855d9SJustin Ossevoort     GuestFilesystemTrimResult *result;
1628eab5fd59SPaolo Bonzini     int ret = 0;
1629eab5fd59SPaolo Bonzini     FsMountList mounts;
1630eab5fd59SPaolo Bonzini     struct FsMount *mount;
1631eab5fd59SPaolo Bonzini     int fd;
163273a652a1SJustin Ossevoort     struct fstrim_range r;
1633eab5fd59SPaolo Bonzini 
1634eab5fd59SPaolo Bonzini     slog("guest-fstrim called");
1635eab5fd59SPaolo Bonzini 
1636eab5fd59SPaolo Bonzini     QTAILQ_INIT(&mounts);
1637561bfcb6SMarc-André Lureau     if (!build_fs_mount_list(&mounts, errp)) {
1638e82855d9SJustin Ossevoort         return NULL;
1639eab5fd59SPaolo Bonzini     }
1640eab5fd59SPaolo Bonzini 
1641e82855d9SJustin Ossevoort     response = g_malloc0(sizeof(*response));
1642e82855d9SJustin Ossevoort 
1643eab5fd59SPaolo Bonzini     QTAILQ_FOREACH(mount, &mounts, next) {
1644e82855d9SJustin Ossevoort         result = g_malloc0(sizeof(*result));
1645e82855d9SJustin Ossevoort         result->path = g_strdup(mount->dirname);
1646e82855d9SJustin Ossevoort 
164754aa3de7SEric Blake         QAPI_LIST_PREPEND(response->paths, result);
1648e82855d9SJustin Ossevoort 
1649b9947c9cSMarc-André Lureau         fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0);
1650eab5fd59SPaolo Bonzini         if (fd == -1) {
1651e82855d9SJustin Ossevoort             result->error = g_strdup_printf("failed to open: %s",
1652e82855d9SJustin Ossevoort                                             strerror(errno));
1653e82855d9SJustin Ossevoort             result->has_error = true;
1654e82855d9SJustin Ossevoort             continue;
1655eab5fd59SPaolo Bonzini         }
1656eab5fd59SPaolo Bonzini 
1657e35916acSMichael Tokarev         /* We try to cull filesystems we know won't work in advance, but other
1658e35916acSMichael Tokarev          * filesystems may not implement fstrim for less obvious reasons.
1659e35916acSMichael Tokarev          * These will report EOPNOTSUPP; while in some other cases ENOTTY
1660e35916acSMichael Tokarev          * will be reported (e.g. CD-ROMs).
1661e82855d9SJustin Ossevoort          * Any other error means an unexpected error.
1662eab5fd59SPaolo Bonzini          */
166373a652a1SJustin Ossevoort         r.start = 0;
166473a652a1SJustin Ossevoort         r.len = -1;
166573a652a1SJustin Ossevoort         r.minlen = has_minimum ? minimum : 0;
1666eab5fd59SPaolo Bonzini         ret = ioctl(fd, FITRIM, &r);
1667eab5fd59SPaolo Bonzini         if (ret == -1) {
1668e82855d9SJustin Ossevoort             result->has_error = true;
1669e82855d9SJustin Ossevoort             if (errno == ENOTTY || errno == EOPNOTSUPP) {
1670e82855d9SJustin Ossevoort                 result->error = g_strdup("trim not supported");
1671e82855d9SJustin Ossevoort             } else {
1672e82855d9SJustin Ossevoort                 result->error = g_strdup_printf("failed to trim: %s",
1673e82855d9SJustin Ossevoort                                                 strerror(errno));
1674e82855d9SJustin Ossevoort             }
1675eab5fd59SPaolo Bonzini             close(fd);
1676e82855d9SJustin Ossevoort             continue;
1677eab5fd59SPaolo Bonzini         }
1678e82855d9SJustin Ossevoort 
1679e82855d9SJustin Ossevoort         result->has_minimum = true;
1680e82855d9SJustin Ossevoort         result->minimum = r.minlen;
1681e82855d9SJustin Ossevoort         result->has_trimmed = true;
1682e82855d9SJustin Ossevoort         result->trimmed = r.len;
1683eab5fd59SPaolo Bonzini         close(fd);
1684eab5fd59SPaolo Bonzini     }
1685eab5fd59SPaolo Bonzini 
1686eab5fd59SPaolo Bonzini     free_fs_mount_list(&mounts);
1687e82855d9SJustin Ossevoort     return response;
1688eab5fd59SPaolo Bonzini }
1689eab5fd59SPaolo Bonzini #endif /* CONFIG_FSTRIM */
1690eab5fd59SPaolo Bonzini 
1691eab5fd59SPaolo Bonzini 
169211d0f125SLuiz Capitulino #define LINUX_SYS_STATE_FILE "/sys/power/state"
169311d0f125SLuiz Capitulino #define SUSPEND_SUPPORTED 0
169411d0f125SLuiz Capitulino #define SUSPEND_NOT_SUPPORTED 1
169511d0f125SLuiz Capitulino 
16968b020b5eSDaniel Henrique Barboza typedef enum {
16978b020b5eSDaniel Henrique Barboza     SUSPEND_MODE_DISK = 0,
16988b020b5eSDaniel Henrique Barboza     SUSPEND_MODE_RAM = 1,
16998b020b5eSDaniel Henrique Barboza     SUSPEND_MODE_HYBRID = 2,
17008b020b5eSDaniel Henrique Barboza } SuspendMode;
17018b020b5eSDaniel Henrique Barboza 
17028b020b5eSDaniel Henrique Barboza /*
17038b020b5eSDaniel Henrique Barboza  * Executes a command in a child process using g_spawn_sync,
17048b020b5eSDaniel Henrique Barboza  * returning an int >= 0 representing the exit status of the
17058b020b5eSDaniel Henrique Barboza  * process.
17068b020b5eSDaniel Henrique Barboza  *
17078b020b5eSDaniel Henrique Barboza  * If the program wasn't found in path, returns -1.
17088b020b5eSDaniel Henrique Barboza  *
17098b020b5eSDaniel Henrique Barboza  * If a problem happened when creating the child process,
17108b020b5eSDaniel Henrique Barboza  * returns -1 and errp is set.
17118b020b5eSDaniel Henrique Barboza  */
17128b020b5eSDaniel Henrique Barboza static int run_process_child(const char *command[], Error **errp)
17138b020b5eSDaniel Henrique Barboza {
17148b020b5eSDaniel Henrique Barboza     int exit_status, spawn_flag;
17158b020b5eSDaniel Henrique Barboza     GError *g_err = NULL;
17168b020b5eSDaniel Henrique Barboza     bool success;
17178b020b5eSDaniel Henrique Barboza 
17188b020b5eSDaniel Henrique Barboza     spawn_flag = G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL |
17198b020b5eSDaniel Henrique Barboza                  G_SPAWN_STDERR_TO_DEV_NULL;
17208b020b5eSDaniel Henrique Barboza 
1721fcc41961SMarc-André Lureau     success =  g_spawn_sync(NULL, (char **)command, NULL, spawn_flag,
17228b020b5eSDaniel Henrique Barboza                             NULL, NULL, NULL, NULL,
17238b020b5eSDaniel Henrique Barboza                             &exit_status, &g_err);
17248b020b5eSDaniel Henrique Barboza 
17258b020b5eSDaniel Henrique Barboza     if (success) {
17268b020b5eSDaniel Henrique Barboza         return WEXITSTATUS(exit_status);
17278b020b5eSDaniel Henrique Barboza     }
17288b020b5eSDaniel Henrique Barboza 
17298b020b5eSDaniel Henrique Barboza     if (g_err && (g_err->code != G_SPAWN_ERROR_NOENT)) {
17308b020b5eSDaniel Henrique Barboza         error_setg(errp, "failed to create child process, error '%s'",
17318b020b5eSDaniel Henrique Barboza                    g_err->message);
17328b020b5eSDaniel Henrique Barboza     }
17338b020b5eSDaniel Henrique Barboza 
17348b020b5eSDaniel Henrique Barboza     g_error_free(g_err);
17358b020b5eSDaniel Henrique Barboza     return -1;
17368b020b5eSDaniel Henrique Barboza }
17378b020b5eSDaniel Henrique Barboza 
1738067927d6SDaniel Henrique Barboza static bool systemd_supports_mode(SuspendMode mode, Error **errp)
1739067927d6SDaniel Henrique Barboza {
1740067927d6SDaniel Henrique Barboza     const char *systemctl_args[3] = {"systemd-hibernate", "systemd-suspend",
1741067927d6SDaniel Henrique Barboza                                      "systemd-hybrid-sleep"};
1742067927d6SDaniel Henrique Barboza     const char *cmd[4] = {"systemctl", "status", systemctl_args[mode], NULL};
1743067927d6SDaniel Henrique Barboza     int status;
1744067927d6SDaniel Henrique Barboza 
1745992861fbSMarkus Armbruster     status = run_process_child(cmd, errp);
1746067927d6SDaniel Henrique Barboza 
1747067927d6SDaniel Henrique Barboza     /*
1748067927d6SDaniel Henrique Barboza      * systemctl status uses LSB return codes so we can expect
1749067927d6SDaniel Henrique Barboza      * status > 0 and be ok. To assert if the guest has support
1750067927d6SDaniel Henrique Barboza      * for the selected suspend mode, status should be < 4. 4 is
1751067927d6SDaniel Henrique Barboza      * the code for unknown service status, the return value when
1752067927d6SDaniel Henrique Barboza      * the service does not exist. A common value is status = 3
1753067927d6SDaniel Henrique Barboza      * (program is not running).
1754067927d6SDaniel Henrique Barboza      */
1755067927d6SDaniel Henrique Barboza     if (status > 0 && status < 4) {
1756067927d6SDaniel Henrique Barboza         return true;
1757067927d6SDaniel Henrique Barboza     }
1758067927d6SDaniel Henrique Barboza 
1759067927d6SDaniel Henrique Barboza     return false;
1760067927d6SDaniel Henrique Barboza }
1761067927d6SDaniel Henrique Barboza 
1762067927d6SDaniel Henrique Barboza static void systemd_suspend(SuspendMode mode, Error **errp)
1763067927d6SDaniel Henrique Barboza {
1764067927d6SDaniel Henrique Barboza     Error *local_err = NULL;
1765067927d6SDaniel Henrique Barboza     const char *systemctl_args[3] = {"hibernate", "suspend", "hybrid-sleep"};
1766067927d6SDaniel Henrique Barboza     const char *cmd[3] = {"systemctl", systemctl_args[mode], NULL};
1767067927d6SDaniel Henrique Barboza     int status;
1768067927d6SDaniel Henrique Barboza 
1769067927d6SDaniel Henrique Barboza     status = run_process_child(cmd, &local_err);
1770067927d6SDaniel Henrique Barboza 
1771067927d6SDaniel Henrique Barboza     if (status == 0) {
1772067927d6SDaniel Henrique Barboza         return;
1773067927d6SDaniel Henrique Barboza     }
1774067927d6SDaniel Henrique Barboza 
1775067927d6SDaniel Henrique Barboza     if ((status == -1) && !local_err) {
1776067927d6SDaniel Henrique Barboza         error_setg(errp, "the helper program 'systemctl %s' was not found",
1777067927d6SDaniel Henrique Barboza                    systemctl_args[mode]);
1778067927d6SDaniel Henrique Barboza         return;
1779067927d6SDaniel Henrique Barboza     }
1780067927d6SDaniel Henrique Barboza 
1781067927d6SDaniel Henrique Barboza     if (local_err) {
1782067927d6SDaniel Henrique Barboza         error_propagate(errp, local_err);
1783067927d6SDaniel Henrique Barboza     } else {
1784067927d6SDaniel Henrique Barboza         error_setg(errp, "the helper program 'systemctl %s' returned an "
1785067927d6SDaniel Henrique Barboza                    "unexpected exit status code (%d)",
1786067927d6SDaniel Henrique Barboza                    systemctl_args[mode], status);
1787067927d6SDaniel Henrique Barboza     }
1788067927d6SDaniel Henrique Barboza }
1789067927d6SDaniel Henrique Barboza 
17908b020b5eSDaniel Henrique Barboza static bool pmutils_supports_mode(SuspendMode mode, Error **errp)
179111d0f125SLuiz Capitulino {
17926b26e837SLuiz Capitulino     Error *local_err = NULL;
17938b020b5eSDaniel Henrique Barboza     const char *pmutils_args[3] = {"--hibernate", "--suspend",
17948b020b5eSDaniel Henrique Barboza                                    "--suspend-hybrid"};
17958b020b5eSDaniel Henrique Barboza     const char *cmd[3] = {"pm-is-supported", pmutils_args[mode], NULL};
1796dc8764f0SLuiz Capitulino     int status;
179711d0f125SLuiz Capitulino 
17988b020b5eSDaniel Henrique Barboza     status = run_process_child(cmd, &local_err);
1799304a0fcbSDaniel Henrique Barboza 
18008b020b5eSDaniel Henrique Barboza     if (status == SUSPEND_SUPPORTED) {
18018b020b5eSDaniel Henrique Barboza         return true;
1802304a0fcbSDaniel Henrique Barboza     }
1803304a0fcbSDaniel Henrique Barboza 
18048b020b5eSDaniel Henrique Barboza     if ((status == -1) && !local_err) {
18058b020b5eSDaniel Henrique Barboza         return false;
1806a5fcf0e3SDaniel Henrique Barboza     }
180711d0f125SLuiz Capitulino 
180884d18f06SMarkus Armbruster     if (local_err) {
180977dbc81bSMarkus Armbruster         error_propagate(errp, local_err);
18108b020b5eSDaniel Henrique Barboza     } else {
181177dbc81bSMarkus Armbruster         error_setg(errp,
18128b020b5eSDaniel Henrique Barboza                    "the helper program '%s' returned an unexpected exit"
18138b020b5eSDaniel Henrique Barboza                    " status code (%d)", "pm-is-supported", status);
1814dc8764f0SLuiz Capitulino     }
181511d0f125SLuiz Capitulino 
18168b020b5eSDaniel Henrique Barboza     return false;
1817a5fcf0e3SDaniel Henrique Barboza }
1818a5fcf0e3SDaniel Henrique Barboza 
18198b020b5eSDaniel Henrique Barboza static void pmutils_suspend(SuspendMode mode, Error **errp)
1820246d76ebSDaniel Henrique Barboza {
1821246d76ebSDaniel Henrique Barboza     Error *local_err = NULL;
18228b020b5eSDaniel Henrique Barboza     const char *pmutils_binaries[3] = {"pm-hibernate", "pm-suspend",
18238b020b5eSDaniel Henrique Barboza                                        "pm-suspend-hybrid"};
18248b020b5eSDaniel Henrique Barboza     const char *cmd[2] = {pmutils_binaries[mode], NULL};
1825246d76ebSDaniel Henrique Barboza     int status;
1826246d76ebSDaniel Henrique Barboza 
18278b020b5eSDaniel Henrique Barboza     status = run_process_child(cmd, &local_err);
1828246d76ebSDaniel Henrique Barboza 
18298b020b5eSDaniel Henrique Barboza     if (status == 0) {
1830246d76ebSDaniel Henrique Barboza         return;
1831246d76ebSDaniel Henrique Barboza     }
1832246d76ebSDaniel Henrique Barboza 
18338b020b5eSDaniel Henrique Barboza     if ((status == -1) && !local_err) {
18348b020b5eSDaniel Henrique Barboza         error_setg(errp, "the helper program '%s' was not found",
18358b020b5eSDaniel Henrique Barboza                    pmutils_binaries[mode]);
1836246d76ebSDaniel Henrique Barboza         return;
1837246d76ebSDaniel Henrique Barboza     }
1838246d76ebSDaniel Henrique Barboza 
1839246d76ebSDaniel Henrique Barboza     if (local_err) {
1840246d76ebSDaniel Henrique Barboza         error_propagate(errp, local_err);
18418b020b5eSDaniel Henrique Barboza     } else {
1842246d76ebSDaniel Henrique Barboza         error_setg(errp,
18438b020b5eSDaniel Henrique Barboza                    "the helper program '%s' returned an unexpected exit"
18448b020b5eSDaniel Henrique Barboza                    " status code (%d)", pmutils_binaries[mode], status);
18458b020b5eSDaniel Henrique Barboza     }
1846246d76ebSDaniel Henrique Barboza }
1847246d76ebSDaniel Henrique Barboza 
18488b020b5eSDaniel Henrique Barboza static bool linux_sys_state_supports_mode(SuspendMode mode, Error **errp)
1849a5fcf0e3SDaniel Henrique Barboza {
18508b020b5eSDaniel Henrique Barboza     const char *sysfile_strs[3] = {"disk", "mem", NULL};
18518b020b5eSDaniel Henrique Barboza     const char *sysfile_str = sysfile_strs[mode];
1852a5fcf0e3SDaniel Henrique Barboza     char buf[32]; /* hopefully big enough */
1853a5fcf0e3SDaniel Henrique Barboza     int fd;
1854a5fcf0e3SDaniel Henrique Barboza     ssize_t ret;
1855a5fcf0e3SDaniel Henrique Barboza 
18568b020b5eSDaniel Henrique Barboza     if (!sysfile_str) {
18578b020b5eSDaniel Henrique Barboza         error_setg(errp, "unknown guest suspend mode");
1858a5fcf0e3SDaniel Henrique Barboza         return false;
1859a5fcf0e3SDaniel Henrique Barboza     }
1860a5fcf0e3SDaniel Henrique Barboza 
1861a5fcf0e3SDaniel Henrique Barboza     fd = open(LINUX_SYS_STATE_FILE, O_RDONLY);
1862a5fcf0e3SDaniel Henrique Barboza     if (fd < 0) {
1863a5fcf0e3SDaniel Henrique Barboza         return false;
1864a5fcf0e3SDaniel Henrique Barboza     }
1865a5fcf0e3SDaniel Henrique Barboza 
1866a5fcf0e3SDaniel Henrique Barboza     ret = read(fd, buf, sizeof(buf) - 1);
1867d9c745c1SPaolo Bonzini     close(fd);
1868a5fcf0e3SDaniel Henrique Barboza     if (ret <= 0) {
1869a5fcf0e3SDaniel Henrique Barboza         return false;
1870a5fcf0e3SDaniel Henrique Barboza     }
1871a5fcf0e3SDaniel Henrique Barboza     buf[ret] = '\0';
1872a5fcf0e3SDaniel Henrique Barboza 
1873a5fcf0e3SDaniel Henrique Barboza     if (strstr(buf, sysfile_str)) {
1874a5fcf0e3SDaniel Henrique Barboza         return true;
1875a5fcf0e3SDaniel Henrique Barboza     }
1876a5fcf0e3SDaniel Henrique Barboza     return false;
1877a5fcf0e3SDaniel Henrique Barboza }
1878a5fcf0e3SDaniel Henrique Barboza 
18798b020b5eSDaniel Henrique Barboza static void linux_sys_state_suspend(SuspendMode mode, Error **errp)
1880246d76ebSDaniel Henrique Barboza {
1881246d76ebSDaniel Henrique Barboza     Error *local_err = NULL;
18828b020b5eSDaniel Henrique Barboza     const char *sysfile_strs[3] = {"disk", "mem", NULL};
18838b020b5eSDaniel Henrique Barboza     const char *sysfile_str = sysfile_strs[mode];
1884246d76ebSDaniel Henrique Barboza     pid_t pid;
1885246d76ebSDaniel Henrique Barboza     int status;
1886246d76ebSDaniel Henrique Barboza 
18878b020b5eSDaniel Henrique Barboza     if (!sysfile_str) {
1888246d76ebSDaniel Henrique Barboza         error_setg(errp, "unknown guest suspend mode");
1889246d76ebSDaniel Henrique Barboza         return;
1890246d76ebSDaniel Henrique Barboza     }
1891246d76ebSDaniel Henrique Barboza 
1892246d76ebSDaniel Henrique Barboza     pid = fork();
1893246d76ebSDaniel Henrique Barboza     if (!pid) {
1894246d76ebSDaniel Henrique Barboza         /* child */
1895246d76ebSDaniel Henrique Barboza         int fd;
1896246d76ebSDaniel Henrique Barboza 
1897246d76ebSDaniel Henrique Barboza         setsid();
1898246d76ebSDaniel Henrique Barboza         reopen_fd_to_null(0);
1899246d76ebSDaniel Henrique Barboza         reopen_fd_to_null(1);
1900246d76ebSDaniel Henrique Barboza         reopen_fd_to_null(2);
1901246d76ebSDaniel Henrique Barboza 
1902246d76ebSDaniel Henrique Barboza         fd = open(LINUX_SYS_STATE_FILE, O_WRONLY);
1903246d76ebSDaniel Henrique Barboza         if (fd < 0) {
1904246d76ebSDaniel Henrique Barboza             _exit(EXIT_FAILURE);
1905246d76ebSDaniel Henrique Barboza         }
1906246d76ebSDaniel Henrique Barboza 
1907246d76ebSDaniel Henrique Barboza         if (write(fd, sysfile_str, strlen(sysfile_str)) < 0) {
1908246d76ebSDaniel Henrique Barboza             _exit(EXIT_FAILURE);
1909246d76ebSDaniel Henrique Barboza         }
1910246d76ebSDaniel Henrique Barboza 
1911246d76ebSDaniel Henrique Barboza         _exit(EXIT_SUCCESS);
1912246d76ebSDaniel Henrique Barboza     } else if (pid < 0) {
1913246d76ebSDaniel Henrique Barboza         error_setg_errno(errp, errno, "failed to create child process");
1914246d76ebSDaniel Henrique Barboza         return;
1915246d76ebSDaniel Henrique Barboza     }
1916246d76ebSDaniel Henrique Barboza 
1917246d76ebSDaniel Henrique Barboza     ga_wait_child(pid, &status, &local_err);
1918246d76ebSDaniel Henrique Barboza     if (local_err) {
1919246d76ebSDaniel Henrique Barboza         error_propagate(errp, local_err);
1920246d76ebSDaniel Henrique Barboza         return;
1921246d76ebSDaniel Henrique Barboza     }
1922246d76ebSDaniel Henrique Barboza 
1923246d76ebSDaniel Henrique Barboza     if (WEXITSTATUS(status)) {
1924246d76ebSDaniel Henrique Barboza         error_setg(errp, "child process has failed to suspend");
1925246d76ebSDaniel Henrique Barboza     }
1926246d76ebSDaniel Henrique Barboza 
1927246d76ebSDaniel Henrique Barboza }
1928246d76ebSDaniel Henrique Barboza 
19298b020b5eSDaniel Henrique Barboza static void guest_suspend(SuspendMode mode, Error **errp)
193011d0f125SLuiz Capitulino {
19317b376087SLuiz Capitulino     Error *local_err = NULL;
193273e1d8ebSDaniel Henrique Barboza     bool mode_supported = false;
193311d0f125SLuiz Capitulino 
193473e1d8ebSDaniel Henrique Barboza     if (systemd_supports_mode(mode, &local_err)) {
193573e1d8ebSDaniel Henrique Barboza         mode_supported = true;
1936067927d6SDaniel Henrique Barboza         systemd_suspend(mode, &local_err);
193773e1d8ebSDaniel Henrique Barboza     }
193873e1d8ebSDaniel Henrique Barboza 
1939067927d6SDaniel Henrique Barboza     if (!local_err) {
1940067927d6SDaniel Henrique Barboza         return;
1941067927d6SDaniel Henrique Barboza     }
1942067927d6SDaniel Henrique Barboza 
1943067927d6SDaniel Henrique Barboza     error_free(local_err);
19446a4a3853SVladimir Sementsov-Ogievskiy     local_err = NULL;
1945067927d6SDaniel Henrique Barboza 
194673e1d8ebSDaniel Henrique Barboza     if (pmutils_supports_mode(mode, &local_err)) {
194773e1d8ebSDaniel Henrique Barboza         mode_supported = true;
19488b020b5eSDaniel Henrique Barboza         pmutils_suspend(mode, &local_err);
194973e1d8ebSDaniel Henrique Barboza     }
195073e1d8ebSDaniel Henrique Barboza 
1951246d76ebSDaniel Henrique Barboza     if (!local_err) {
1952304a0fcbSDaniel Henrique Barboza         return;
1953304a0fcbSDaniel Henrique Barboza     }
1954304a0fcbSDaniel Henrique Barboza 
1955246d76ebSDaniel Henrique Barboza     error_free(local_err);
19566a4a3853SVladimir Sementsov-Ogievskiy     local_err = NULL;
195711d0f125SLuiz Capitulino 
195873e1d8ebSDaniel Henrique Barboza     if (linux_sys_state_supports_mode(mode, &local_err)) {
195973e1d8ebSDaniel Henrique Barboza         mode_supported = true;
19608b020b5eSDaniel Henrique Barboza         linux_sys_state_suspend(mode, &local_err);
196173e1d8ebSDaniel Henrique Barboza     }
196273e1d8ebSDaniel Henrique Barboza 
196373e1d8ebSDaniel Henrique Barboza     if (!mode_supported) {
19646a4a3853SVladimir Sementsov-Ogievskiy         error_free(local_err);
196573e1d8ebSDaniel Henrique Barboza         error_setg(errp,
196673e1d8ebSDaniel Henrique Barboza                    "the requested suspend mode is not supported by the guest");
1967b2322003SMarkus Armbruster     } else {
196877dbc81bSMarkus Armbruster         error_propagate(errp, local_err);
19697b376087SLuiz Capitulino     }
197011d0f125SLuiz Capitulino }
197111d0f125SLuiz Capitulino 
197277dbc81bSMarkus Armbruster void qmp_guest_suspend_disk(Error **errp)
197311d0f125SLuiz Capitulino {
1974304a0fcbSDaniel Henrique Barboza     guest_suspend(SUSPEND_MODE_DISK, errp);
197511d0f125SLuiz Capitulino }
197611d0f125SLuiz Capitulino 
197777dbc81bSMarkus Armbruster void qmp_guest_suspend_ram(Error **errp)
1978fbf42210SLuiz Capitulino {
1979304a0fcbSDaniel Henrique Barboza     guest_suspend(SUSPEND_MODE_RAM, errp);
1980fbf42210SLuiz Capitulino }
1981fbf42210SLuiz Capitulino 
198277dbc81bSMarkus Armbruster void qmp_guest_suspend_hybrid(Error **errp)
198395f4f404SLuiz Capitulino {
1984304a0fcbSDaniel Henrique Barboza     guest_suspend(SUSPEND_MODE_HYBRID, errp);
198595f4f404SLuiz Capitulino }
198695f4f404SLuiz Capitulino 
1987d2baff62SLaszlo Ersek /* Transfer online/offline status between @vcpu and the guest system.
1988d2baff62SLaszlo Ersek  *
1989d2baff62SLaszlo Ersek  * On input either @errp or *@errp must be NULL.
1990d2baff62SLaszlo Ersek  *
1991d2baff62SLaszlo Ersek  * In system-to-@vcpu direction, the following @vcpu fields are accessed:
1992d2baff62SLaszlo Ersek  * - R: vcpu->logical_id
1993d2baff62SLaszlo Ersek  * - W: vcpu->online
1994d2baff62SLaszlo Ersek  * - W: vcpu->can_offline
1995d2baff62SLaszlo Ersek  *
1996d2baff62SLaszlo Ersek  * In @vcpu-to-system direction, the following @vcpu fields are accessed:
1997d2baff62SLaszlo Ersek  * - R: vcpu->logical_id
1998d2baff62SLaszlo Ersek  * - R: vcpu->online
1999d2baff62SLaszlo Ersek  *
2000d2baff62SLaszlo Ersek  * Written members remain unmodified on error.
2001d2baff62SLaszlo Ersek  */
2002d2baff62SLaszlo Ersek static void transfer_vcpu(GuestLogicalProcessor *vcpu, bool sys2vcpu,
2003b4bf912aSIgor Mammedov                           char *dirpath, Error **errp)
2004d2baff62SLaszlo Ersek {
2005b4bf912aSIgor Mammedov     int fd;
2006b4bf912aSIgor Mammedov     int res;
2007d2baff62SLaszlo Ersek     int dirfd;
2008b4bf912aSIgor Mammedov     static const char fn[] = "online";
2009d2baff62SLaszlo Ersek 
2010d2baff62SLaszlo Ersek     dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
2011d2baff62SLaszlo Ersek     if (dirfd == -1) {
2012d2baff62SLaszlo Ersek         error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
2013b4bf912aSIgor Mammedov         return;
2014b4bf912aSIgor Mammedov     }
2015d2baff62SLaszlo Ersek 
2016d2baff62SLaszlo Ersek     fd = openat(dirfd, fn, sys2vcpu ? O_RDONLY : O_RDWR);
2017d2baff62SLaszlo Ersek     if (fd == -1) {
2018d2baff62SLaszlo Ersek         if (errno != ENOENT) {
2019d2baff62SLaszlo Ersek             error_setg_errno(errp, errno, "open(\"%s/%s\")", dirpath, fn);
2020d2baff62SLaszlo Ersek         } else if (sys2vcpu) {
2021d2baff62SLaszlo Ersek             vcpu->online = true;
2022d2baff62SLaszlo Ersek             vcpu->can_offline = false;
2023d2baff62SLaszlo Ersek         } else if (!vcpu->online) {
2024d2baff62SLaszlo Ersek             error_setg(errp, "logical processor #%" PRId64 " can't be "
2025d2baff62SLaszlo Ersek                        "offlined", vcpu->logical_id);
2026d2baff62SLaszlo Ersek         } /* otherwise pretend successful re-onlining */
2027d2baff62SLaszlo Ersek     } else {
2028d2baff62SLaszlo Ersek         unsigned char status;
2029d2baff62SLaszlo Ersek 
2030d2baff62SLaszlo Ersek         res = pread(fd, &status, 1, 0);
2031d2baff62SLaszlo Ersek         if (res == -1) {
2032d2baff62SLaszlo Ersek             error_setg_errno(errp, errno, "pread(\"%s/%s\")", dirpath, fn);
2033d2baff62SLaszlo Ersek         } else if (res == 0) {
2034d2baff62SLaszlo Ersek             error_setg(errp, "pread(\"%s/%s\"): unexpected EOF", dirpath,
2035d2baff62SLaszlo Ersek                        fn);
2036d2baff62SLaszlo Ersek         } else if (sys2vcpu) {
2037d2baff62SLaszlo Ersek             vcpu->online = (status != '0');
2038d2baff62SLaszlo Ersek             vcpu->can_offline = true;
2039d2baff62SLaszlo Ersek         } else if (vcpu->online != (status != '0')) {
2040d2baff62SLaszlo Ersek             status = '0' + vcpu->online;
2041d2baff62SLaszlo Ersek             if (pwrite(fd, &status, 1, 0) == -1) {
2042d2baff62SLaszlo Ersek                 error_setg_errno(errp, errno, "pwrite(\"%s/%s\")", dirpath,
2043d2baff62SLaszlo Ersek                                  fn);
2044d2baff62SLaszlo Ersek             }
2045d2baff62SLaszlo Ersek         } /* otherwise pretend successful re-(on|off)-lining */
2046d2baff62SLaszlo Ersek 
2047d2baff62SLaszlo Ersek         res = close(fd);
2048d2baff62SLaszlo Ersek         g_assert(res == 0);
2049d2baff62SLaszlo Ersek     }
2050d2baff62SLaszlo Ersek 
2051d2baff62SLaszlo Ersek     res = close(dirfd);
2052d2baff62SLaszlo Ersek     g_assert(res == 0);
2053d2baff62SLaszlo Ersek }
2054d2baff62SLaszlo Ersek 
2055d2baff62SLaszlo Ersek GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
2056d2baff62SLaszlo Ersek {
2057c3033fd3SEric Blake     GuestLogicalProcessorList *head, **tail;
205827e7de3cSLin Ma     const char *cpu_dir = "/sys/devices/system/cpu";
205927e7de3cSLin Ma     const gchar *line;
206027e7de3cSLin Ma     g_autoptr(GDir) cpu_gdir = NULL;
2061d2baff62SLaszlo Ersek     Error *local_err = NULL;
2062d2baff62SLaszlo Ersek 
2063d2baff62SLaszlo Ersek     head = NULL;
2064c3033fd3SEric Blake     tail = &head;
206527e7de3cSLin Ma     cpu_gdir = g_dir_open(cpu_dir, 0, NULL);
2066d2baff62SLaszlo Ersek 
206727e7de3cSLin Ma     if (cpu_gdir == NULL) {
206827e7de3cSLin Ma         error_setg_errno(errp, errno, "failed to list entries: %s", cpu_dir);
206927e7de3cSLin Ma         return NULL;
207027e7de3cSLin Ma     }
207127e7de3cSLin Ma 
207227e7de3cSLin Ma     while (local_err == NULL && (line = g_dir_read_name(cpu_gdir)) != NULL) {
2073d2baff62SLaszlo Ersek         GuestLogicalProcessor *vcpu;
207427e7de3cSLin Ma         int64_t id;
207527e7de3cSLin Ma         if (sscanf(line, "cpu%" PRId64, &id)) {
207627e7de3cSLin Ma             g_autofree char *path = g_strdup_printf("/sys/devices/system/cpu/"
207727e7de3cSLin Ma                                                     "cpu%" PRId64 "/", id);
2078d2baff62SLaszlo Ersek             vcpu = g_malloc0(sizeof *vcpu);
2079b4bf912aSIgor Mammedov             vcpu->logical_id = id;
2080d2baff62SLaszlo Ersek             vcpu->has_can_offline = true; /* lolspeak ftw */
2081b4bf912aSIgor Mammedov             transfer_vcpu(vcpu, true, path, &local_err);
2082c3033fd3SEric Blake             QAPI_LIST_APPEND(tail, vcpu);
2083d2baff62SLaszlo Ersek         }
2084b4bf912aSIgor Mammedov     }
2085d2baff62SLaszlo Ersek 
2086d2baff62SLaszlo Ersek     if (local_err == NULL) {
2087d2baff62SLaszlo Ersek         /* there's no guest with zero VCPUs */
2088d2baff62SLaszlo Ersek         g_assert(head != NULL);
2089d2baff62SLaszlo Ersek         return head;
2090d2baff62SLaszlo Ersek     }
2091d2baff62SLaszlo Ersek 
2092d2baff62SLaszlo Ersek     qapi_free_GuestLogicalProcessorList(head);
2093d2baff62SLaszlo Ersek     error_propagate(errp, local_err);
2094d2baff62SLaszlo Ersek     return NULL;
2095d2baff62SLaszlo Ersek }
2096d2baff62SLaszlo Ersek 
2097cbb65fc2SLaszlo Ersek int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
2098cbb65fc2SLaszlo Ersek {
2099cbb65fc2SLaszlo Ersek     int64_t processed;
2100cbb65fc2SLaszlo Ersek     Error *local_err = NULL;
2101cbb65fc2SLaszlo Ersek 
2102cbb65fc2SLaszlo Ersek     processed = 0;
2103cbb65fc2SLaszlo Ersek     while (vcpus != NULL) {
2104b4bf912aSIgor Mammedov         char *path = g_strdup_printf("/sys/devices/system/cpu/cpu%" PRId64 "/",
2105b4bf912aSIgor Mammedov                                      vcpus->value->logical_id);
2106b4bf912aSIgor Mammedov 
2107b4bf912aSIgor Mammedov         transfer_vcpu(vcpus->value, false, path, &local_err);
2108b4bf912aSIgor Mammedov         g_free(path);
2109cbb65fc2SLaszlo Ersek         if (local_err != NULL) {
2110cbb65fc2SLaszlo Ersek             break;
2111cbb65fc2SLaszlo Ersek         }
2112cbb65fc2SLaszlo Ersek         ++processed;
2113cbb65fc2SLaszlo Ersek         vcpus = vcpus->next;
2114cbb65fc2SLaszlo Ersek     }
2115cbb65fc2SLaszlo Ersek 
2116cbb65fc2SLaszlo Ersek     if (local_err != NULL) {
2117cbb65fc2SLaszlo Ersek         if (processed == 0) {
2118cbb65fc2SLaszlo Ersek             error_propagate(errp, local_err);
2119cbb65fc2SLaszlo Ersek         } else {
2120cbb65fc2SLaszlo Ersek             error_free(local_err);
2121cbb65fc2SLaszlo Ersek         }
2122cbb65fc2SLaszlo Ersek     }
2123cbb65fc2SLaszlo Ersek 
2124cbb65fc2SLaszlo Ersek     return processed;
2125cbb65fc2SLaszlo Ersek }
2126cbb65fc2SLaszlo Ersek 
2127215a2771SDaniel P. Berrange void qmp_guest_set_user_password(const char *username,
2128215a2771SDaniel P. Berrange                                  const char *password,
2129215a2771SDaniel P. Berrange                                  bool crypted,
2130215a2771SDaniel P. Berrange                                  Error **errp)
2131215a2771SDaniel P. Berrange {
2132215a2771SDaniel P. Berrange     Error *local_err = NULL;
2133215a2771SDaniel P. Berrange     char *passwd_path = NULL;
2134215a2771SDaniel P. Berrange     pid_t pid;
2135215a2771SDaniel P. Berrange     int status;
2136215a2771SDaniel P. Berrange     int datafd[2] = { -1, -1 };
2137215a2771SDaniel P. Berrange     char *rawpasswddata = NULL;
2138215a2771SDaniel P. Berrange     size_t rawpasswdlen;
2139215a2771SDaniel P. Berrange     char *chpasswddata = NULL;
2140215a2771SDaniel P. Berrange     size_t chpasswdlen;
2141215a2771SDaniel P. Berrange 
2142920639caSDaniel P. Berrange     rawpasswddata = (char *)qbase64_decode(password, -1, &rawpasswdlen, errp);
2143920639caSDaniel P. Berrange     if (!rawpasswddata) {
2144920639caSDaniel P. Berrange         return;
2145920639caSDaniel P. Berrange     }
2146215a2771SDaniel P. Berrange     rawpasswddata = g_renew(char, rawpasswddata, rawpasswdlen + 1);
2147215a2771SDaniel P. Berrange     rawpasswddata[rawpasswdlen] = '\0';
2148215a2771SDaniel P. Berrange 
2149215a2771SDaniel P. Berrange     if (strchr(rawpasswddata, '\n')) {
2150215a2771SDaniel P. Berrange         error_setg(errp, "forbidden characters in raw password");
2151215a2771SDaniel P. Berrange         goto out;
2152215a2771SDaniel P. Berrange     }
2153215a2771SDaniel P. Berrange 
2154215a2771SDaniel P. Berrange     if (strchr(username, '\n') ||
2155215a2771SDaniel P. Berrange         strchr(username, ':')) {
2156215a2771SDaniel P. Berrange         error_setg(errp, "forbidden characters in username");
2157215a2771SDaniel P. Berrange         goto out;
2158215a2771SDaniel P. Berrange     }
2159215a2771SDaniel P. Berrange 
2160215a2771SDaniel P. Berrange     chpasswddata = g_strdup_printf("%s:%s\n", username, rawpasswddata);
2161215a2771SDaniel P. Berrange     chpasswdlen = strlen(chpasswddata);
2162215a2771SDaniel P. Berrange 
2163215a2771SDaniel P. Berrange     passwd_path = g_find_program_in_path("chpasswd");
2164215a2771SDaniel P. Berrange 
2165215a2771SDaniel P. Berrange     if (!passwd_path) {
2166215a2771SDaniel P. Berrange         error_setg(errp, "cannot find 'passwd' program in PATH");
2167215a2771SDaniel P. Berrange         goto out;
2168215a2771SDaniel P. Berrange     }
2169215a2771SDaniel P. Berrange 
2170ed78331dSMarc-André Lureau     if (!g_unix_open_pipe(datafd, FD_CLOEXEC, NULL)) {
2171215a2771SDaniel P. Berrange         error_setg(errp, "cannot create pipe FDs");
2172215a2771SDaniel P. Berrange         goto out;
2173215a2771SDaniel P. Berrange     }
2174215a2771SDaniel P. Berrange 
2175215a2771SDaniel P. Berrange     pid = fork();
2176215a2771SDaniel P. Berrange     if (pid == 0) {
2177215a2771SDaniel P. Berrange         close(datafd[1]);
2178215a2771SDaniel P. Berrange         /* child */
2179215a2771SDaniel P. Berrange         setsid();
2180215a2771SDaniel P. Berrange         dup2(datafd[0], 0);
2181215a2771SDaniel P. Berrange         reopen_fd_to_null(1);
2182215a2771SDaniel P. Berrange         reopen_fd_to_null(2);
2183215a2771SDaniel P. Berrange 
2184215a2771SDaniel P. Berrange         if (crypted) {
2185fcc41961SMarc-André Lureau             execl(passwd_path, "chpasswd", "-e", NULL);
2186215a2771SDaniel P. Berrange         } else {
2187fcc41961SMarc-André Lureau             execl(passwd_path, "chpasswd", NULL);
2188215a2771SDaniel P. Berrange         }
2189215a2771SDaniel P. Berrange         _exit(EXIT_FAILURE);
2190215a2771SDaniel P. Berrange     } else if (pid < 0) {
2191215a2771SDaniel P. Berrange         error_setg_errno(errp, errno, "failed to create child process");
2192215a2771SDaniel P. Berrange         goto out;
2193215a2771SDaniel P. Berrange     }
2194215a2771SDaniel P. Berrange     close(datafd[0]);
2195215a2771SDaniel P. Berrange     datafd[0] = -1;
2196215a2771SDaniel P. Berrange 
2197215a2771SDaniel P. Berrange     if (qemu_write_full(datafd[1], chpasswddata, chpasswdlen) != chpasswdlen) {
2198215a2771SDaniel P. Berrange         error_setg_errno(errp, errno, "cannot write new account password");
2199215a2771SDaniel P. Berrange         goto out;
2200215a2771SDaniel P. Berrange     }
2201215a2771SDaniel P. Berrange     close(datafd[1]);
2202215a2771SDaniel P. Berrange     datafd[1] = -1;
2203215a2771SDaniel P. Berrange 
2204215a2771SDaniel P. Berrange     ga_wait_child(pid, &status, &local_err);
2205215a2771SDaniel P. Berrange     if (local_err) {
2206215a2771SDaniel P. Berrange         error_propagate(errp, local_err);
2207215a2771SDaniel P. Berrange         goto out;
2208215a2771SDaniel P. Berrange     }
2209215a2771SDaniel P. Berrange 
2210215a2771SDaniel P. Berrange     if (!WIFEXITED(status)) {
2211215a2771SDaniel P. Berrange         error_setg(errp, "child process has terminated abnormally");
2212215a2771SDaniel P. Berrange         goto out;
2213215a2771SDaniel P. Berrange     }
2214215a2771SDaniel P. Berrange 
2215215a2771SDaniel P. Berrange     if (WEXITSTATUS(status)) {
2216215a2771SDaniel P. Berrange         error_setg(errp, "child process has failed to set user password");
2217215a2771SDaniel P. Berrange         goto out;
2218215a2771SDaniel P. Berrange     }
2219215a2771SDaniel P. Berrange 
2220215a2771SDaniel P. Berrange out:
2221215a2771SDaniel P. Berrange     g_free(chpasswddata);
2222215a2771SDaniel P. Berrange     g_free(rawpasswddata);
2223215a2771SDaniel P. Berrange     g_free(passwd_path);
2224215a2771SDaniel P. Berrange     if (datafd[0] != -1) {
2225215a2771SDaniel P. Berrange         close(datafd[0]);
2226215a2771SDaniel P. Berrange     }
2227215a2771SDaniel P. Berrange     if (datafd[1] != -1) {
2228215a2771SDaniel P. Berrange         close(datafd[1]);
2229215a2771SDaniel P. Berrange     }
2230215a2771SDaniel P. Berrange }
2231215a2771SDaniel P. Berrange 
2232bd240fcaSzhanghailiang static void ga_read_sysfs_file(int dirfd, const char *pathname, char *buf,
2233bd240fcaSzhanghailiang                                int size, Error **errp)
2234bd240fcaSzhanghailiang {
2235bd240fcaSzhanghailiang     int fd;
2236bd240fcaSzhanghailiang     int res;
2237bd240fcaSzhanghailiang 
2238bd240fcaSzhanghailiang     errno = 0;
2239bd240fcaSzhanghailiang     fd = openat(dirfd, pathname, O_RDONLY);
2240bd240fcaSzhanghailiang     if (fd == -1) {
2241bd240fcaSzhanghailiang         error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
2242bd240fcaSzhanghailiang         return;
2243bd240fcaSzhanghailiang     }
2244bd240fcaSzhanghailiang 
2245bd240fcaSzhanghailiang     res = pread(fd, buf, size, 0);
2246bd240fcaSzhanghailiang     if (res == -1) {
2247bd240fcaSzhanghailiang         error_setg_errno(errp, errno, "pread sysfs file \"%s\"", pathname);
2248bd240fcaSzhanghailiang     } else if (res == 0) {
2249bd240fcaSzhanghailiang         error_setg(errp, "pread sysfs file \"%s\": unexpected EOF", pathname);
2250bd240fcaSzhanghailiang     }
2251bd240fcaSzhanghailiang     close(fd);
2252bd240fcaSzhanghailiang }
2253bd240fcaSzhanghailiang 
2254bd240fcaSzhanghailiang static void ga_write_sysfs_file(int dirfd, const char *pathname,
2255bd240fcaSzhanghailiang                                 const char *buf, int size, Error **errp)
2256bd240fcaSzhanghailiang {
2257bd240fcaSzhanghailiang     int fd;
2258bd240fcaSzhanghailiang 
2259bd240fcaSzhanghailiang     errno = 0;
2260bd240fcaSzhanghailiang     fd = openat(dirfd, pathname, O_WRONLY);
2261bd240fcaSzhanghailiang     if (fd == -1) {
2262bd240fcaSzhanghailiang         error_setg_errno(errp, errno, "open sysfs file \"%s\"", pathname);
2263bd240fcaSzhanghailiang         return;
2264bd240fcaSzhanghailiang     }
2265bd240fcaSzhanghailiang 
2266bd240fcaSzhanghailiang     if (pwrite(fd, buf, size, 0) == -1) {
2267bd240fcaSzhanghailiang         error_setg_errno(errp, errno, "pwrite sysfs file \"%s\"", pathname);
2268bd240fcaSzhanghailiang     }
2269bd240fcaSzhanghailiang 
2270bd240fcaSzhanghailiang     close(fd);
2271bd240fcaSzhanghailiang }
2272bd240fcaSzhanghailiang 
2273bd240fcaSzhanghailiang /* Transfer online/offline status between @mem_blk and the guest system.
2274bd240fcaSzhanghailiang  *
2275bd240fcaSzhanghailiang  * On input either @errp or *@errp must be NULL.
2276bd240fcaSzhanghailiang  *
2277bd240fcaSzhanghailiang  * In system-to-@mem_blk direction, the following @mem_blk fields are accessed:
2278bd240fcaSzhanghailiang  * - R: mem_blk->phys_index
2279bd240fcaSzhanghailiang  * - W: mem_blk->online
2280bd240fcaSzhanghailiang  * - W: mem_blk->can_offline
2281bd240fcaSzhanghailiang  *
2282bd240fcaSzhanghailiang  * In @mem_blk-to-system direction, the following @mem_blk fields are accessed:
2283bd240fcaSzhanghailiang  * - R: mem_blk->phys_index
2284bd240fcaSzhanghailiang  * - R: mem_blk->online
2285bd240fcaSzhanghailiang  *-  R: mem_blk->can_offline
2286bd240fcaSzhanghailiang  * Written members remain unmodified on error.
2287bd240fcaSzhanghailiang  */
2288bd240fcaSzhanghailiang static void transfer_memory_block(GuestMemoryBlock *mem_blk, bool sys2memblk,
2289bd240fcaSzhanghailiang                                   GuestMemoryBlockResponse *result,
2290bd240fcaSzhanghailiang                                   Error **errp)
2291bd240fcaSzhanghailiang {
2292bd240fcaSzhanghailiang     char *dirpath;
2293bd240fcaSzhanghailiang     int dirfd;
2294bd240fcaSzhanghailiang     char *status;
2295bd240fcaSzhanghailiang     Error *local_err = NULL;
2296bd240fcaSzhanghailiang 
2297bd240fcaSzhanghailiang     if (!sys2memblk) {
2298bd240fcaSzhanghailiang         DIR *dp;
2299bd240fcaSzhanghailiang 
2300bd240fcaSzhanghailiang         if (!result) {
2301bd240fcaSzhanghailiang             error_setg(errp, "Internal error, 'result' should not be NULL");
2302bd240fcaSzhanghailiang             return;
2303bd240fcaSzhanghailiang         }
2304bd240fcaSzhanghailiang         errno = 0;
2305bd240fcaSzhanghailiang         dp = opendir("/sys/devices/system/memory/");
2306bd240fcaSzhanghailiang          /* if there is no 'memory' directory in sysfs,
2307bd240fcaSzhanghailiang          * we think this VM does not support online/offline memory block,
2308bd240fcaSzhanghailiang          * any other solution?
2309bd240fcaSzhanghailiang          */
23109879f5acSPhilippe Mathieu-Daudé         if (!dp) {
23119879f5acSPhilippe Mathieu-Daudé             if (errno == ENOENT) {
2312bd240fcaSzhanghailiang                 result->response =
2313bd240fcaSzhanghailiang                     GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
23149879f5acSPhilippe Mathieu-Daudé             }
2315bd240fcaSzhanghailiang             goto out1;
2316bd240fcaSzhanghailiang         }
2317bd240fcaSzhanghailiang         closedir(dp);
2318bd240fcaSzhanghailiang     }
2319bd240fcaSzhanghailiang 
2320bd240fcaSzhanghailiang     dirpath = g_strdup_printf("/sys/devices/system/memory/memory%" PRId64 "/",
2321bd240fcaSzhanghailiang                               mem_blk->phys_index);
2322bd240fcaSzhanghailiang     dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
2323bd240fcaSzhanghailiang     if (dirfd == -1) {
2324bd240fcaSzhanghailiang         if (sys2memblk) {
2325bd240fcaSzhanghailiang             error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
2326bd240fcaSzhanghailiang         } else {
2327bd240fcaSzhanghailiang             if (errno == ENOENT) {
2328bd240fcaSzhanghailiang                 result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_NOT_FOUND;
2329bd240fcaSzhanghailiang             } else {
2330bd240fcaSzhanghailiang                 result->response =
2331bd240fcaSzhanghailiang                     GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
2332bd240fcaSzhanghailiang             }
2333bd240fcaSzhanghailiang         }
2334bd240fcaSzhanghailiang         g_free(dirpath);
2335bd240fcaSzhanghailiang         goto out1;
2336bd240fcaSzhanghailiang     }
2337bd240fcaSzhanghailiang     g_free(dirpath);
2338bd240fcaSzhanghailiang 
2339bd240fcaSzhanghailiang     status = g_malloc0(10);
2340bd240fcaSzhanghailiang     ga_read_sysfs_file(dirfd, "state", status, 10, &local_err);
2341bd240fcaSzhanghailiang     if (local_err) {
2342bd240fcaSzhanghailiang         /* treat with sysfs file that not exist in old kernel */
2343bd240fcaSzhanghailiang         if (errno == ENOENT) {
2344bd240fcaSzhanghailiang             error_free(local_err);
2345bd240fcaSzhanghailiang             if (sys2memblk) {
2346bd240fcaSzhanghailiang                 mem_blk->online = true;
2347bd240fcaSzhanghailiang                 mem_blk->can_offline = false;
2348bd240fcaSzhanghailiang             } else if (!mem_blk->online) {
2349bd240fcaSzhanghailiang                 result->response =
2350bd240fcaSzhanghailiang                     GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_NOT_SUPPORTED;
2351bd240fcaSzhanghailiang             }
2352bd240fcaSzhanghailiang         } else {
2353bd240fcaSzhanghailiang             if (sys2memblk) {
2354bd240fcaSzhanghailiang                 error_propagate(errp, local_err);
2355bd240fcaSzhanghailiang             } else {
2356b368123dSMarkus Armbruster                 error_free(local_err);
2357bd240fcaSzhanghailiang                 result->response =
2358bd240fcaSzhanghailiang                     GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
2359bd240fcaSzhanghailiang             }
2360bd240fcaSzhanghailiang         }
2361bd240fcaSzhanghailiang         goto out2;
2362bd240fcaSzhanghailiang     }
2363bd240fcaSzhanghailiang 
2364bd240fcaSzhanghailiang     if (sys2memblk) {
2365bd240fcaSzhanghailiang         char removable = '0';
2366bd240fcaSzhanghailiang 
2367bd240fcaSzhanghailiang         mem_blk->online = (strncmp(status, "online", 6) == 0);
2368bd240fcaSzhanghailiang 
2369bd240fcaSzhanghailiang         ga_read_sysfs_file(dirfd, "removable", &removable, 1, &local_err);
2370bd240fcaSzhanghailiang         if (local_err) {
237167cc32ebSVeres Lajos             /* if no 'removable' file, it doesn't support offline mem blk */
2372bd240fcaSzhanghailiang             if (errno == ENOENT) {
2373bd240fcaSzhanghailiang                 error_free(local_err);
2374bd240fcaSzhanghailiang                 mem_blk->can_offline = false;
2375bd240fcaSzhanghailiang             } else {
2376bd240fcaSzhanghailiang                 error_propagate(errp, local_err);
2377bd240fcaSzhanghailiang             }
2378bd240fcaSzhanghailiang         } else {
2379bd240fcaSzhanghailiang             mem_blk->can_offline = (removable != '0');
2380bd240fcaSzhanghailiang         }
2381bd240fcaSzhanghailiang     } else {
2382bd240fcaSzhanghailiang         if (mem_blk->online != (strncmp(status, "online", 6) == 0)) {
23837064024dSMarc-André Lureau             const char *new_state = mem_blk->online ? "online" : "offline";
2384bd240fcaSzhanghailiang 
2385bd240fcaSzhanghailiang             ga_write_sysfs_file(dirfd, "state", new_state, strlen(new_state),
2386bd240fcaSzhanghailiang                                 &local_err);
2387bd240fcaSzhanghailiang             if (local_err) {
2388bd240fcaSzhanghailiang                 error_free(local_err);
2389bd240fcaSzhanghailiang                 result->response =
2390bd240fcaSzhanghailiang                     GUEST_MEMORY_BLOCK_RESPONSE_TYPE_OPERATION_FAILED;
2391bd240fcaSzhanghailiang                 goto out2;
2392bd240fcaSzhanghailiang             }
2393bd240fcaSzhanghailiang 
2394bd240fcaSzhanghailiang             result->response = GUEST_MEMORY_BLOCK_RESPONSE_TYPE_SUCCESS;
2395bd240fcaSzhanghailiang             result->has_error_code = false;
2396bd240fcaSzhanghailiang         } /* otherwise pretend successful re-(on|off)-lining */
2397bd240fcaSzhanghailiang     }
2398bd240fcaSzhanghailiang     g_free(status);
2399bd240fcaSzhanghailiang     close(dirfd);
2400bd240fcaSzhanghailiang     return;
2401bd240fcaSzhanghailiang 
2402bd240fcaSzhanghailiang out2:
2403bd240fcaSzhanghailiang     g_free(status);
2404bd240fcaSzhanghailiang     close(dirfd);
2405bd240fcaSzhanghailiang out1:
2406bd240fcaSzhanghailiang     if (!sys2memblk) {
2407bd240fcaSzhanghailiang         result->has_error_code = true;
2408bd240fcaSzhanghailiang         result->error_code = errno;
2409bd240fcaSzhanghailiang     }
2410bd240fcaSzhanghailiang }
2411bd240fcaSzhanghailiang 
2412a065aaa9Szhanghailiang GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
2413a065aaa9Szhanghailiang {
2414c3033fd3SEric Blake     GuestMemoryBlockList *head, **tail;
2415bd240fcaSzhanghailiang     Error *local_err = NULL;
2416bd240fcaSzhanghailiang     struct dirent *de;
2417bd240fcaSzhanghailiang     DIR *dp;
2418bd240fcaSzhanghailiang 
2419bd240fcaSzhanghailiang     head = NULL;
2420c3033fd3SEric Blake     tail = &head;
2421bd240fcaSzhanghailiang 
2422bd240fcaSzhanghailiang     dp = opendir("/sys/devices/system/memory/");
2423bd240fcaSzhanghailiang     if (!dp) {
2424f693fe6eSMichael Roth         /* it's ok if this happens to be a system that doesn't expose
2425f693fe6eSMichael Roth          * memory blocks via sysfs, but otherwise we should report
2426f693fe6eSMichael Roth          * an error
2427f693fe6eSMichael Roth          */
2428f693fe6eSMichael Roth         if (errno != ENOENT) {
2429bd240fcaSzhanghailiang             error_setg_errno(errp, errno, "Can't open directory"
24309af9e0feSMarkus Armbruster                              "\"/sys/devices/system/memory/\"");
2431f693fe6eSMichael Roth         }
2432bd240fcaSzhanghailiang         return NULL;
2433bd240fcaSzhanghailiang     }
2434bd240fcaSzhanghailiang 
2435bd240fcaSzhanghailiang     /* Note: the phys_index of memory block may be discontinuous,
2436bd240fcaSzhanghailiang      * this is because a memblk is the unit of the Sparse Memory design, which
2437bd240fcaSzhanghailiang      * allows discontinuous memory ranges (ex. NUMA), so here we should
2438bd240fcaSzhanghailiang      * traverse the memory block directory.
2439bd240fcaSzhanghailiang      */
2440bd240fcaSzhanghailiang     while ((de = readdir(dp)) != NULL) {
2441bd240fcaSzhanghailiang         GuestMemoryBlock *mem_blk;
2442bd240fcaSzhanghailiang 
2443bd240fcaSzhanghailiang         if ((strncmp(de->d_name, "memory", 6) != 0) ||
2444bd240fcaSzhanghailiang             !(de->d_type & DT_DIR)) {
2445bd240fcaSzhanghailiang             continue;
2446bd240fcaSzhanghailiang         }
2447bd240fcaSzhanghailiang 
2448bd240fcaSzhanghailiang         mem_blk = g_malloc0(sizeof *mem_blk);
2449bd240fcaSzhanghailiang         /* The d_name is "memoryXXX",  phys_index is block id, same as XXX */
2450bd240fcaSzhanghailiang         mem_blk->phys_index = strtoul(&de->d_name[6], NULL, 10);
2451bd240fcaSzhanghailiang         mem_blk->has_can_offline = true; /* lolspeak ftw */
2452bd240fcaSzhanghailiang         transfer_memory_block(mem_blk, true, NULL, &local_err);
24534155c998SMarkus Armbruster         if (local_err) {
24544155c998SMarkus Armbruster             break;
24554155c998SMarkus Armbruster         }
2456bd240fcaSzhanghailiang 
2457c3033fd3SEric Blake         QAPI_LIST_APPEND(tail, mem_blk);
2458bd240fcaSzhanghailiang     }
2459bd240fcaSzhanghailiang 
2460bd240fcaSzhanghailiang     closedir(dp);
2461bd240fcaSzhanghailiang     if (local_err == NULL) {
2462bd240fcaSzhanghailiang         /* there's no guest with zero memory blocks */
2463bd240fcaSzhanghailiang         if (head == NULL) {
2464bd240fcaSzhanghailiang             error_setg(errp, "guest reported zero memory blocks!");
2465bd240fcaSzhanghailiang         }
2466bd240fcaSzhanghailiang         return head;
2467bd240fcaSzhanghailiang     }
2468bd240fcaSzhanghailiang 
2469bd240fcaSzhanghailiang     qapi_free_GuestMemoryBlockList(head);
2470bd240fcaSzhanghailiang     error_propagate(errp, local_err);
2471a065aaa9Szhanghailiang     return NULL;
2472a065aaa9Szhanghailiang }
2473a065aaa9Szhanghailiang 
2474a065aaa9Szhanghailiang GuestMemoryBlockResponseList *
2475a065aaa9Szhanghailiang qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
2476a065aaa9Szhanghailiang {
2477c3033fd3SEric Blake     GuestMemoryBlockResponseList *head, **tail;
247832ca7927Szhanghailiang     Error *local_err = NULL;
247932ca7927Szhanghailiang 
248032ca7927Szhanghailiang     head = NULL;
2481c3033fd3SEric Blake     tail = &head;
248232ca7927Szhanghailiang 
248332ca7927Szhanghailiang     while (mem_blks != NULL) {
248432ca7927Szhanghailiang         GuestMemoryBlockResponse *result;
248532ca7927Szhanghailiang         GuestMemoryBlock *current_mem_blk = mem_blks->value;
248632ca7927Szhanghailiang 
248732ca7927Szhanghailiang         result = g_malloc0(sizeof(*result));
248832ca7927Szhanghailiang         result->phys_index = current_mem_blk->phys_index;
248932ca7927Szhanghailiang         transfer_memory_block(current_mem_blk, false, result, &local_err);
249032ca7927Szhanghailiang         if (local_err) { /* should never happen */
249132ca7927Szhanghailiang             goto err;
249232ca7927Szhanghailiang         }
249332ca7927Szhanghailiang 
2494c3033fd3SEric Blake         QAPI_LIST_APPEND(tail, result);
249532ca7927Szhanghailiang         mem_blks = mem_blks->next;
249632ca7927Szhanghailiang     }
249732ca7927Szhanghailiang 
249832ca7927Szhanghailiang     return head;
249932ca7927Szhanghailiang err:
250032ca7927Szhanghailiang     qapi_free_GuestMemoryBlockResponseList(head);
250132ca7927Szhanghailiang     error_propagate(errp, local_err);
2502a065aaa9Szhanghailiang     return NULL;
2503a065aaa9Szhanghailiang }
2504a065aaa9Szhanghailiang 
2505a065aaa9Szhanghailiang GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
2506a065aaa9Szhanghailiang {
2507ef82b60bSzhanghailiang     Error *local_err = NULL;
2508ef82b60bSzhanghailiang     char *dirpath;
2509ef82b60bSzhanghailiang     int dirfd;
2510ef82b60bSzhanghailiang     char *buf;
2511ef82b60bSzhanghailiang     GuestMemoryBlockInfo *info;
2512ef82b60bSzhanghailiang 
2513ef82b60bSzhanghailiang     dirpath = g_strdup_printf("/sys/devices/system/memory/");
2514ef82b60bSzhanghailiang     dirfd = open(dirpath, O_RDONLY | O_DIRECTORY);
2515ef82b60bSzhanghailiang     if (dirfd == -1) {
2516ef82b60bSzhanghailiang         error_setg_errno(errp, errno, "open(\"%s\")", dirpath);
2517ef82b60bSzhanghailiang         g_free(dirpath);
2518a065aaa9Szhanghailiang         return NULL;
2519a065aaa9Szhanghailiang     }
2520ef82b60bSzhanghailiang     g_free(dirpath);
2521ef82b60bSzhanghailiang 
2522ef82b60bSzhanghailiang     buf = g_malloc0(20);
2523ef82b60bSzhanghailiang     ga_read_sysfs_file(dirfd, "block_size_bytes", buf, 20, &local_err);
25248ce1ee46SShannon Zhao     close(dirfd);
2525ef82b60bSzhanghailiang     if (local_err) {
2526ef82b60bSzhanghailiang         g_free(buf);
2527ef82b60bSzhanghailiang         error_propagate(errp, local_err);
2528ef82b60bSzhanghailiang         return NULL;
2529ef82b60bSzhanghailiang     }
2530ef82b60bSzhanghailiang 
2531ef82b60bSzhanghailiang     info = g_new0(GuestMemoryBlockInfo, 1);
2532ef82b60bSzhanghailiang     info->size = strtol(buf, NULL, 16); /* the unit is bytes */
2533ef82b60bSzhanghailiang 
2534ef82b60bSzhanghailiang     g_free(buf);
2535ef82b60bSzhanghailiang 
2536ef82b60bSzhanghailiang     return info;
2537ef82b60bSzhanghailiang }
2538a065aaa9Szhanghailiang 
25393569664eSluzhipeng #define MAX_NAME_LEN 128
25403569664eSluzhipeng static GuestDiskStatsInfoList *guest_get_diskstats(Error **errp)
25413569664eSluzhipeng {
25423569664eSluzhipeng #ifdef CONFIG_LINUX
25433569664eSluzhipeng     GuestDiskStatsInfoList *head = NULL, **tail = &head;
25443569664eSluzhipeng     const char *diskstats = "/proc/diskstats";
25453569664eSluzhipeng     FILE *fp;
25463569664eSluzhipeng     size_t n;
25473569664eSluzhipeng     char *line = NULL;
25483569664eSluzhipeng 
25493569664eSluzhipeng     fp = fopen(diskstats, "r");
25503569664eSluzhipeng     if (fp  == NULL) {
25513569664eSluzhipeng         error_setg_errno(errp, errno, "open(\"%s\")", diskstats);
25523569664eSluzhipeng         return NULL;
25533569664eSluzhipeng     }
25543569664eSluzhipeng 
25553569664eSluzhipeng     while (getline(&line, &n, fp) != -1) {
25563569664eSluzhipeng         g_autofree GuestDiskStatsInfo *diskstatinfo = NULL;
25573569664eSluzhipeng         g_autofree GuestDiskStats *diskstat = NULL;
25583569664eSluzhipeng         char dev_name[MAX_NAME_LEN];
25593569664eSluzhipeng         unsigned int ios_pgr, tot_ticks, rq_ticks, wr_ticks, dc_ticks, fl_ticks;
25603569664eSluzhipeng         unsigned long rd_ios, rd_merges_or_rd_sec, rd_ticks_or_wr_sec, wr_ios;
25613569664eSluzhipeng         unsigned long wr_merges, rd_sec_or_wr_ios, wr_sec;
25623569664eSluzhipeng         unsigned long dc_ios, dc_merges, dc_sec, fl_ios;
25633569664eSluzhipeng         unsigned int major, minor;
25643569664eSluzhipeng         int i;
25653569664eSluzhipeng 
25663569664eSluzhipeng         i = sscanf(line, "%u %u %s %lu %lu %lu"
25673569664eSluzhipeng                    "%lu %lu %lu %lu %u %u %u %u"
25683569664eSluzhipeng                    "%lu %lu %lu %u %lu %u",
25693569664eSluzhipeng                    &major, &minor, dev_name,
25703569664eSluzhipeng                    &rd_ios, &rd_merges_or_rd_sec, &rd_sec_or_wr_ios,
25713569664eSluzhipeng                    &rd_ticks_or_wr_sec, &wr_ios, &wr_merges, &wr_sec,
25723569664eSluzhipeng                    &wr_ticks, &ios_pgr, &tot_ticks, &rq_ticks,
25733569664eSluzhipeng                    &dc_ios, &dc_merges, &dc_sec, &dc_ticks,
25743569664eSluzhipeng                    &fl_ios, &fl_ticks);
25753569664eSluzhipeng 
25763569664eSluzhipeng         if (i < 7) {
25773569664eSluzhipeng             continue;
25783569664eSluzhipeng         }
25793569664eSluzhipeng 
25803569664eSluzhipeng         diskstatinfo = g_new0(GuestDiskStatsInfo, 1);
25813569664eSluzhipeng         diskstatinfo->name = g_strdup(dev_name);
25823569664eSluzhipeng         diskstatinfo->major = major;
25833569664eSluzhipeng         diskstatinfo->minor = minor;
25843569664eSluzhipeng 
25853569664eSluzhipeng         diskstat = g_new0(GuestDiskStats, 1);
25863569664eSluzhipeng         if (i == 7) {
25873569664eSluzhipeng             diskstat->has_read_ios = true;
25883569664eSluzhipeng             diskstat->read_ios = rd_ios;
25893569664eSluzhipeng             diskstat->has_read_sectors = true;
25903569664eSluzhipeng             diskstat->read_sectors = rd_merges_or_rd_sec;
25913569664eSluzhipeng             diskstat->has_write_ios = true;
25923569664eSluzhipeng             diskstat->write_ios = rd_sec_or_wr_ios;
25933569664eSluzhipeng             diskstat->has_write_sectors = true;
25943569664eSluzhipeng             diskstat->write_sectors = rd_ticks_or_wr_sec;
25953569664eSluzhipeng         }
25963569664eSluzhipeng         if (i >= 14) {
25973569664eSluzhipeng             diskstat->has_read_ios = true;
25983569664eSluzhipeng             diskstat->read_ios = rd_ios;
25993569664eSluzhipeng             diskstat->has_read_sectors = true;
26003569664eSluzhipeng             diskstat->read_sectors = rd_sec_or_wr_ios;
26013569664eSluzhipeng             diskstat->has_read_merges = true;
26023569664eSluzhipeng             diskstat->read_merges = rd_merges_or_rd_sec;
26033569664eSluzhipeng             diskstat->has_read_ticks = true;
26043569664eSluzhipeng             diskstat->read_ticks = rd_ticks_or_wr_sec;
26053569664eSluzhipeng             diskstat->has_write_ios = true;
26063569664eSluzhipeng             diskstat->write_ios = wr_ios;
26073569664eSluzhipeng             diskstat->has_write_sectors = true;
26083569664eSluzhipeng             diskstat->write_sectors = wr_sec;
26093569664eSluzhipeng             diskstat->has_write_merges = true;
26103569664eSluzhipeng             diskstat->write_merges = wr_merges;
26113569664eSluzhipeng             diskstat->has_write_ticks = true;
26123569664eSluzhipeng             diskstat->write_ticks = wr_ticks;
26133569664eSluzhipeng             diskstat->has_ios_pgr = true;
26143569664eSluzhipeng             diskstat->ios_pgr = ios_pgr;
26153569664eSluzhipeng             diskstat->has_total_ticks = true;
26163569664eSluzhipeng             diskstat->total_ticks = tot_ticks;
26173569664eSluzhipeng             diskstat->has_weight_ticks = true;
26183569664eSluzhipeng             diskstat->weight_ticks = rq_ticks;
26193569664eSluzhipeng         }
26203569664eSluzhipeng         if (i >= 18) {
26213569664eSluzhipeng             diskstat->has_discard_ios = true;
26223569664eSluzhipeng             diskstat->discard_ios = dc_ios;
26233569664eSluzhipeng             diskstat->has_discard_merges = true;
26243569664eSluzhipeng             diskstat->discard_merges = dc_merges;
26253569664eSluzhipeng             diskstat->has_discard_sectors = true;
26263569664eSluzhipeng             diskstat->discard_sectors = dc_sec;
26273569664eSluzhipeng             diskstat->has_discard_ticks = true;
26283569664eSluzhipeng             diskstat->discard_ticks = dc_ticks;
26293569664eSluzhipeng         }
26303569664eSluzhipeng         if (i >= 20) {
26313569664eSluzhipeng             diskstat->has_flush_ios = true;
26323569664eSluzhipeng             diskstat->flush_ios = fl_ios;
26333569664eSluzhipeng             diskstat->has_flush_ticks = true;
26343569664eSluzhipeng             diskstat->flush_ticks = fl_ticks;
26353569664eSluzhipeng         }
26363569664eSluzhipeng 
26373569664eSluzhipeng         diskstatinfo->stats = g_steal_pointer(&diskstat);
26383569664eSluzhipeng         QAPI_LIST_APPEND(tail, diskstatinfo);
26393569664eSluzhipeng         diskstatinfo = NULL;
26403569664eSluzhipeng     }
26413569664eSluzhipeng     free(line);
26423569664eSluzhipeng     fclose(fp);
26433569664eSluzhipeng     return head;
26443569664eSluzhipeng #else
26453569664eSluzhipeng     g_debug("disk stats reporting available only for Linux");
26463569664eSluzhipeng     return NULL;
26473569664eSluzhipeng #endif
26483569664eSluzhipeng }
26493569664eSluzhipeng 
26503569664eSluzhipeng GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
26513569664eSluzhipeng {
26523569664eSluzhipeng     return guest_get_diskstats(errp);
26533569664eSluzhipeng }
26543569664eSluzhipeng 
26551db8a0b0Szhenwei pi GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
26561db8a0b0Szhenwei pi {
26571db8a0b0Szhenwei pi     GuestCpuStatsList *head = NULL, **tail = &head;
26581db8a0b0Szhenwei pi     const char *cpustats = "/proc/stat";
26591db8a0b0Szhenwei pi     int clk_tck = sysconf(_SC_CLK_TCK);
26601db8a0b0Szhenwei pi     FILE *fp;
26611db8a0b0Szhenwei pi     size_t n;
26621db8a0b0Szhenwei pi     char *line = NULL;
26631db8a0b0Szhenwei pi 
26641db8a0b0Szhenwei pi     fp = fopen(cpustats, "r");
26651db8a0b0Szhenwei pi     if (fp  == NULL) {
26661db8a0b0Szhenwei pi         error_setg_errno(errp, errno, "open(\"%s\")", cpustats);
26671db8a0b0Szhenwei pi         return NULL;
26681db8a0b0Szhenwei pi     }
26691db8a0b0Szhenwei pi 
26701db8a0b0Szhenwei pi     while (getline(&line, &n, fp) != -1) {
26711db8a0b0Szhenwei pi         GuestCpuStats *cpustat = NULL;
26721db8a0b0Szhenwei pi         GuestLinuxCpuStats *linuxcpustat;
26731db8a0b0Szhenwei pi         int i;
26741db8a0b0Szhenwei pi         unsigned long user, system, idle, iowait, irq, softirq, steal, guest;
26751db8a0b0Szhenwei pi         unsigned long nice, guest_nice;
26761db8a0b0Szhenwei pi         char name[64];
26771db8a0b0Szhenwei pi 
26781db8a0b0Szhenwei pi         i = sscanf(line, "%s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
26791db8a0b0Szhenwei pi                    name, &user, &nice, &system, &idle, &iowait, &irq, &softirq,
26801db8a0b0Szhenwei pi                    &steal, &guest, &guest_nice);
26811db8a0b0Szhenwei pi 
26821db8a0b0Szhenwei pi         /* drop "cpu 1 2 3 ...", get "cpuX 1 2 3 ..." only */
26831db8a0b0Szhenwei pi         if ((i == EOF) || strncmp(name, "cpu", 3) || (name[3] == '\0')) {
26841db8a0b0Szhenwei pi             continue;
26851db8a0b0Szhenwei pi         }
26861db8a0b0Szhenwei pi 
26871db8a0b0Szhenwei pi         if (i < 5) {
26881db8a0b0Szhenwei pi             slog("Parsing cpu stat from %s failed, see \"man proc\"", cpustats);
26891db8a0b0Szhenwei pi             break;
26901db8a0b0Szhenwei pi         }
26911db8a0b0Szhenwei pi 
26921db8a0b0Szhenwei pi         cpustat = g_new0(GuestCpuStats, 1);
26931db8a0b0Szhenwei pi         cpustat->type = GUEST_CPU_STATS_TYPE_LINUX;
26941db8a0b0Szhenwei pi 
26951db8a0b0Szhenwei pi         linuxcpustat = &cpustat->u.q_linux;
26961db8a0b0Szhenwei pi         linuxcpustat->cpu = atoi(&name[3]);
26971db8a0b0Szhenwei pi         linuxcpustat->user = user * 1000 / clk_tck;
26981db8a0b0Szhenwei pi         linuxcpustat->nice = nice * 1000 / clk_tck;
26991db8a0b0Szhenwei pi         linuxcpustat->system = system * 1000 / clk_tck;
27001db8a0b0Szhenwei pi         linuxcpustat->idle = idle * 1000 / clk_tck;
27011db8a0b0Szhenwei pi 
27021db8a0b0Szhenwei pi         if (i > 5) {
27031db8a0b0Szhenwei pi             linuxcpustat->has_iowait = true;
27041db8a0b0Szhenwei pi             linuxcpustat->iowait = iowait * 1000 / clk_tck;
27051db8a0b0Szhenwei pi         }
27061db8a0b0Szhenwei pi 
27071db8a0b0Szhenwei pi         if (i > 6) {
27081db8a0b0Szhenwei pi             linuxcpustat->has_irq = true;
27091db8a0b0Szhenwei pi             linuxcpustat->irq = irq * 1000 / clk_tck;
27101db8a0b0Szhenwei pi             linuxcpustat->has_softirq = true;
27111db8a0b0Szhenwei pi             linuxcpustat->softirq = softirq * 1000 / clk_tck;
27121db8a0b0Szhenwei pi         }
27131db8a0b0Szhenwei pi 
27141db8a0b0Szhenwei pi         if (i > 8) {
27151db8a0b0Szhenwei pi             linuxcpustat->has_steal = true;
27161db8a0b0Szhenwei pi             linuxcpustat->steal = steal * 1000 / clk_tck;
27171db8a0b0Szhenwei pi         }
27181db8a0b0Szhenwei pi 
27191db8a0b0Szhenwei pi         if (i > 9) {
27201db8a0b0Szhenwei pi             linuxcpustat->has_guest = true;
27211db8a0b0Szhenwei pi             linuxcpustat->guest = guest * 1000 / clk_tck;
27221db8a0b0Szhenwei pi         }
27231db8a0b0Szhenwei pi 
27241db8a0b0Szhenwei pi         if (i > 10) {
27251db8a0b0Szhenwei pi             linuxcpustat->has_guest = true;
27261db8a0b0Szhenwei pi             linuxcpustat->guest = guest * 1000 / clk_tck;
27271db8a0b0Szhenwei pi             linuxcpustat->has_guestnice = true;
27281db8a0b0Szhenwei pi             linuxcpustat->guestnice = guest_nice * 1000 / clk_tck;
27291db8a0b0Szhenwei pi         }
27301db8a0b0Szhenwei pi 
27311db8a0b0Szhenwei pi         QAPI_LIST_APPEND(tail, cpustat);
27321db8a0b0Szhenwei pi     }
27331db8a0b0Szhenwei pi 
27341db8a0b0Szhenwei pi     free(line);
27351db8a0b0Szhenwei pi     fclose(fp);
27361db8a0b0Szhenwei pi     return head;
27371db8a0b0Szhenwei pi }
27381db8a0b0Szhenwei pi 
2739e72c3f2eSMichael Roth #else /* defined(__linux__) */
2740e72c3f2eSMichael Roth 
274177dbc81bSMarkus Armbruster void qmp_guest_suspend_disk(Error **errp)
2742e72c3f2eSMichael Roth {
2743c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2744e72c3f2eSMichael Roth }
2745e72c3f2eSMichael Roth 
274677dbc81bSMarkus Armbruster void qmp_guest_suspend_ram(Error **errp)
2747e72c3f2eSMichael Roth {
2748c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2749e72c3f2eSMichael Roth }
2750e72c3f2eSMichael Roth 
275177dbc81bSMarkus Armbruster void qmp_guest_suspend_hybrid(Error **errp)
2752e72c3f2eSMichael Roth {
2753c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2754e72c3f2eSMichael Roth }
2755e72c3f2eSMichael Roth 
2756d2baff62SLaszlo Ersek GuestLogicalProcessorList *qmp_guest_get_vcpus(Error **errp)
2757d2baff62SLaszlo Ersek {
2758c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2759d2baff62SLaszlo Ersek     return NULL;
2760d2baff62SLaszlo Ersek }
2761d2baff62SLaszlo Ersek 
2762cbb65fc2SLaszlo Ersek int64_t qmp_guest_set_vcpus(GuestLogicalProcessorList *vcpus, Error **errp)
2763cbb65fc2SLaszlo Ersek {
2764c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2765cbb65fc2SLaszlo Ersek     return -1;
2766cbb65fc2SLaszlo Ersek }
2767cbb65fc2SLaszlo Ersek 
2768215a2771SDaniel P. Berrange void qmp_guest_set_user_password(const char *username,
2769215a2771SDaniel P. Berrange                                  const char *password,
2770215a2771SDaniel P. Berrange                                  bool crypted,
2771215a2771SDaniel P. Berrange                                  Error **errp)
2772215a2771SDaniel P. Berrange {
2773c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2774215a2771SDaniel P. Berrange }
2775215a2771SDaniel P. Berrange 
2776a065aaa9Szhanghailiang GuestMemoryBlockList *qmp_guest_get_memory_blocks(Error **errp)
2777a065aaa9Szhanghailiang {
2778c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2779a065aaa9Szhanghailiang     return NULL;
2780a065aaa9Szhanghailiang }
2781a065aaa9Szhanghailiang 
2782a065aaa9Szhanghailiang GuestMemoryBlockResponseList *
2783a065aaa9Szhanghailiang qmp_guest_set_memory_blocks(GuestMemoryBlockList *mem_blks, Error **errp)
2784a065aaa9Szhanghailiang {
2785c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2786a065aaa9Szhanghailiang     return NULL;
2787a065aaa9Szhanghailiang }
2788a065aaa9Szhanghailiang 
2789a065aaa9Szhanghailiang GuestMemoryBlockInfo *qmp_guest_get_memory_block_info(Error **errp)
2790a065aaa9Szhanghailiang {
2791c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
2792a065aaa9Szhanghailiang     return NULL;
2793a065aaa9Szhanghailiang }
2794a065aaa9Szhanghailiang 
2795e72c3f2eSMichael Roth #endif
2796e72c3f2eSMichael Roth 
279759e35c7bSAndrew Deason #ifdef HAVE_GETIFADDRS
279859e35c7bSAndrew Deason static GuestNetworkInterface *
279959e35c7bSAndrew Deason guest_find_interface(GuestNetworkInterfaceList *head,
280059e35c7bSAndrew Deason                      const char *name)
280159e35c7bSAndrew Deason {
280259e35c7bSAndrew Deason     for (; head; head = head->next) {
280359e35c7bSAndrew Deason         if (strcmp(head->value->name, name) == 0) {
280459e35c7bSAndrew Deason             return head->value;
280559e35c7bSAndrew Deason         }
280659e35c7bSAndrew Deason     }
280759e35c7bSAndrew Deason 
280859e35c7bSAndrew Deason     return NULL;
280959e35c7bSAndrew Deason }
281059e35c7bSAndrew Deason 
281159e35c7bSAndrew Deason static int guest_get_network_stats(const char *name,
281259e35c7bSAndrew Deason                        GuestNetworkInterfaceStat *stats)
281359e35c7bSAndrew Deason {
281470335c46SAndrew Deason #ifdef CONFIG_LINUX
281559e35c7bSAndrew Deason     int name_len;
281659e35c7bSAndrew Deason     char const *devinfo = "/proc/net/dev";
281759e35c7bSAndrew Deason     FILE *fp;
281859e35c7bSAndrew Deason     char *line = NULL, *colon;
281959e35c7bSAndrew Deason     size_t n = 0;
282059e35c7bSAndrew Deason     fp = fopen(devinfo, "r");
282159e35c7bSAndrew Deason     if (!fp) {
2822a539dc8aSAndrew Deason         g_debug("failed to open network stats %s: %s", devinfo,
2823a539dc8aSAndrew Deason                 g_strerror(errno));
282459e35c7bSAndrew Deason         return -1;
282559e35c7bSAndrew Deason     }
282659e35c7bSAndrew Deason     name_len = strlen(name);
282759e35c7bSAndrew Deason     while (getline(&line, &n, fp) != -1) {
282859e35c7bSAndrew Deason         long long dummy;
282959e35c7bSAndrew Deason         long long rx_bytes;
283059e35c7bSAndrew Deason         long long rx_packets;
283159e35c7bSAndrew Deason         long long rx_errs;
283259e35c7bSAndrew Deason         long long rx_dropped;
283359e35c7bSAndrew Deason         long long tx_bytes;
283459e35c7bSAndrew Deason         long long tx_packets;
283559e35c7bSAndrew Deason         long long tx_errs;
283659e35c7bSAndrew Deason         long long tx_dropped;
283759e35c7bSAndrew Deason         char *trim_line;
283859e35c7bSAndrew Deason         trim_line = g_strchug(line);
283959e35c7bSAndrew Deason         if (trim_line[0] == '\0') {
284059e35c7bSAndrew Deason             continue;
284159e35c7bSAndrew Deason         }
284259e35c7bSAndrew Deason         colon = strchr(trim_line, ':');
284359e35c7bSAndrew Deason         if (!colon) {
284459e35c7bSAndrew Deason             continue;
284559e35c7bSAndrew Deason         }
284659e35c7bSAndrew Deason         if (colon - name_len  == trim_line &&
284759e35c7bSAndrew Deason            strncmp(trim_line, name, name_len) == 0) {
284859e35c7bSAndrew Deason             if (sscanf(colon + 1,
284959e35c7bSAndrew Deason                 "%lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld %lld",
285059e35c7bSAndrew Deason                   &rx_bytes, &rx_packets, &rx_errs, &rx_dropped,
285159e35c7bSAndrew Deason                   &dummy, &dummy, &dummy, &dummy,
285259e35c7bSAndrew Deason                   &tx_bytes, &tx_packets, &tx_errs, &tx_dropped,
285359e35c7bSAndrew Deason                   &dummy, &dummy, &dummy, &dummy) != 16) {
285459e35c7bSAndrew Deason                 continue;
285559e35c7bSAndrew Deason             }
285659e35c7bSAndrew Deason             stats->rx_bytes = rx_bytes;
285759e35c7bSAndrew Deason             stats->rx_packets = rx_packets;
285859e35c7bSAndrew Deason             stats->rx_errs = rx_errs;
285959e35c7bSAndrew Deason             stats->rx_dropped = rx_dropped;
286059e35c7bSAndrew Deason             stats->tx_bytes = tx_bytes;
286159e35c7bSAndrew Deason             stats->tx_packets = tx_packets;
286259e35c7bSAndrew Deason             stats->tx_errs = tx_errs;
286359e35c7bSAndrew Deason             stats->tx_dropped = tx_dropped;
286459e35c7bSAndrew Deason             fclose(fp);
286559e35c7bSAndrew Deason             g_free(line);
286659e35c7bSAndrew Deason             return 0;
286759e35c7bSAndrew Deason         }
286859e35c7bSAndrew Deason     }
286959e35c7bSAndrew Deason     fclose(fp);
287059e35c7bSAndrew Deason     g_free(line);
287159e35c7bSAndrew Deason     g_debug("/proc/net/dev: Interface '%s' not found", name);
2872a539dc8aSAndrew Deason #else /* !CONFIG_LINUX */
2873a539dc8aSAndrew Deason     g_debug("Network stats reporting available only for Linux");
2874a539dc8aSAndrew Deason #endif /* !CONFIG_LINUX */
287559e35c7bSAndrew Deason     return -1;
287659e35c7bSAndrew Deason }
287759e35c7bSAndrew Deason 
287859e35c7bSAndrew Deason /*
287959e35c7bSAndrew Deason  * Build information about guest interfaces
288059e35c7bSAndrew Deason  */
288159e35c7bSAndrew Deason GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
288259e35c7bSAndrew Deason {
288359e35c7bSAndrew Deason     GuestNetworkInterfaceList *head = NULL, **tail = &head;
288459e35c7bSAndrew Deason     struct ifaddrs *ifap, *ifa;
288559e35c7bSAndrew Deason 
288659e35c7bSAndrew Deason     if (getifaddrs(&ifap) < 0) {
288759e35c7bSAndrew Deason         error_setg_errno(errp, errno, "getifaddrs failed");
288859e35c7bSAndrew Deason         goto error;
288959e35c7bSAndrew Deason     }
289059e35c7bSAndrew Deason 
289159e35c7bSAndrew Deason     for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
289259e35c7bSAndrew Deason         GuestNetworkInterface *info;
289359e35c7bSAndrew Deason         GuestIpAddressList **address_tail;
289459e35c7bSAndrew Deason         GuestIpAddress *address_item = NULL;
289559e35c7bSAndrew Deason         GuestNetworkInterfaceStat *interface_stat = NULL;
289659e35c7bSAndrew Deason         char addr4[INET_ADDRSTRLEN];
289759e35c7bSAndrew Deason         char addr6[INET6_ADDRSTRLEN];
289859e35c7bSAndrew Deason         int sock;
289959e35c7bSAndrew Deason         struct ifreq ifr;
290059e35c7bSAndrew Deason         unsigned char *mac_addr;
290159e35c7bSAndrew Deason         void *p;
290259e35c7bSAndrew Deason 
290359e35c7bSAndrew Deason         g_debug("Processing %s interface", ifa->ifa_name);
290459e35c7bSAndrew Deason 
290559e35c7bSAndrew Deason         info = guest_find_interface(head, ifa->ifa_name);
290659e35c7bSAndrew Deason 
290759e35c7bSAndrew Deason         if (!info) {
290859e35c7bSAndrew Deason             info = g_malloc0(sizeof(*info));
290959e35c7bSAndrew Deason             info->name = g_strdup(ifa->ifa_name);
291059e35c7bSAndrew Deason 
291159e35c7bSAndrew Deason             QAPI_LIST_APPEND(tail, info);
291259e35c7bSAndrew Deason         }
291359e35c7bSAndrew Deason 
2914aec0730eSAndrew Deason         if (!info->has_hardware_address) {
291559e35c7bSAndrew Deason             /* we haven't obtained HW address yet */
291659e35c7bSAndrew Deason             sock = socket(PF_INET, SOCK_STREAM, 0);
291759e35c7bSAndrew Deason             if (sock == -1) {
291859e35c7bSAndrew Deason                 error_setg_errno(errp, errno, "failed to create socket");
291959e35c7bSAndrew Deason                 goto error;
292059e35c7bSAndrew Deason             }
292159e35c7bSAndrew Deason 
292259e35c7bSAndrew Deason             memset(&ifr, 0, sizeof(ifr));
292359e35c7bSAndrew Deason             pstrcpy(ifr.ifr_name, IF_NAMESIZE, info->name);
292459e35c7bSAndrew Deason             if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) {
2925aec0730eSAndrew Deason                 /*
2926aec0730eSAndrew Deason                  * We can't get the hw addr of this interface, but that's not a
2927aec0730eSAndrew Deason                  * fatal error. Don't set info->hardware_address, but keep
2928aec0730eSAndrew Deason                  * going.
2929aec0730eSAndrew Deason                  */
2930aec0730eSAndrew Deason                 if (errno == EADDRNOTAVAIL) {
2931aec0730eSAndrew Deason                     /* The interface doesn't have a hw addr (e.g. loopback). */
2932aec0730eSAndrew Deason                     g_debug("failed to get MAC address of %s: %s",
2933aec0730eSAndrew Deason                             ifa->ifa_name, strerror(errno));
2934aec0730eSAndrew Deason                 } else{
2935aec0730eSAndrew Deason                     g_warning("failed to get MAC address of %s: %s",
2936aec0730eSAndrew Deason                               ifa->ifa_name, strerror(errno));
293759e35c7bSAndrew Deason                 }
293859e35c7bSAndrew Deason 
2939aec0730eSAndrew Deason             } else {
294070335c46SAndrew Deason #ifdef CONFIG_SOLARIS
294170335c46SAndrew Deason                 mac_addr = (unsigned char *) &ifr.ifr_addr.sa_data;
294270335c46SAndrew Deason #else
294359e35c7bSAndrew Deason                 mac_addr = (unsigned char *) &ifr.ifr_hwaddr.sa_data;
294470335c46SAndrew Deason #endif
294559e35c7bSAndrew Deason                 info->hardware_address =
294659e35c7bSAndrew Deason                     g_strdup_printf("%02x:%02x:%02x:%02x:%02x:%02x",
294759e35c7bSAndrew Deason                                     (int) mac_addr[0], (int) mac_addr[1],
294859e35c7bSAndrew Deason                                     (int) mac_addr[2], (int) mac_addr[3],
294959e35c7bSAndrew Deason                                     (int) mac_addr[4], (int) mac_addr[5]);
295059e35c7bSAndrew Deason 
295159e35c7bSAndrew Deason                 info->has_hardware_address = true;
295259e35c7bSAndrew Deason             }
2953aec0730eSAndrew Deason             close(sock);
2954aec0730eSAndrew Deason         }
295559e35c7bSAndrew Deason 
295659e35c7bSAndrew Deason         if (ifa->ifa_addr &&
295759e35c7bSAndrew Deason             ifa->ifa_addr->sa_family == AF_INET) {
295859e35c7bSAndrew Deason             /* interface with IPv4 address */
295959e35c7bSAndrew Deason             p = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
296059e35c7bSAndrew Deason             if (!inet_ntop(AF_INET, p, addr4, sizeof(addr4))) {
296159e35c7bSAndrew Deason                 error_setg_errno(errp, errno, "inet_ntop failed");
296259e35c7bSAndrew Deason                 goto error;
296359e35c7bSAndrew Deason             }
296459e35c7bSAndrew Deason 
296559e35c7bSAndrew Deason             address_item = g_malloc0(sizeof(*address_item));
296659e35c7bSAndrew Deason             address_item->ip_address = g_strdup(addr4);
296759e35c7bSAndrew Deason             address_item->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV4;
296859e35c7bSAndrew Deason 
296959e35c7bSAndrew Deason             if (ifa->ifa_netmask) {
297059e35c7bSAndrew Deason                 /* Count the number of set bits in netmask.
297159e35c7bSAndrew Deason                  * This is safe as '1' and '0' cannot be shuffled in netmask. */
297259e35c7bSAndrew Deason                 p = &((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr;
297359e35c7bSAndrew Deason                 address_item->prefix = ctpop32(((uint32_t *) p)[0]);
297459e35c7bSAndrew Deason             }
297559e35c7bSAndrew Deason         } else if (ifa->ifa_addr &&
297659e35c7bSAndrew Deason                    ifa->ifa_addr->sa_family == AF_INET6) {
297759e35c7bSAndrew Deason             /* interface with IPv6 address */
297859e35c7bSAndrew Deason             p = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
297959e35c7bSAndrew Deason             if (!inet_ntop(AF_INET6, p, addr6, sizeof(addr6))) {
298059e35c7bSAndrew Deason                 error_setg_errno(errp, errno, "inet_ntop failed");
298159e35c7bSAndrew Deason                 goto error;
298259e35c7bSAndrew Deason             }
298359e35c7bSAndrew Deason 
298459e35c7bSAndrew Deason             address_item = g_malloc0(sizeof(*address_item));
298559e35c7bSAndrew Deason             address_item->ip_address = g_strdup(addr6);
298659e35c7bSAndrew Deason             address_item->ip_address_type = GUEST_IP_ADDRESS_TYPE_IPV6;
298759e35c7bSAndrew Deason 
298859e35c7bSAndrew Deason             if (ifa->ifa_netmask) {
298959e35c7bSAndrew Deason                 /* Count the number of set bits in netmask.
299059e35c7bSAndrew Deason                  * This is safe as '1' and '0' cannot be shuffled in netmask. */
299159e35c7bSAndrew Deason                 p = &((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr;
299259e35c7bSAndrew Deason                 address_item->prefix =
299359e35c7bSAndrew Deason                     ctpop32(((uint32_t *) p)[0]) +
299459e35c7bSAndrew Deason                     ctpop32(((uint32_t *) p)[1]) +
299559e35c7bSAndrew Deason                     ctpop32(((uint32_t *) p)[2]) +
299659e35c7bSAndrew Deason                     ctpop32(((uint32_t *) p)[3]);
299759e35c7bSAndrew Deason             }
299859e35c7bSAndrew Deason         }
299959e35c7bSAndrew Deason 
300059e35c7bSAndrew Deason         if (!address_item) {
300159e35c7bSAndrew Deason             continue;
300259e35c7bSAndrew Deason         }
300359e35c7bSAndrew Deason 
300459e35c7bSAndrew Deason         address_tail = &info->ip_addresses;
300559e35c7bSAndrew Deason         while (*address_tail) {
300659e35c7bSAndrew Deason             address_tail = &(*address_tail)->next;
300759e35c7bSAndrew Deason         }
300859e35c7bSAndrew Deason         QAPI_LIST_APPEND(address_tail, address_item);
300959e35c7bSAndrew Deason 
301059e35c7bSAndrew Deason         info->has_ip_addresses = true;
301159e35c7bSAndrew Deason 
301259e35c7bSAndrew Deason         if (!info->has_statistics) {
301359e35c7bSAndrew Deason             interface_stat = g_malloc0(sizeof(*interface_stat));
301459e35c7bSAndrew Deason             if (guest_get_network_stats(info->name, interface_stat) == -1) {
301559e35c7bSAndrew Deason                 info->has_statistics = false;
301659e35c7bSAndrew Deason                 g_free(interface_stat);
301759e35c7bSAndrew Deason             } else {
301859e35c7bSAndrew Deason                 info->statistics = interface_stat;
301959e35c7bSAndrew Deason                 info->has_statistics = true;
302059e35c7bSAndrew Deason             }
302159e35c7bSAndrew Deason         }
302259e35c7bSAndrew Deason     }
302359e35c7bSAndrew Deason 
302459e35c7bSAndrew Deason     freeifaddrs(ifap);
302559e35c7bSAndrew Deason     return head;
302659e35c7bSAndrew Deason 
302759e35c7bSAndrew Deason error:
302859e35c7bSAndrew Deason     freeifaddrs(ifap);
302959e35c7bSAndrew Deason     qapi_free_GuestNetworkInterfaceList(head);
303059e35c7bSAndrew Deason     return NULL;
303159e35c7bSAndrew Deason }
303259e35c7bSAndrew Deason 
303359e35c7bSAndrew Deason #else
303459e35c7bSAndrew Deason 
303559e35c7bSAndrew Deason GuestNetworkInterfaceList *qmp_guest_network_get_interfaces(Error **errp)
303659e35c7bSAndrew Deason {
303759e35c7bSAndrew Deason     error_setg(errp, QERR_UNSUPPORTED);
303859e35c7bSAndrew Deason     return NULL;
303959e35c7bSAndrew Deason }
304059e35c7bSAndrew Deason 
304159e35c7bSAndrew Deason #endif /* HAVE_GETIFADDRS */
304259e35c7bSAndrew Deason 
3043d35d4cb5SMichael Roth #if !defined(CONFIG_FSFREEZE)
3044d35d4cb5SMichael Roth 
304546d4c572STomoki Sekiyama GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp)
304646d4c572STomoki Sekiyama {
3047c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
304846d4c572STomoki Sekiyama     return NULL;
304946d4c572STomoki Sekiyama }
305046d4c572STomoki Sekiyama 
305177dbc81bSMarkus Armbruster GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **errp)
3052d35d4cb5SMichael Roth {
3053c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
3054d35d4cb5SMichael Roth 
3055d35d4cb5SMichael Roth     return 0;
3056d35d4cb5SMichael Roth }
3057d35d4cb5SMichael Roth 
305877dbc81bSMarkus Armbruster int64_t qmp_guest_fsfreeze_freeze(Error **errp)
3059d35d4cb5SMichael Roth {
3060c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
3061d35d4cb5SMichael Roth 
3062d35d4cb5SMichael Roth     return 0;
3063d35d4cb5SMichael Roth }
3064d35d4cb5SMichael Roth 
3065e99bce20STomoki Sekiyama int64_t qmp_guest_fsfreeze_freeze_list(bool has_mountpoints,
3066e99bce20STomoki Sekiyama                                        strList *mountpoints,
3067e99bce20STomoki Sekiyama                                        Error **errp)
3068e99bce20STomoki Sekiyama {
3069c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
3070e99bce20STomoki Sekiyama 
3071e99bce20STomoki Sekiyama     return 0;
3072e99bce20STomoki Sekiyama }
3073e99bce20STomoki Sekiyama 
307477dbc81bSMarkus Armbruster int64_t qmp_guest_fsfreeze_thaw(Error **errp)
3075d35d4cb5SMichael Roth {
3076c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
3077d35d4cb5SMichael Roth 
3078d35d4cb5SMichael Roth     return 0;
3079d35d4cb5SMichael Roth }
3080fed39564STomáš Golembiovský 
3081fed39564STomáš Golembiovský GuestDiskInfoList *qmp_guest_get_disks(Error **errp)
3082fed39564STomáš Golembiovský {
3083fed39564STomáš Golembiovský     error_setg(errp, QERR_UNSUPPORTED);
3084fed39564STomáš Golembiovský     return NULL;
3085fed39564STomáš Golembiovský }
3086fed39564STomáš Golembiovský 
30873569664eSluzhipeng GuestDiskStatsInfoList *qmp_guest_get_diskstats(Error **errp)
30883569664eSluzhipeng {
30893569664eSluzhipeng     error_setg(errp, QERR_UNSUPPORTED);
30903569664eSluzhipeng     return NULL;
30913569664eSluzhipeng }
30923569664eSluzhipeng 
30931db8a0b0Szhenwei pi GuestCpuStatsList *qmp_guest_get_cpustats(Error **errp)
30941db8a0b0Szhenwei pi {
30951db8a0b0Szhenwei pi     error_setg(errp, QERR_UNSUPPORTED);
30961db8a0b0Szhenwei pi     return NULL;
30971db8a0b0Szhenwei pi }
30983569664eSluzhipeng 
3099eab5fd59SPaolo Bonzini #endif /* CONFIG_FSFREEZE */
3100d35d4cb5SMichael Roth 
3101eab5fd59SPaolo Bonzini #if !defined(CONFIG_FSTRIM)
3102e82855d9SJustin Ossevoort GuestFilesystemTrimResponse *
3103e82855d9SJustin Ossevoort qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **errp)
3104eab5fd59SPaolo Bonzini {
3105c6bd8c70SMarkus Armbruster     error_setg(errp, QERR_UNSUPPORTED);
3106e82855d9SJustin Ossevoort     return NULL;
3107eab5fd59SPaolo Bonzini }
3108d35d4cb5SMichael Roth #endif
3109d35d4cb5SMichael Roth 
31100e4ef702SThomas Huth /* add unsupported commands to the list of blocked RPCs */
31110e4ef702SThomas Huth GList *ga_command_init_blockedrpcs(GList *blockedrpcs)
31121281c08aSTomoki Sekiyama {
31131281c08aSTomoki Sekiyama #if !defined(__linux__)
31141281c08aSTomoki Sekiyama     {
31151281c08aSTomoki Sekiyama         const char *list[] = {
31161281c08aSTomoki Sekiyama             "guest-suspend-disk", "guest-suspend-ram",
311759e35c7bSAndrew Deason             "guest-suspend-hybrid", "guest-get-vcpus", "guest-set-vcpus",
31180dd38a03Szhanghailiang             "guest-get-memory-blocks", "guest-set-memory-blocks",
311928d8dd35SBasil Salman             "guest-get-memory-block-size", "guest-get-memory-block-info",
312028d8dd35SBasil Salman             NULL};
31211281c08aSTomoki Sekiyama         char **p = (char **)list;
31221281c08aSTomoki Sekiyama 
31231281c08aSTomoki Sekiyama         while (*p) {
31240e4ef702SThomas Huth             blockedrpcs = g_list_append(blockedrpcs, g_strdup(*p++));
31251281c08aSTomoki Sekiyama         }
31261281c08aSTomoki Sekiyama     }
31271281c08aSTomoki Sekiyama #endif
31281281c08aSTomoki Sekiyama 
312959e35c7bSAndrew Deason #if !defined(HAVE_GETIFADDRS)
31300e4ef702SThomas Huth     blockedrpcs = g_list_append(blockedrpcs,
313159e35c7bSAndrew Deason                               g_strdup("guest-network-get-interfaces"));
313259e35c7bSAndrew Deason #endif
313359e35c7bSAndrew Deason 
31341281c08aSTomoki Sekiyama #if !defined(CONFIG_FSFREEZE)
31351281c08aSTomoki Sekiyama     {
31361281c08aSTomoki Sekiyama         const char *list[] = {
31371281c08aSTomoki Sekiyama             "guest-get-fsinfo", "guest-fsfreeze-status",
31381281c08aSTomoki Sekiyama             "guest-fsfreeze-freeze", "guest-fsfreeze-freeze-list",
3139fed39564STomáš Golembiovský             "guest-fsfreeze-thaw", "guest-get-fsinfo",
3140fed39564STomáš Golembiovský             "guest-get-disks", NULL};
31411281c08aSTomoki Sekiyama         char **p = (char **)list;
31421281c08aSTomoki Sekiyama 
31431281c08aSTomoki Sekiyama         while (*p) {
31440e4ef702SThomas Huth             blockedrpcs = g_list_append(blockedrpcs, g_strdup(*p++));
31451281c08aSTomoki Sekiyama         }
31461281c08aSTomoki Sekiyama     }
31471281c08aSTomoki Sekiyama #endif
31481281c08aSTomoki Sekiyama 
31491281c08aSTomoki Sekiyama #if !defined(CONFIG_FSTRIM)
31500e4ef702SThomas Huth     blockedrpcs = g_list_append(blockedrpcs, g_strdup("guest-fstrim"));
31511281c08aSTomoki Sekiyama #endif
31521281c08aSTomoki Sekiyama 
31530e4ef702SThomas Huth     blockedrpcs = g_list_append(blockedrpcs, g_strdup("guest-get-devices"));
31542e4211ceSTomáš Golembiovský 
31550e4ef702SThomas Huth     return blockedrpcs;
31561281c08aSTomoki Sekiyama }
31571281c08aSTomoki Sekiyama 
3158c216e5adSMichael Roth /* register init/cleanup routines for stateful command groups */
3159c216e5adSMichael Roth void ga_command_state_init(GAState *s, GACommandState *cs)
3160c216e5adSMichael Roth {
3161c216e5adSMichael Roth #if defined(CONFIG_FSFREEZE)
3162f22d85e9SMichael Roth     ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup);
3163c216e5adSMichael Roth #endif
3164c216e5adSMichael Roth }
3165161a56a9SVinzenz Feenstra 
3166e674605fSTomáš Golembiovský #ifdef HAVE_UTMPX
3167e674605fSTomáš Golembiovský 
3168161a56a9SVinzenz Feenstra #define QGA_MICRO_SECOND_TO_SECOND 1000000
3169161a56a9SVinzenz Feenstra 
3170161a56a9SVinzenz Feenstra static double ga_get_login_time(struct utmpx *user_info)
3171161a56a9SVinzenz Feenstra {
3172161a56a9SVinzenz Feenstra     double seconds = (double)user_info->ut_tv.tv_sec;
3173161a56a9SVinzenz Feenstra     double useconds = (double)user_info->ut_tv.tv_usec;
3174161a56a9SVinzenz Feenstra     useconds /= QGA_MICRO_SECOND_TO_SECOND;
3175161a56a9SVinzenz Feenstra     return seconds + useconds;
3176161a56a9SVinzenz Feenstra }
3177161a56a9SVinzenz Feenstra 
3178b90abbacSVladimir Sementsov-Ogievskiy GuestUserList *qmp_guest_get_users(Error **errp)
3179161a56a9SVinzenz Feenstra {
3180161a56a9SVinzenz Feenstra     GHashTable *cache = NULL;
318195b3a8c8SEric Blake     GuestUserList *head = NULL, **tail = &head;
3182161a56a9SVinzenz Feenstra     struct utmpx *user_info = NULL;
3183161a56a9SVinzenz Feenstra     gpointer value = NULL;
3184161a56a9SVinzenz Feenstra     GuestUser *user = NULL;
3185161a56a9SVinzenz Feenstra     double login_time = 0;
3186161a56a9SVinzenz Feenstra 
3187161a56a9SVinzenz Feenstra     cache = g_hash_table_new(g_str_hash, g_str_equal);
3188161a56a9SVinzenz Feenstra     setutxent();
3189161a56a9SVinzenz Feenstra 
3190161a56a9SVinzenz Feenstra     for (;;) {
3191161a56a9SVinzenz Feenstra         user_info = getutxent();
3192161a56a9SVinzenz Feenstra         if (user_info == NULL) {
3193161a56a9SVinzenz Feenstra             break;
3194161a56a9SVinzenz Feenstra         } else if (user_info->ut_type != USER_PROCESS) {
3195161a56a9SVinzenz Feenstra             continue;
3196161a56a9SVinzenz Feenstra         } else if (g_hash_table_contains(cache, user_info->ut_user)) {
3197161a56a9SVinzenz Feenstra             value = g_hash_table_lookup(cache, user_info->ut_user);
3198161a56a9SVinzenz Feenstra             user = (GuestUser *)value;
3199161a56a9SVinzenz Feenstra             login_time = ga_get_login_time(user_info);
3200161a56a9SVinzenz Feenstra             /* We're ensuring the earliest login time to be sent */
3201161a56a9SVinzenz Feenstra             if (login_time < user->login_time) {
3202161a56a9SVinzenz Feenstra                 user->login_time = login_time;
3203161a56a9SVinzenz Feenstra             }
3204161a56a9SVinzenz Feenstra             continue;
3205161a56a9SVinzenz Feenstra         }
3206161a56a9SVinzenz Feenstra 
320795b3a8c8SEric Blake         user = g_new0(GuestUser, 1);
320895b3a8c8SEric Blake         user->user = g_strdup(user_info->ut_user);
320995b3a8c8SEric Blake         user->login_time = ga_get_login_time(user_info);
3210161a56a9SVinzenz Feenstra 
321195b3a8c8SEric Blake         g_hash_table_insert(cache, user->user, user);
3212161a56a9SVinzenz Feenstra 
321395b3a8c8SEric Blake         QAPI_LIST_APPEND(tail, user);
3214161a56a9SVinzenz Feenstra     }
3215161a56a9SVinzenz Feenstra     endutxent();
3216161a56a9SVinzenz Feenstra     g_hash_table_destroy(cache);
3217161a56a9SVinzenz Feenstra     return head;
3218161a56a9SVinzenz Feenstra }
3219e674605fSTomáš Golembiovský 
3220e674605fSTomáš Golembiovský #else
3221e674605fSTomáš Golembiovský 
3222e674605fSTomáš Golembiovský GuestUserList *qmp_guest_get_users(Error **errp)
3223e674605fSTomáš Golembiovský {
3224e674605fSTomáš Golembiovský     error_setg(errp, QERR_UNSUPPORTED);
3225e674605fSTomáš Golembiovský     return NULL;
3226e674605fSTomáš Golembiovský }
3227e674605fSTomáš Golembiovský 
3228e674605fSTomáš Golembiovský #endif
32299848f797STomáš Golembiovský 
32309848f797STomáš Golembiovský /* Replace escaped special characters with theire real values. The replacement
32319848f797STomáš Golembiovský  * is done in place -- returned value is in the original string.
32329848f797STomáš Golembiovský  */
32339848f797STomáš Golembiovský static void ga_osrelease_replace_special(gchar *value)
32349848f797STomáš Golembiovský {
32359848f797STomáš Golembiovský     gchar *p, *p2, quote;
32369848f797STomáš Golembiovský 
32379848f797STomáš Golembiovský     /* Trim the string at first space or semicolon if it is not enclosed in
32389848f797STomáš Golembiovský      * single or double quotes. */
32399848f797STomáš Golembiovský     if ((value[0] != '"') || (value[0] == '\'')) {
32409848f797STomáš Golembiovský         p = strchr(value, ' ');
32419848f797STomáš Golembiovský         if (p != NULL) {
32429848f797STomáš Golembiovský             *p = 0;
32439848f797STomáš Golembiovský         }
32449848f797STomáš Golembiovský         p = strchr(value, ';');
32459848f797STomáš Golembiovský         if (p != NULL) {
32469848f797STomáš Golembiovský             *p = 0;
32479848f797STomáš Golembiovský         }
32489848f797STomáš Golembiovský         return;
32499848f797STomáš Golembiovský     }
32509848f797STomáš Golembiovský 
32519848f797STomáš Golembiovský     quote = value[0];
32529848f797STomáš Golembiovský     p2 = value;
32539848f797STomáš Golembiovský     p = value + 1;
32549848f797STomáš Golembiovský     while (*p != 0) {
32559848f797STomáš Golembiovský         if (*p == '\\') {
32569848f797STomáš Golembiovský             p++;
32579848f797STomáš Golembiovský             switch (*p) {
32589848f797STomáš Golembiovský             case '$':
32599848f797STomáš Golembiovský             case '\'':
32609848f797STomáš Golembiovský             case '"':
32619848f797STomáš Golembiovský             case '\\':
32629848f797STomáš Golembiovský             case '`':
32639848f797STomáš Golembiovský                 break;
32649848f797STomáš Golembiovský             default:
32659848f797STomáš Golembiovský                 /* Keep literal backslash followed by whatever is there */
32669848f797STomáš Golembiovský                 p--;
32679848f797STomáš Golembiovský                 break;
32689848f797STomáš Golembiovský             }
32699848f797STomáš Golembiovský         } else if (*p == quote) {
32709848f797STomáš Golembiovský             *p2 = 0;
32719848f797STomáš Golembiovský             break;
32729848f797STomáš Golembiovský         }
32739848f797STomáš Golembiovský         *(p2++) = *(p++);
32749848f797STomáš Golembiovský     }
32759848f797STomáš Golembiovský }
32769848f797STomáš Golembiovský 
32779848f797STomáš Golembiovský static GKeyFile *ga_parse_osrelease(const char *fname)
32789848f797STomáš Golembiovský {
32799848f797STomáš Golembiovský     gchar *content = NULL;
32809848f797STomáš Golembiovský     gchar *content2 = NULL;
32819848f797STomáš Golembiovský     GError *err = NULL;
32829848f797STomáš Golembiovský     GKeyFile *keys = g_key_file_new();
32839848f797STomáš Golembiovský     const char *group = "[os-release]\n";
32849848f797STomáš Golembiovský 
32859848f797STomáš Golembiovský     if (!g_file_get_contents(fname, &content, NULL, &err)) {
32869848f797STomáš Golembiovský         slog("failed to read '%s', error: %s", fname, err->message);
32879848f797STomáš Golembiovský         goto fail;
32889848f797STomáš Golembiovský     }
32899848f797STomáš Golembiovský 
32909848f797STomáš Golembiovský     if (!g_utf8_validate(content, -1, NULL)) {
32919848f797STomáš Golembiovský         slog("file is not utf-8 encoded: %s", fname);
32929848f797STomáš Golembiovský         goto fail;
32939848f797STomáš Golembiovský     }
32949848f797STomáš Golembiovský     content2 = g_strdup_printf("%s%s", group, content);
32959848f797STomáš Golembiovský 
32969848f797STomáš Golembiovský     if (!g_key_file_load_from_data(keys, content2, -1, G_KEY_FILE_NONE,
32979848f797STomáš Golembiovský                                    &err)) {
32989848f797STomáš Golembiovský         slog("failed to parse file '%s', error: %s", fname, err->message);
32999848f797STomáš Golembiovský         goto fail;
33009848f797STomáš Golembiovský     }
33019848f797STomáš Golembiovský 
33029848f797STomáš Golembiovský     g_free(content);
33039848f797STomáš Golembiovský     g_free(content2);
33049848f797STomáš Golembiovský     return keys;
33059848f797STomáš Golembiovský 
33069848f797STomáš Golembiovský fail:
33079848f797STomáš Golembiovský     g_error_free(err);
33089848f797STomáš Golembiovský     g_free(content);
33099848f797STomáš Golembiovský     g_free(content2);
33109848f797STomáš Golembiovský     g_key_file_free(keys);
33119848f797STomáš Golembiovský     return NULL;
33129848f797STomáš Golembiovský }
33139848f797STomáš Golembiovský 
33149848f797STomáš Golembiovský GuestOSInfo *qmp_guest_get_osinfo(Error **errp)
33159848f797STomáš Golembiovský {
33169848f797STomáš Golembiovský     GuestOSInfo *info = NULL;
33179848f797STomáš Golembiovský     struct utsname kinfo;
3318339ca68bSTomáš Golembiovský     GKeyFile *osrelease = NULL;
3319339ca68bSTomáš Golembiovský     const char *qga_os_release = g_getenv("QGA_OS_RELEASE");
33209848f797STomáš Golembiovský 
33219848f797STomáš Golembiovský     info = g_new0(GuestOSInfo, 1);
33229848f797STomáš Golembiovský 
33239848f797STomáš Golembiovský     if (uname(&kinfo) != 0) {
33249848f797STomáš Golembiovský         error_setg_errno(errp, errno, "uname failed");
33259848f797STomáš Golembiovský     } else {
33269848f797STomáš Golembiovský         info->has_kernel_version = true;
33279848f797STomáš Golembiovský         info->kernel_version = g_strdup(kinfo.version);
33289848f797STomáš Golembiovský         info->has_kernel_release = true;
33299848f797STomáš Golembiovský         info->kernel_release = g_strdup(kinfo.release);
33309848f797STomáš Golembiovský         info->has_machine = true;
33319848f797STomáš Golembiovský         info->machine = g_strdup(kinfo.machine);
33329848f797STomáš Golembiovský     }
33339848f797STomáš Golembiovský 
3334339ca68bSTomáš Golembiovský     if (qga_os_release != NULL) {
3335339ca68bSTomáš Golembiovský         osrelease = ga_parse_osrelease(qga_os_release);
3336339ca68bSTomáš Golembiovský     } else {
33379848f797STomáš Golembiovský         osrelease = ga_parse_osrelease("/etc/os-release");
33389848f797STomáš Golembiovský         if (osrelease == NULL) {
33399848f797STomáš Golembiovský             osrelease = ga_parse_osrelease("/usr/lib/os-release");
33409848f797STomáš Golembiovský         }
3341339ca68bSTomáš Golembiovský     }
33429848f797STomáš Golembiovský 
33439848f797STomáš Golembiovský     if (osrelease != NULL) {
33449848f797STomáš Golembiovský         char *value;
33459848f797STomáš Golembiovský 
33469848f797STomáš Golembiovský #define GET_FIELD(field, osfield) do { \
33479848f797STomáš Golembiovský     value = g_key_file_get_value(osrelease, "os-release", osfield, NULL); \
33489848f797STomáš Golembiovský     if (value != NULL) { \
33499848f797STomáš Golembiovský         ga_osrelease_replace_special(value); \
33509848f797STomáš Golembiovský         info->has_ ## field = true; \
33519848f797STomáš Golembiovský         info->field = value; \
33529848f797STomáš Golembiovský     } \
33539848f797STomáš Golembiovský } while (0)
33549848f797STomáš Golembiovský         GET_FIELD(id, "ID");
33559848f797STomáš Golembiovský         GET_FIELD(name, "NAME");
33569848f797STomáš Golembiovský         GET_FIELD(pretty_name, "PRETTY_NAME");
33579848f797STomáš Golembiovský         GET_FIELD(version, "VERSION");
33589848f797STomáš Golembiovský         GET_FIELD(version_id, "VERSION_ID");
33599848f797STomáš Golembiovský         GET_FIELD(variant, "VARIANT");
33609848f797STomáš Golembiovský         GET_FIELD(variant_id, "VARIANT_ID");
33619848f797STomáš Golembiovský #undef GET_FIELD
33629848f797STomáš Golembiovský 
33639848f797STomáš Golembiovský         g_key_file_free(osrelease);
33649848f797STomáš Golembiovský     }
33659848f797STomáš Golembiovský 
33669848f797STomáš Golembiovský     return info;
33679848f797STomáš Golembiovský }
33682e4211ceSTomáš Golembiovský 
33692e4211ceSTomáš Golembiovský GuestDeviceInfoList *qmp_guest_get_devices(Error **errp)
33702e4211ceSTomáš Golembiovský {
33712e4211ceSTomáš Golembiovský     error_setg(errp, QERR_UNSUPPORTED);
33722e4211ceSTomáš Golembiovský 
33732e4211ceSTomáš Golembiovský     return NULL;
33742e4211ceSTomáš Golembiovský }
3375548fb0daSMarc-André Lureau 
3376548fb0daSMarc-André Lureau #ifndef HOST_NAME_MAX
3377548fb0daSMarc-André Lureau # ifdef _POSIX_HOST_NAME_MAX
3378548fb0daSMarc-André Lureau #  define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
3379548fb0daSMarc-André Lureau # else
3380548fb0daSMarc-André Lureau #  define HOST_NAME_MAX 255
3381548fb0daSMarc-André Lureau # endif
3382548fb0daSMarc-André Lureau #endif
3383548fb0daSMarc-André Lureau 
3384548fb0daSMarc-André Lureau char *qga_get_host_name(Error **errp)
3385548fb0daSMarc-André Lureau {
3386548fb0daSMarc-André Lureau     long len = -1;
3387548fb0daSMarc-André Lureau     g_autofree char *hostname = NULL;
3388548fb0daSMarc-André Lureau 
3389548fb0daSMarc-André Lureau #ifdef _SC_HOST_NAME_MAX
3390548fb0daSMarc-André Lureau     len = sysconf(_SC_HOST_NAME_MAX);
3391548fb0daSMarc-André Lureau #endif /* _SC_HOST_NAME_MAX */
3392548fb0daSMarc-André Lureau 
3393548fb0daSMarc-André Lureau     if (len < 0) {
3394548fb0daSMarc-André Lureau         len = HOST_NAME_MAX;
3395548fb0daSMarc-André Lureau     }
3396548fb0daSMarc-André Lureau 
3397548fb0daSMarc-André Lureau     /* Unfortunately, gethostname() below does not guarantee a
3398548fb0daSMarc-André Lureau      * NULL terminated string. Therefore, allocate one byte more
3399548fb0daSMarc-André Lureau      * to be sure. */
3400548fb0daSMarc-André Lureau     hostname = g_new0(char, len + 1);
3401548fb0daSMarc-André Lureau 
3402548fb0daSMarc-André Lureau     if (gethostname(hostname, len) < 0) {
3403548fb0daSMarc-André Lureau         error_setg_errno(errp, errno,
3404548fb0daSMarc-André Lureau                          "cannot get hostname");
3405548fb0daSMarc-André Lureau         return NULL;
3406548fb0daSMarc-André Lureau     }
3407548fb0daSMarc-André Lureau 
3408548fb0daSMarc-André Lureau     return g_steal_pointer(&hostname);
3409548fb0daSMarc-André Lureau }
3410