1 /*
2  * storage_backend_zfs.c: storage backend for ZFS handling
3  *
4  * Copyright (C) 2014 Roman Bogorodskiy
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library.  If not, see
18  * <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include <config.h>
23 
24 #include "viralloc.h"
25 #include "virerror.h"
26 #include "virfile.h"
27 #include "storage_backend_zfs.h"
28 #include "virlog.h"
29 #include "virstring.h"
30 #include "storage_util.h"
31 
32 #define VIR_FROM_THIS VIR_FROM_STORAGE
33 
34 VIR_LOG_INIT("storage.storage_backend_zfs");
35 
36 #define ZFS "zfs"
37 #define ZPOOL "zpool"
38 
39 /*
40  * Some common flags of zfs and zpool commands we use:
41  * -H -- don't print headers and separate fields by tab
42  * -p -- show exact numbers instead of human-readable, i.e.
43  *       for size, show just a number instead of 2G etc
44  */
45 
46 /**
47  * virStorageBackendZFSVolModeNeeded:
48  *
49  * Checks if it's necessary to specify 'volmode' (i.e. that
50  * we're working with BSD ZFS implementation).
51  *
52  * Returns 1 if 'volmode' is need, 0 if not needed, -1 on error
53  */
54 static int
virStorageBackendZFSVolModeNeeded(void)55 virStorageBackendZFSVolModeNeeded(void)
56 {
57     int ret = -1, exit_code = -1;
58     g_autofree char *error = NULL;
59     g_autoptr(virCommand) cmd = NULL;
60 
61     /* 'zfs get' without arguments prints out
62      * usage information to stderr, including
63      * list of supported options, and exits with
64      * exit code 2
65      */
66     cmd = virCommandNewArgList(ZFS, "get", NULL);
67     virCommandAddEnvString(cmd, "LC_ALL=C");
68     virCommandSetErrorBuffer(cmd, &error);
69 
70     ret = virCommandRun(cmd, &exit_code);
71     if ((ret < 0) || (exit_code != 2)) {
72         VIR_WARN("Command 'zfs get' either failed "
73                  "to run or exited with unexpected status");
74         return ret;
75     }
76 
77     if (strstr(error, " volmode "))
78         return 1;
79     else
80         return 0;
81 }
82 
83 static int
virStorageBackendZFSCheckPool(virStoragePoolObj * pool G_GNUC_UNUSED,bool * isActive)84 virStorageBackendZFSCheckPool(virStoragePoolObj *pool G_GNUC_UNUSED,
85                               bool *isActive)
86 {
87     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
88     g_autofree char *devpath = NULL;
89 
90     devpath = g_strdup_printf("/dev/zvol/%s", def->source.name);
91     *isActive = virFileIsDir(devpath);
92 
93     return 0;
94 }
95 
96 static int
virStorageBackendZFSParseVol(virStoragePoolObj * pool,virStorageVolDef * vol,const char * volume_string)97 virStorageBackendZFSParseVol(virStoragePoolObj *pool,
98                              virStorageVolDef *vol,
99                              const char *volume_string)
100 {
101     int ret = -1;
102     char *vol_name;
103     bool is_new_vol = false;
104     virStorageVolDef *volume = NULL;
105     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
106     g_auto(GStrv) tokens = NULL;
107     char *tmp;
108 
109     if (!(tokens = g_strsplit(volume_string, "\t", 0)))
110         return -1;
111 
112     if (g_strv_length(tokens) != 3)
113         goto cleanup;
114 
115     vol_name = tokens[0];
116     if ((tmp = strrchr(vol_name, '/')))
117         vol_name = tmp + 1;
118 
119     if (vol == NULL)
120         volume = virStorageVolDefFindByName(pool, vol_name);
121     else
122         volume = vol;
123 
124     if (volume == NULL) {
125         volume = g_new0(virStorageVolDef, 1);
126 
127         is_new_vol = true;
128         volume->type = VIR_STORAGE_VOL_BLOCK;
129 
130         volume->name = g_strdup(vol_name);
131     }
132 
133     if (!volume->key)
134         volume->key = g_strdup(tokens[0]);
135 
136     if (volume->target.path == NULL) {
137         volume->target.path = g_strdup_printf("%s/%s", def->target.path,
138                                               volume->name);
139     }
140 
141     if (virStrToLong_ull(tokens[1], NULL, 10, &volume->target.capacity) < 0) {
142         virReportError(VIR_ERR_INTERNAL_ERROR,
143                        "%s", _("malformed volsize reported"));
144         goto cleanup;
145     }
146 
147     if (virStrToLong_ull(tokens[2], NULL, 10, &volume->target.allocation) < 0) {
148         virReportError(VIR_ERR_INTERNAL_ERROR,
149                        "%s", _("malformed refreservation reported"));
150         goto cleanup;
151     }
152 
153     if (volume->target.allocation < volume->target.capacity)
154         volume->target.sparse = true;
155 
156     if (is_new_vol && virStoragePoolObjAddVol(pool, volume) < 0)
157         goto cleanup;
158     volume = NULL;
159 
160     ret = 0;
161  cleanup:
162     if (is_new_vol)
163         virStorageVolDefFree(volume);
164     return ret;
165 }
166 
167 static int
virStorageBackendZFSFindVols(virStoragePoolObj * pool,virStorageVolDef * vol)168 virStorageBackendZFSFindVols(virStoragePoolObj *pool,
169                              virStorageVolDef *vol)
170 {
171     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
172     size_t i;
173     g_auto(GStrv) lines = NULL;
174     g_autoptr(virCommand) cmd = NULL;
175     g_autofree char *volumes_list = NULL;
176 
177     /**
178      * $ zfs list -Hp -t volume -o name,volsize -r test
179      * test/vol1       5368709120
180      * test/vol3       1073741824
181      * test/vol4       1572864000
182      * $
183      *
184      * Arguments description:
185      *  -t volume -- we want to see only volumes
186      *  -o name,volsize -- limit output to name and volume size
187      *  -r -- we want to see all the childer of our pool
188      */
189     cmd = virCommandNewArgList(ZFS,
190                                "list", "-Hp",
191                                "-t", "volume", "-r",
192                                "-o", "name,volsize,refreservation",
193                                def->source.name,
194                                NULL);
195     virCommandSetOutputBuffer(cmd, &volumes_list);
196     if (virCommandRun(cmd, NULL) < 0)
197         return -1;
198 
199     if (!(lines = g_strsplit(volumes_list, "\n", 0)))
200         return -1;
201 
202     for (i = 0; lines[i]; i++) {
203         if (STREQ(lines[i], ""))
204             continue;
205 
206         if (virStorageBackendZFSParseVol(pool, vol, lines[i]) < 0)
207             continue;
208     }
209 
210     return 0;
211 }
212 
213 static int
virStorageBackendZFSRefreshPool(virStoragePoolObj * pool G_GNUC_UNUSED)214 virStorageBackendZFSRefreshPool(virStoragePoolObj *pool G_GNUC_UNUSED)
215 {
216     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
217     char *zpool_props = NULL;
218     size_t i;
219     g_autoptr(virCommand) cmd = NULL;
220     g_auto(GStrv) lines = NULL;
221     g_autofree char *name = g_strdup(def->source.name);
222     char *tmp;
223 
224     /**
225      * $ zpool get -Hp health,size,free,allocated test
226      * test    health  ONLINE  -
227      * test    size    199715979264    -
228      * test    free    198899976704    -
229      * test    allocated       816002560       -
230      * $
231      *
232      * Here we just provide a list of properties we want to see
233      */
234     if ((tmp = strchr(name, '/')))
235         *tmp = '\0';
236 
237     cmd = virCommandNewArgList(ZPOOL,
238                                "get", "-Hp",
239                                "health,size,free,allocated",
240                                name,
241                                NULL);
242     virCommandSetOutputBuffer(cmd, &zpool_props);
243     if (virCommandRun(cmd, NULL) < 0)
244         goto cleanup;
245 
246     if (!(lines = g_strsplit(zpool_props, "\n", 0)))
247         goto cleanup;
248 
249     for (i = 0; lines[i]; i++) {
250         g_auto(GStrv) tokens = NULL;
251         char *prop_name;
252 
253         if (STREQ(lines[i], ""))
254             continue;
255 
256         if (!(tokens = g_strsplit(lines[i], "\t", 0)))
257             goto cleanup;
258 
259         if (g_strv_length(tokens) != 4)
260             continue;
261 
262         prop_name = tokens[1];
263 
264         if (STREQ(prop_name, "free") || STREQ(prop_name, "size") ||
265             STREQ(prop_name, "allocated")) {
266             unsigned long long value;
267             if (virStrToLong_ull(tokens[2], NULL, 10, &value) < 0)
268                 goto cleanup;
269 
270             if (STREQ(prop_name, "free"))
271                 def->available = value;
272             else if (STREQ(prop_name, "size"))
273                 def->capacity = value;
274             else if (STREQ(prop_name, "allocated"))
275                 def->allocation = value;
276         }
277     }
278 
279     /* Obtain a list of volumes */
280     if (virStorageBackendZFSFindVols(pool, NULL) < 0)
281         goto cleanup;
282 
283  cleanup:
284     VIR_FREE(zpool_props);
285 
286     return 0;
287 }
288 
289 static int
virStorageBackendZFSCreateVol(virStoragePoolObj * pool,virStorageVolDef * vol)290 virStorageBackendZFSCreateVol(virStoragePoolObj *pool,
291                               virStorageVolDef *vol)
292 {
293     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
294     int volmode_needed = -1;
295     g_autoptr(virCommand) cmd = NULL;
296 
297     if (vol->target.encryption != NULL) {
298         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
299                        "%s", _("storage pool does not support encrypted "
300                                "volumes"));
301         return -1;
302     }
303 
304     vol->type = VIR_STORAGE_VOL_BLOCK;
305 
306     VIR_FREE(vol->target.path);
307     vol->target.path = g_strdup_printf("%s/%s", def->target.path, vol->name);
308 
309     vol->key = g_strdup(vol->target.path);
310 
311     volmode_needed = virStorageBackendZFSVolModeNeeded();
312     if (volmode_needed < 0)
313         return -1;
314     /**
315      * $ zfs create -o volmode=dev -V 10240K test/volname
316      * $ zfs create -o volmode=dev -s -V 10240K test/volname
317      * $ zfs create -o volmode=dev -s -o refreservation=1024K -V 10240K test/volname
318      *
319      * -o volmode=dev -- we want to get volumes exposed as cdev
320      *                   devices. If we don't specify that zfs
321      *                   will lookup vfs.zfs.vol.mode sysctl value
322      * -s -- create a sparse volume
323      * -o refreservation -- reserve the specified amount of space
324      * -V -- tells to create a volume with the specified size
325      */
326     cmd = virCommandNewArgList(ZFS, "create", NULL);
327     if (volmode_needed)
328         virCommandAddArgList(cmd, "-o", "volmode=dev", NULL);
329     if (vol->target.capacity != vol->target.allocation) {
330         virCommandAddArg(cmd, "-s");
331         if (vol->target.allocation > 0) {
332             virCommandAddArg(cmd, "-o");
333             virCommandAddArgFormat(cmd, "refreservation=%lluK",
334                                    VIR_DIV_UP(vol->target.allocation, 1024));
335         }
336         vol->target.sparse = true;
337     }
338     virCommandAddArg(cmd, "-V");
339     virCommandAddArgFormat(cmd, "%lluK",
340                            VIR_DIV_UP(vol->target.capacity, 1024));
341     virCommandAddArgFormat(cmd, "%s/%s", def->source.name, vol->name);
342 
343     if (virCommandRun(cmd, NULL) < 0)
344         return -1;
345 
346     if (virStorageBackendZFSFindVols(pool, vol) < 0)
347         return -1;
348 
349     return 0;
350 }
351 
352 static int
virStorageBackendZFSDeleteVol(virStoragePoolObj * pool,virStorageVolDef * vol,unsigned int flags)353 virStorageBackendZFSDeleteVol(virStoragePoolObj *pool,
354                               virStorageVolDef *vol,
355                               unsigned int flags)
356 {
357     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
358     g_autoptr(virCommand) destroy_cmd = NULL;
359 
360     virCheckFlags(0, -1);
361 
362     destroy_cmd = virCommandNewArgList(ZFS, "destroy", NULL);
363 
364     virCommandAddArgFormat(destroy_cmd, "%s/%s",
365                            def->source.name, vol->name);
366 
367     return virCommandRun(destroy_cmd, NULL);
368 }
369 
370 static int
virStorageBackendZFSBuildPool(virStoragePoolObj * pool,unsigned int flags)371 virStorageBackendZFSBuildPool(virStoragePoolObj *pool,
372                               unsigned int flags)
373 {
374     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
375     size_t i;
376     g_autoptr(virCommand) cmd = NULL;
377     int ret = -1;
378     char *tmp;
379 
380     virCheckFlags(0, -1);
381 
382     tmp = strstr(def->source.name, "/");
383     if (tmp) {
384         cmd = virCommandNewArgList(ZFS, "create", "-o", "mountpoint=none",
385                                    def->source.name, NULL);
386     } else {
387         if (def->source.ndevice == 0) {
388             virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
389                            "%s", _("missing source devices"));
390             return -1;
391         }
392 
393         cmd = virCommandNewArgList(ZPOOL, "create",
394                                    def->source.name, NULL);
395 
396         for (i = 0; i < def->source.ndevice; i++)
397             virCommandAddArg(cmd, def->source.devices[i].path);
398     }
399 
400     virObjectUnlock(pool);
401     ret = virCommandRun(cmd, NULL);
402     virObjectLock(pool);
403 
404     return ret;
405 }
406 
407 static int
virStorageBackendZFSDeletePool(virStoragePoolObj * pool,unsigned int flags)408 virStorageBackendZFSDeletePool(virStoragePoolObj *pool,
409                                unsigned int flags)
410 {
411     virStoragePoolDef *def = virStoragePoolObjGetDef(pool);
412     g_autoptr(virCommand) cmd = NULL;
413     char *tmp;
414 
415     virCheckFlags(0, -1);
416 
417     tmp = strstr(def->source.name, "/");
418     if (tmp) {
419         cmd = virCommandNewArgList(ZFS, "destroy", "-r",
420                                    def->source.name, NULL);
421     } else {
422         cmd = virCommandNewArgList(ZPOOL, "destroy",
423                                    def->source.name, NULL);
424     }
425 
426     return virCommandRun(cmd, NULL);
427 }
428 
429 virStorageBackend virStorageBackendZFS = {
430     .type = VIR_STORAGE_POOL_ZFS,
431 
432     .checkPool = virStorageBackendZFSCheckPool,
433     .refreshPool = virStorageBackendZFSRefreshPool,
434     .createVol = virStorageBackendZFSCreateVol,
435     .deleteVol = virStorageBackendZFSDeleteVol,
436     .buildPool = virStorageBackendZFSBuildPool,
437     .deletePool = virStorageBackendZFSDeletePool,
438     .uploadVol = virStorageBackendVolUploadLocal,
439     .downloadVol = virStorageBackendVolDownloadLocal,
440 };
441 
442 
443 int
virStorageBackendZFSRegister(void)444 virStorageBackendZFSRegister(void)
445 {
446     return virStorageBackendRegister(&virStorageBackendZFS);
447 }
448