5a4d8154f254b8352bf3df6bb2606719a158f9e0
[libguestfs.git] / daemon / md.c
1 /* libguestfs - the guestfsd daemon
2  * Copyright (C) 2011 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <inttypes.h>
25 #include <glob.h>
26
27 #include "daemon.h"
28 #include "actions.h"
29 #include "optgroups.h"
30 #include "c-ctype.h"
31
32 int
33 optgroup_mdadm_available (void)
34 {
35   return prog_exists ("mdadm");
36 }
37
38 static size_t
39 count_bits (uint64_t bitmap)
40 {
41   size_t c;
42
43   if (bitmap == 0)
44     return 0;
45
46   c = bitmap & 1 ? 1 : 0;
47   bitmap >>= 1;
48   return c + count_bits (bitmap);
49 }
50
51 /* Takes optional arguments, consult optargs_bitmask. */
52 int
53 do_md_create (const char *name, char *const *devices,
54               int64_t missingbitmap, int nrdevices, int spare,
55               int64_t chunk, const char *level)
56 {
57   char nrdevices_s[32];
58   char spare_s[32];
59   char chunk_s[32];
60   size_t j;
61   int r;
62   char *err;
63   uint64_t umissingbitmap = (uint64_t) missingbitmap;
64
65   /* Check the optional parameters and set defaults where appropriate. */
66   if (!(optargs_bitmask & GUESTFS_MD_CREATE_MISSINGBITMAP_BITMASK))
67     umissingbitmap = 0;
68
69   if (optargs_bitmask & GUESTFS_MD_CREATE_SPARE_BITMASK) {
70     if (spare < 0) {
71       reply_with_error ("spare must not be negative");
72       return -1;
73     }
74   }
75   else
76     spare = 0;
77
78   if (optargs_bitmask & GUESTFS_MD_CREATE_NRDEVICES_BITMASK) {
79     if (nrdevices < 2) {
80       reply_with_error ("nrdevices is less than 2");
81       return -1;
82     }
83   }
84   else
85     nrdevices = count_strings (devices) + count_bits (umissingbitmap);
86
87   if (optargs_bitmask & GUESTFS_MD_CREATE_LEVEL_BITMASK) {
88     if (STRNEQ (level, "linear") && STRNEQ (level, "raid0") &&
89         STRNEQ (level, "0") && STRNEQ (level, "stripe") &&
90         STRNEQ (level, "raid1") && STRNEQ (level, "1") &&
91         STRNEQ (level, "mirror") &&
92         STRNEQ (level, "raid4") && STRNEQ (level, "4") &&
93         STRNEQ (level, "raid5") && STRNEQ (level, "5") &&
94         STRNEQ (level, "raid6") && STRNEQ (level, "6") &&
95         STRNEQ (level, "raid10") && STRNEQ (level, "10")) {
96       reply_with_error ("unknown level parameter: %s", level);
97       return -1;
98     }
99   }
100   else
101     level = "raid1";
102
103   if (optargs_bitmask & GUESTFS_MD_CREATE_CHUNK_BITMASK) {
104     /* chunk is bytes in the libguestfs API, but K when we pass it to mdadm */
105     if ((chunk & 1023) != 0) {
106       reply_with_error ("chunk size must be a multiple of 1024 bytes");
107       return -1;
108     }
109   }
110
111   /* Check invariant. */
112   if (count_strings (devices) + count_bits (umissingbitmap) !=
113       (size_t) (nrdevices + spare)) {
114     reply_with_error ("devices (%zu) + bits set in missingbitmap (%zu) is not equal to nrdevices (%d) + spare (%d)",
115                       count_strings (devices), count_bits (umissingbitmap),
116                       nrdevices, spare);
117     return -1;
118   }
119
120   size_t MAX_ARGS = nrdevices + 16;
121   const char *argv[MAX_ARGS];
122   size_t i = 0;
123
124   ADD_ARG (argv, i, "mdadm");
125   ADD_ARG (argv, i, "--create");
126   /* --run suppresses "Continue creating array" question */
127   ADD_ARG (argv, i, "--run");
128   ADD_ARG (argv, i, name);
129   ADD_ARG (argv, i, "--level");
130   ADD_ARG (argv, i, level);
131   ADD_ARG (argv, i, "--raid-devices");
132   snprintf (nrdevices_s, sizeof nrdevices_s, "%d", nrdevices);
133   ADD_ARG (argv, i, nrdevices_s);
134   if (optargs_bitmask & GUESTFS_MD_CREATE_SPARE_BITMASK) {
135     ADD_ARG (argv, i, "--spare-devices");
136     snprintf (spare_s, sizeof spare_s, "%d", spare);
137     ADD_ARG (argv, i, spare_s);
138   }
139   if (optargs_bitmask & GUESTFS_MD_CREATE_CHUNK_BITMASK) {
140     ADD_ARG (argv, i, "--chunk");
141     snprintf (chunk_s, sizeof chunk_s, "%" PRIi64, chunk / 1024);
142     ADD_ARG (argv, i, chunk_s);
143   }
144
145   /* Add devices and "missing". */
146   j = 0;
147   while (devices[j] != NULL || umissingbitmap != 0) {
148     if (umissingbitmap & 1)
149       ADD_ARG (argv, i, "missing");
150     else {
151       ADD_ARG (argv, i, devices[j]);
152       j++;
153     }
154     umissingbitmap >>= 1;
155   }
156
157   ADD_ARG (argv, i, NULL);
158
159   r = commandv (NULL, &err, argv);
160   if (r == -1) {
161     reply_with_error ("mdadm: %s: %s", name, err);
162     free (err);
163     return -1;
164   }
165
166   free (err);
167
168   udev_settle ();
169
170   return 0;
171 }
172
173 static int
174 glob_errfunc (const char *epath, int eerrno)
175 {
176   fprintf (stderr, "glob: failure reading %s: %s\n", epath, strerror (eerrno));
177   return 1;
178 }
179
180 char **
181 do_list_md_devices (void)
182 {
183   char **r = NULL;
184   int size = 0, alloc = 0;
185   glob_t mds;
186
187   memset(&mds, 0, sizeof(mds));
188
189 #define PREFIX "/sys/block/md"
190 #define SUFFIX "/md"
191
192   /* Look for directories under /sys/block matching md[0-9]*
193    * As an additional check, we also make sure they have a md subdirectory.
194    */
195   int err = glob (PREFIX "[0-9]*" SUFFIX, GLOB_ERR, glob_errfunc, &mds);
196   if (err == GLOB_NOSPACE) {
197     reply_with_error ("glob: returned GLOB_NOSPACE: "
198                       "rerun with LIBGUESTFS_DEBUG=1");
199     goto error;
200   } else if (err == GLOB_ABORTED) {
201     reply_with_error ("glob: returned GLOB_ABORTED: "
202                       "rerun with LIBGUESTFS_DEBUG=1");
203     goto error;
204   }
205
206   for (size_t i = 0; i < mds.gl_pathc; i++) {
207     size_t len = strlen (mds.gl_pathv[i]) - strlen (PREFIX) - strlen (SUFFIX);
208
209 #define DEV "/dev/md"
210     char *dev = malloc (strlen(DEV) + len  + 1);
211     if (NULL == dev) {
212       reply_with_perror("malloc");
213       goto error;
214     }
215
216     char *n = dev;
217     n = mempcpy(n, DEV, strlen(DEV));
218     n = mempcpy(n, &mds.gl_pathv[i][strlen(PREFIX)], len);
219     *n = '\0';
220
221     if (add_string_nodup (&r, &size, &alloc, dev) == -1) goto error;
222   }
223
224   if (add_string_nodup (&r, &size, &alloc, NULL) == -1) goto error;
225   globfree (&mds);
226
227   return r;
228
229 error:
230   globfree (&mds);
231   if (r != NULL) free_strings (r);
232   return NULL;
233 }
234
235 char **
236 do_md_detail(const char *md)
237 {
238   int r;
239
240   char *out = NULL, *err = NULL;
241   char **lines = NULL;
242
243   char **ret = NULL;
244   int size = 0, alloc = 0;
245
246   const char *mdadm[] = { "mdadm", "-D", "--export", md, NULL };
247   r = commandv(&out, &err, mdadm);
248   if (r == -1) {
249     reply_with_error("%s", err);
250     goto error;
251   }
252
253   /* Split the command output into lines */
254   lines = split_lines(out);
255   if (lines == NULL) {
256     reply_with_perror("malloc");
257     goto error;
258   }
259
260   /* Parse the output of mdadm -D --export:
261    * MD_LEVEL=raid1
262    * MD_DEVICES=2
263    * MD_METADATA=1.0
264    * MD_UUID=cfa81b59:b6cfbd53:3f02085b:58f4a2e1
265    * MD_NAME=localhost.localdomain:0
266    */
267   for (char **i = lines; *i != NULL; i++) {
268     char *line = *i;
269
270     /* Skip blank lines (shouldn't happen) */
271     if (line[0] == '\0') continue;
272
273     /* Split the line in 2 at the equals sign */
274     char *eq = strchr(line, '=');
275     if (eq) {
276       *eq = '\0'; eq++;
277
278       /* Remove the MD_ prefix from the key and translate the remainder to lower
279        * case */
280       if (STRPREFIX(line, "MD_")) {
281         line += 3;
282         for (char *j = line; *j != '\0'; j++) {
283           *j = c_tolower(*j);
284         }
285       }
286
287       /* Add the key/value pair to the output */
288       if (add_string(&ret, &size, &alloc, line) == -1 ||
289           add_string(&ret, &size, &alloc, eq) == -1) goto error;
290     } else {
291       /* Ignore lines with no equals sign (shouldn't happen). Log to stderr so
292        * it will show up in LIBGUESTFS_DEBUG. */
293       fprintf(stderr, "md-detail: unexpected mdadm output ignored: %s", line);
294     }
295   }
296
297   free(out);
298   free(err);
299   free(lines); /* We freed the contained strings when we freed out */
300
301   if (add_string(&ret, &size, &alloc, NULL) == -1) return NULL;
302
303   return ret;
304
305 error:
306   free(out);
307   free(err);
308   if (lines) free(lines);
309   if (ret) free_strings(ret);
310
311   return NULL;
312 }
313
314 int
315 do_md_stop(const char *md)
316 {
317   int r;
318   char *err = NULL;
319
320   const char *mdadm[] = { "mdadm", "--stop", md, NULL};
321   r = commandv(NULL, &err, mdadm);
322   if (r == -1) {
323     reply_with_error("%s", err);
324     free(err);
325     return -1;
326   }
327   return 0;
328 }