]>
Commit | Line | Data |
---|---|---|
518b0d80 AI |
1 | /* |
2 | * QEMU Guest Agent Linux-specific command implementations | |
3 | * | |
4 | * Copyright IBM Corp. 2011 | |
5 | * | |
6 | * Authors: | |
7 | * Michael Roth <mdroth@linux.vnet.ibm.com> | |
8 | * Michal Privoznik <mprivozn@redhat.com> | |
9 | * | |
10 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
11 | * See the COPYING file in the top-level directory. | |
12 | */ | |
13 | ||
14 | #include "qemu/osdep.h" | |
15 | #include "qapi/error.h" | |
16 | #include "commands-common.h" | |
17 | #include "cutils.h" | |
18 | #include <mntent.h> | |
19 | #include <sys/ioctl.h> | |
20 | ||
21 | #if defined(CONFIG_FSFREEZE) || defined(CONFIG_FSTRIM) | |
22 | static int dev_major_minor(const char *devpath, | |
23 | unsigned int *devmajor, unsigned int *devminor) | |
24 | { | |
25 | struct stat st; | |
26 | ||
27 | *devmajor = 0; | |
28 | *devminor = 0; | |
29 | ||
30 | if (stat(devpath, &st) < 0) { | |
31 | slog("failed to stat device file '%s': %s", devpath, strerror(errno)); | |
32 | return -1; | |
33 | } | |
34 | if (S_ISDIR(st.st_mode)) { | |
35 | /* It is bind mount */ | |
36 | return -2; | |
37 | } | |
38 | if (S_ISBLK(st.st_mode)) { | |
39 | *devmajor = major(st.st_rdev); | |
40 | *devminor = minor(st.st_rdev); | |
41 | return 0; | |
42 | } | |
43 | return -1; | |
44 | } | |
45 | ||
46 | static bool build_fs_mount_list_from_mtab(FsMountList *mounts, Error **errp) | |
47 | { | |
48 | struct mntent *ment; | |
49 | FsMount *mount; | |
50 | char const *mtab = "/proc/self/mounts"; | |
51 | FILE *fp; | |
52 | unsigned int devmajor, devminor; | |
53 | ||
54 | fp = setmntent(mtab, "r"); | |
55 | if (!fp) { | |
56 | error_setg(errp, "failed to open mtab file: '%s'", mtab); | |
57 | return false; | |
58 | } | |
59 | ||
60 | while ((ment = getmntent(fp))) { | |
61 | /* | |
62 | * An entry which device name doesn't start with a '/' is | |
63 | * either a dummy file system or a network file system. | |
64 | * Add special handling for smbfs and cifs as is done by | |
65 | * coreutils as well. | |
66 | */ | |
67 | if ((ment->mnt_fsname[0] != '/') || | |
68 | (strcmp(ment->mnt_type, "smbfs") == 0) || | |
69 | (strcmp(ment->mnt_type, "cifs") == 0)) { | |
70 | continue; | |
71 | } | |
72 | if (dev_major_minor(ment->mnt_fsname, &devmajor, &devminor) == -2) { | |
73 | /* Skip bind mounts */ | |
74 | continue; | |
75 | } | |
76 | ||
77 | mount = g_new0(FsMount, 1); | |
78 | mount->dirname = g_strdup(ment->mnt_dir); | |
79 | mount->devtype = g_strdup(ment->mnt_type); | |
80 | mount->devmajor = devmajor; | |
81 | mount->devminor = devminor; | |
82 | ||
83 | QTAILQ_INSERT_TAIL(mounts, mount, next); | |
84 | } | |
85 | ||
86 | endmntent(fp); | |
87 | return true; | |
88 | } | |
89 | ||
90 | static void decode_mntname(char *name, int len) | |
91 | { | |
92 | int i, j = 0; | |
93 | for (i = 0; i <= len; i++) { | |
94 | if (name[i] != '\\') { | |
95 | name[j++] = name[i]; | |
96 | } else if (name[i + 1] == '\\') { | |
97 | name[j++] = '\\'; | |
98 | i++; | |
99 | } else if (name[i + 1] >= '0' && name[i + 1] <= '3' && | |
100 | name[i + 2] >= '0' && name[i + 2] <= '7' && | |
101 | name[i + 3] >= '0' && name[i + 3] <= '7') { | |
102 | name[j++] = (name[i + 1] - '0') * 64 + | |
103 | (name[i + 2] - '0') * 8 + | |
104 | (name[i + 3] - '0'); | |
105 | i += 3; | |
106 | } else { | |
107 | name[j++] = name[i]; | |
108 | } | |
109 | } | |
110 | } | |
111 | ||
112 | /* | |
113 | * Walk the mount table and build a list of local file systems | |
114 | */ | |
115 | bool build_fs_mount_list(FsMountList *mounts, Error **errp) | |
116 | { | |
117 | FsMount *mount; | |
118 | char const *mountinfo = "/proc/self/mountinfo"; | |
119 | FILE *fp; | |
120 | char *line = NULL, *dash; | |
121 | size_t n; | |
122 | char check; | |
123 | unsigned int devmajor, devminor; | |
124 | int ret, dir_s, dir_e, type_s, type_e, dev_s, dev_e; | |
125 | ||
126 | fp = fopen(mountinfo, "r"); | |
127 | if (!fp) { | |
128 | return build_fs_mount_list_from_mtab(mounts, errp); | |
129 | } | |
130 | ||
131 | while (getline(&line, &n, fp) != -1) { | |
132 | ret = sscanf(line, "%*u %*u %u:%u %*s %n%*s%n%c", | |
133 | &devmajor, &devminor, &dir_s, &dir_e, &check); | |
134 | if (ret < 3) { | |
135 | continue; | |
136 | } | |
137 | dash = strstr(line + dir_e, " - "); | |
138 | if (!dash) { | |
139 | continue; | |
140 | } | |
141 | ret = sscanf(dash, " - %n%*s%n %n%*s%n%c", | |
142 | &type_s, &type_e, &dev_s, &dev_e, &check); | |
143 | if (ret < 1) { | |
144 | continue; | |
145 | } | |
146 | line[dir_e] = 0; | |
147 | dash[type_e] = 0; | |
148 | dash[dev_e] = 0; | |
149 | decode_mntname(line + dir_s, dir_e - dir_s); | |
150 | decode_mntname(dash + dev_s, dev_e - dev_s); | |
151 | if (devmajor == 0) { | |
152 | /* btrfs reports major number = 0 */ | |
153 | if (strcmp("btrfs", dash + type_s) != 0 || | |
154 | dev_major_minor(dash + dev_s, &devmajor, &devminor) < 0) { | |
155 | continue; | |
156 | } | |
157 | } | |
158 | ||
159 | mount = g_new0(FsMount, 1); | |
160 | mount->dirname = g_strdup(line + dir_s); | |
161 | mount->devtype = g_strdup(dash + type_s); | |
162 | mount->devmajor = devmajor; | |
163 | mount->devminor = devminor; | |
164 | ||
165 | QTAILQ_INSERT_TAIL(mounts, mount, next); | |
166 | } | |
167 | free(line); | |
168 | ||
169 | fclose(fp); | |
170 | return true; | |
171 | } | |
172 | #endif /* CONFIG_FSFREEZE || CONFIG_FSTRIM */ | |
173 | ||
174 | #ifdef CONFIG_FSFREEZE | |
175 | /* | |
176 | * Walk list of mounted file systems in the guest, and freeze the ones which | |
177 | * are real local file systems. | |
178 | */ | |
179 | int64_t qmp_guest_fsfreeze_do_freeze_list(bool has_mountpoints, | |
180 | strList *mountpoints, | |
181 | FsMountList mounts, | |
182 | Error **errp) | |
183 | { | |
184 | struct FsMount *mount; | |
185 | strList *list; | |
186 | int fd, ret, i = 0; | |
187 | ||
188 | QTAILQ_FOREACH_REVERSE(mount, &mounts, next) { | |
189 | /* To issue fsfreeze in the reverse order of mounts, check if the | |
190 | * mount is listed in the list here */ | |
191 | if (has_mountpoints) { | |
192 | for (list = mountpoints; list; list = list->next) { | |
193 | if (strcmp(list->value, mount->dirname) == 0) { | |
194 | break; | |
195 | } | |
196 | } | |
197 | if (!list) { | |
198 | continue; | |
199 | } | |
200 | } | |
201 | ||
202 | fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); | |
203 | if (fd == -1) { | |
204 | error_setg_errno(errp, errno, "failed to open %s", mount->dirname); | |
205 | return -1; | |
206 | } | |
207 | ||
208 | /* we try to cull filesystems we know won't work in advance, but other | |
209 | * filesystems may not implement fsfreeze for less obvious reasons. | |
210 | * these will report EOPNOTSUPP. we simply ignore these when tallying | |
211 | * the number of frozen filesystems. | |
212 | * if a filesystem is mounted more than once (aka bind mount) a | |
213 | * consecutive attempt to freeze an already frozen filesystem will | |
214 | * return EBUSY. | |
215 | * | |
216 | * any other error means a failure to freeze a filesystem we | |
217 | * expect to be freezable, so return an error in those cases | |
218 | * and return system to thawed state. | |
219 | */ | |
220 | ret = ioctl(fd, FIFREEZE); | |
221 | if (ret == -1) { | |
222 | if (errno != EOPNOTSUPP && errno != EBUSY) { | |
223 | error_setg_errno(errp, errno, "failed to freeze %s", | |
224 | mount->dirname); | |
225 | close(fd); | |
226 | return -1; | |
227 | } | |
228 | } else { | |
229 | i++; | |
230 | } | |
231 | close(fd); | |
232 | } | |
233 | return i; | |
234 | } | |
235 | ||
236 | int qmp_guest_fsfreeze_do_thaw(Error **errp) | |
237 | { | |
238 | int ret; | |
239 | FsMountList mounts; | |
240 | FsMount *mount; | |
241 | int fd, i = 0, logged; | |
242 | Error *local_err = NULL; | |
243 | ||
244 | QTAILQ_INIT(&mounts); | |
245 | if (!build_fs_mount_list(&mounts, &local_err)) { | |
246 | error_propagate(errp, local_err); | |
247 | return -1; | |
248 | } | |
249 | ||
250 | QTAILQ_FOREACH(mount, &mounts, next) { | |
251 | logged = false; | |
252 | fd = qga_open_cloexec(mount->dirname, O_RDONLY, 0); | |
253 | if (fd == -1) { | |
254 | continue; | |
255 | } | |
256 | /* we have no way of knowing whether a filesystem was actually unfrozen | |
257 | * as a result of a successful call to FITHAW, only that if an error | |
258 | * was returned the filesystem was *not* unfrozen by that particular | |
259 | * call. | |
260 | * | |
261 | * since multiple preceding FIFREEZEs require multiple calls to FITHAW | |
262 | * to unfreeze, continuing issuing FITHAW until an error is returned, | |
263 | * in which case either the filesystem is in an unfreezable state, or, | |
264 | * more likely, it was thawed previously (and remains so afterward). | |
265 | * | |
266 | * also, since the most recent successful call is the one that did | |
267 | * the actual unfreeze, we can use this to provide an accurate count | |
268 | * of the number of filesystems unfrozen by guest-fsfreeze-thaw, which | |
269 | * may * be useful for determining whether a filesystem was unfrozen | |
270 | * during the freeze/thaw phase by a process other than qemu-ga. | |
271 | */ | |
272 | do { | |
273 | ret = ioctl(fd, FITHAW); | |
274 | if (ret == 0 && !logged) { | |
275 | i++; | |
276 | logged = true; | |
277 | } | |
278 | } while (ret == 0); | |
279 | close(fd); | |
280 | } | |
281 | ||
282 | free_fs_mount_list(&mounts); | |
283 | ||
284 | return i; | |
285 | } | |
286 | #endif /* CONFIG_FSFREEZE */ |