btrfs-progs: don't leak fd in test_dev_for_mkfs() error paths
[platform/upstream/btrfs-progs.git] / cmds-replace.c
1 /*
2  * Copyright (C) 2012 STRATO.  All rights reserved.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License v2 as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public
14  * License along with this program; if not, write to the
15  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16  * Boston, MA 021110-1307, USA.
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/ioctl.h>
25 #include <errno.h>
26 #include <sys/stat.h>
27 #include <time.h>
28 #include <assert.h>
29 #include <inttypes.h>
30 #include <sys/wait.h>
31
32 #include "kerncompat.h"
33 #include "ctree.h"
34 #include "ioctl.h"
35 #include "utils.h"
36 #include "volumes.h"
37 #include "disk-io.h"
38
39 #include "commands.h"
40
41
42 static int print_replace_status(int fd, const char *path, int once);
43 static char *time2string(char *buf, size_t s, __u64 t);
44 static char *progress2string(char *buf, size_t s, int progress_1000);
45
46
47 static const char *replace_dev_result2string(__u64 result)
48 {
49         switch (result) {
50         case BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR:
51                 return "no error";
52         case BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED:
53                 return "not started";
54         case BTRFS_IOCTL_DEV_REPLACE_RESULT_ALREADY_STARTED:
55                 return "already started";
56         default:
57                 return "<illegal result value>";
58         }
59 }
60
61 static const char * const replace_cmd_group_usage[] = {
62         "btrfs replace <command> [<args>]",
63         NULL
64 };
65
66 static int is_numerical(const char *str)
67 {
68         if (!(*str >= '0' && *str <= '9'))
69                 return 0;
70         while (*str >= '0' && *str <= '9')
71                 str++;
72         if (*str != '\0')
73                 return 0;
74         return 1;
75 }
76
77 static int dev_replace_cancel_fd = -1;
78 static void dev_replace_sigint_handler(int signal)
79 {
80         int ret;
81         struct btrfs_ioctl_dev_replace_args args = {0};
82
83         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
84         ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
85         if (ret < 0)
86                 perror("Device replace cancel failed");
87 }
88
89 static int dev_replace_handle_sigint(int fd)
90 {
91         struct sigaction sa = {
92                 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
93         };
94
95         dev_replace_cancel_fd = fd;
96         return sigaction(SIGINT, &sa, NULL);
97 }
98
99 static const char *const cmd_start_replace_usage[] = {
100         "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
101         "Replace device of a btrfs filesystem.",
102         "On a live filesystem, duplicate the data to the target device which",
103         "is currently stored on the source device. If the source device is not",
104         "available anymore, or if the -r option is set, the data is built",
105         "only using the RAID redundancy mechanisms. After completion of the",
106         "operation, the source device is removed from the filesystem.",
107         "If the <srcdev> is a numerical value, it is assumed to be the device id",
108         "of the filesystem which is mounted at <mount_point>, otherwise it is",
109         "the path to the source device. If the source device is disconnected,",
110         "from the system, you have to use the <devid> parameter format.",
111         "The <targetdev> needs to be same size or larger than the <srcdev>.",
112         "",
113         "-r     only read from <srcdev> if no other zero-defect mirror exists",
114         "       (enable this if your drive has lots of read errors, the access",
115         "       would be very slow)",
116         "-f     force using and overwriting <targetdev> even if it looks like",
117         "       containing a valid btrfs filesystem. A valid filesystem is",
118         "       assumed if a btrfs superblock is found which contains a",
119         "       correct checksum. Devices which are currently mounted are",
120         "       never allowed to be used as the <targetdev>",
121         "-B     do not background",
122         NULL
123 };
124
125 static int cmd_start_replace(int argc, char **argv)
126 {
127         struct btrfs_ioctl_dev_replace_args start_args = {0};
128         struct btrfs_ioctl_dev_replace_args status_args = {0};
129         int ret;
130         int i;
131         int c;
132         int fdmnt = -1;
133         int fdsrcdev = -1;
134         int fddstdev = -1;
135         char *path;
136         char *srcdev;
137         char *dstdev;
138         int avoid_reading_from_srcdev = 0;
139         int force_using_targetdev = 0;
140         struct stat st;
141         u64 dstdev_block_count;
142         int do_not_background = 0;
143         int mixed = 0;
144         DIR *dirstream = NULL;
145         char estr[100]; /* check test_dev_for_mkfs() for error string size*/
146
147         while ((c = getopt(argc, argv, "Brf")) != -1) {
148                 switch (c) {
149                 case 'B':
150                         do_not_background = 1;
151                         break;
152                 case 'r':
153                         avoid_reading_from_srcdev = 1;
154                         break;
155                 case 'f':
156                         force_using_targetdev = 1;
157                         break;
158                 case '?':
159                 default:
160                         usage(cmd_start_replace_usage);
161                 }
162         }
163
164         start_args.start.cont_reading_from_srcdev_mode =
165                 avoid_reading_from_srcdev ?
166                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
167                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
168         if (check_argc_exact(argc - optind, 3))
169                 usage(cmd_start_replace_usage);
170         path = argv[optind + 2];
171
172         fdmnt = open_path_or_dev_mnt(path, &dirstream);
173
174         if (fdmnt < 0) {
175                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
176                         path, strerror(errno));
177                 goto leave_with_error;
178         }
179
180         /* check for possible errors before backgrounding */
181         status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
182         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
183         if (ret) {
184                 fprintf(stderr,
185                         "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
186                         path, strerror(errno),
187                         replace_dev_result2string(status_args.result));
188                 goto leave_with_error;
189         }
190
191         if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
192                 fprintf(stderr,
193                         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
194                         path, replace_dev_result2string(status_args.result));
195                 goto leave_with_error;
196         }
197
198         if (status_args.status.replace_state ==
199             BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
200                 fprintf(stderr,
201                         "ERROR: btrfs replace on \"%s\" already started!\n",
202                         path);
203                 goto leave_with_error;
204         }
205
206         srcdev = argv[optind];
207         dstdev = argv[optind + 1];
208
209         if (is_numerical(srcdev)) {
210                 struct btrfs_ioctl_fs_info_args fi_args;
211                 struct btrfs_ioctl_dev_info_args *di_args = NULL;
212
213                 if (atoi(srcdev) == 0) {
214                         fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
215                                 srcdev);
216                         goto leave_with_error;
217                 }
218                 start_args.start.srcdevid = (__u64)atoi(srcdev);
219
220                 ret = get_fs_info(path, &fi_args, &di_args);
221                 if (ret) {
222                         fprintf(stderr, "ERROR: getting dev info for devstats failed: "
223                                         "%s\n", strerror(-ret));
224                         free(di_args);
225                         goto leave_with_error;
226                 }
227                 if (!fi_args.num_devices) {
228                         fprintf(stderr, "ERROR: no devices found\n");
229                         free(di_args);
230                         goto leave_with_error;
231                 }
232
233                 for (i = 0; i < fi_args.num_devices; i++)
234                         if (start_args.start.srcdevid == di_args[i].devid)
235                                 break;
236                 free(di_args);
237                 if (i == fi_args.num_devices) {
238                         fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
239                                 srcdev, path);
240                         goto leave_with_error;
241                 }
242         } else {
243                 fdsrcdev = open(srcdev, O_RDWR);
244                 if (fdsrcdev < 0) {
245                         fprintf(stderr, "Error: Unable to open device '%s'\n",
246                                 srcdev);
247                         fprintf(stderr, "\tTry using the devid instead of the path\n");
248                         goto leave_with_error;
249                 }
250                 ret = fstat(fdsrcdev, &st);
251                 if (ret) {
252                         fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
253                         goto leave_with_error;
254                 }
255                 if (!S_ISBLK(st.st_mode)) {
256                         fprintf(stderr, "Error: '%s' is not a block device\n",
257                                 srcdev);
258                         goto leave_with_error;
259                 }
260                 strncpy((char *)start_args.start.srcdev_name, srcdev,
261                         BTRFS_DEVICE_PATH_NAME_MAX);
262                 close(fdsrcdev);
263                 fdsrcdev = -1;
264                 start_args.start.srcdevid = 0;
265         }
266
267         ret = test_dev_for_mkfs(dstdev, force_using_targetdev, estr);
268         if (ret) {
269                 fprintf(stderr, "%s", estr);
270                 goto leave_with_error;
271         }
272         fddstdev = open(dstdev, O_RDWR);
273         if (fddstdev < 0) {
274                 fprintf(stderr, "Unable to open %s\n", dstdev);
275                 goto leave_with_error;
276         }
277         strncpy((char *)start_args.start.tgtdev_name, dstdev,
278                 BTRFS_DEVICE_PATH_NAME_MAX);
279         if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
280                                  &mixed, 0)) {
281                 fprintf(stderr, "Error: Failed to prepare device '%s'\n",
282                         dstdev);
283                 goto leave_with_error;
284         }
285         close(fddstdev);
286         fddstdev = -1;
287
288         dev_replace_handle_sigint(fdmnt);
289         if (!do_not_background) {
290                 if (daemon(0, 0) < 0) {
291                         fprintf(stderr, "ERROR, backgrounding failed: %s\n",
292                                 strerror(errno));
293                         goto leave_with_error;
294                 }
295         }
296
297         start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
298         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
299         if (do_not_background) {
300                 if (ret) {
301                         fprintf(stderr,
302                                 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
303                                 path, strerror(errno),
304                                 replace_dev_result2string(start_args.result));
305                         goto leave_with_error;
306                 }
307
308                 if (start_args.result !=
309                     BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
310                         fprintf(stderr,
311                                 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
312                                 path,
313                                 replace_dev_result2string(start_args.result));
314                         goto leave_with_error;
315                 }
316         }
317         close_file_or_dir(fdmnt, dirstream);
318         return 0;
319
320 leave_with_error:
321         if (fdmnt != -1)
322                 close(fdmnt);
323         if (fdsrcdev != -1)
324                 close(fdsrcdev);
325         if (fddstdev != -1)
326                 close(fddstdev);
327         return 1;
328 }
329
330 static const char *const cmd_status_replace_usage[] = {
331         "btrfs replace status [-1] <mount_point>",
332         "Print status and progress information of a running device replace",
333         "operation",
334         "",
335         "-1     print once instead of print continuously until the replace",
336         "       operation finishes (or is canceled)",
337         NULL
338 };
339
340 static int cmd_status_replace(int argc, char **argv)
341 {
342         int fd;
343         int e;
344         int c;
345         char *path;
346         int once = 0;
347         int ret;
348         DIR *dirstream = NULL;
349
350         while ((c = getopt(argc, argv, "1")) != -1) {
351                 switch (c) {
352                 case '1':
353                         once = 1;
354                         break;
355                 case '?':
356                 default:
357                         usage(cmd_status_replace_usage);
358                 }
359         }
360
361         if (check_argc_exact(argc - optind, 1))
362                 usage(cmd_status_replace_usage);
363
364         path = argv[optind];
365         fd = open_file_or_dir(path, &dirstream);
366         e = errno;
367         if (fd < 0) {
368                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
369                         path, strerror(e));
370                 return 1;
371         }
372
373         ret = print_replace_status(fd, path, once);
374         close_file_or_dir(fd, dirstream);
375         return !!ret;
376 }
377
378 static int print_replace_status(int fd, const char *path, int once)
379 {
380         struct btrfs_ioctl_dev_replace_args args = {0};
381         struct btrfs_ioctl_dev_replace_status_params *status;
382         int ret;
383         int prevent_loop = 0;
384         int skip_stats;
385         int num_chars;
386         char string1[80];
387         char string2[80];
388         char string3[80];
389
390         for (;;) {
391                 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
392                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
393                 if (ret) {
394                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
395                                 path, strerror(errno),
396                                 replace_dev_result2string(args.result));
397                         return ret;
398                 }
399
400                 status = &args.status;
401                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
402                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
403                                 path,
404                                 replace_dev_result2string(args.result));
405                         return -1;
406                 }
407
408                 skip_stats = 0;
409                 num_chars = 0;
410                 switch (status->replace_state) {
411                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
412                         num_chars =
413                                 printf("%s done",
414                                        progress2string(string3,
415                                                        sizeof(string3),
416                                                        status->progress_1000));
417                         break;
418                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
419                         prevent_loop = 1;
420                         printf("Started on %s, finished on %s",
421                                time2string(string1, sizeof(string1),
422                                            status->time_started),
423                                time2string(string2, sizeof(string2),
424                                            status->time_stopped));
425                         break;
426                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
427                         prevent_loop = 1;
428                         printf("Started on %s, canceled on %s at %s",
429                                time2string(string1, sizeof(string1),
430                                            status->time_started),
431                                time2string(string2, sizeof(string2),
432                                            status->time_stopped),
433                                progress2string(string3, sizeof(string3),
434                                                status->progress_1000));
435                         break;
436                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
437                         prevent_loop = 1;
438                         printf("Started on %s, suspended on %s at %s",
439                                time2string(string1, sizeof(string1),
440                                            status->time_started),
441                                time2string(string2, sizeof(string2),
442                                            status->time_stopped),
443                                progress2string(string3, sizeof(string3),
444                                                status->progress_1000));
445                         break;
446                 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
447                         prevent_loop = 1;
448                         skip_stats = 1;
449                         printf("Never started");
450                         break;
451                 default:
452                         prevent_loop = 1;
453                         assert(0);
454                         break;
455                 }
456
457                 if (!skip_stats)
458                         num_chars += printf(
459                                 ", %llu write errs, %llu uncorr. read errs",
460                                 (unsigned long long)status->num_write_errors,
461                                 (unsigned long long)
462                                  status->num_uncorrectable_read_errors);
463                 if (once || prevent_loop) {
464                         printf("\n");
465                         return 0;
466                 }
467
468                 fflush(stdout);
469                 sleep(1);
470                 while (num_chars > 0) {
471                         putchar('\b');
472                         num_chars--;
473                 }
474         }
475
476         return 0;
477 }
478
479 static char *
480 time2string(char *buf, size_t s, __u64 t)
481 {
482         struct tm t_tm;
483         time_t t_time_t;
484
485         t_time_t = (time_t)t;
486         assert((__u64)t_time_t == t);
487         localtime_r(&t_time_t, &t_tm);
488         strftime(buf, s, "%e.%b %T", &t_tm);
489         return buf;
490 }
491
492 static char *
493 progress2string(char *buf, size_t s, int progress_1000)
494 {
495         snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
496         assert(s > 0);
497         buf[s - 1] = '\0';
498         return buf;
499 }
500
501 static const char *const cmd_cancel_replace_usage[] = {
502         "btrfs replace cancel <mount_point>",
503         "Cancel a running device replace operation.",
504         NULL
505 };
506
507 static int cmd_cancel_replace(int argc, char **argv)
508 {
509         struct btrfs_ioctl_dev_replace_args args = {0};
510         int ret;
511         int c;
512         int fd;
513         int e;
514         char *path;
515         DIR *dirstream = NULL;
516
517         while ((c = getopt(argc, argv, "")) != -1) {
518                 switch (c) {
519                 case '?':
520                 default:
521                         usage(cmd_cancel_replace_usage);
522                 }
523         }
524
525         if (check_argc_exact(argc - optind, 1))
526                 usage(cmd_cancel_replace_usage);
527
528         path = argv[optind];
529         fd = open_file_or_dir(path, &dirstream);
530         if (fd < 0) {
531                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
532                         path, strerror(errno));
533                 return 1;
534         }
535
536         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
537         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
538         e = errno;
539         close_file_or_dir(fd, dirstream);
540         if (ret) {
541                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
542                         path, strerror(e),
543                         replace_dev_result2string(args.result));
544                 return 1;
545         }
546         if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
547                 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
548                         path, replace_dev_result2string(args.result));
549                 return 2;
550         }
551         return 0;
552 }
553
554 const struct cmd_group replace_cmd_group = {
555         replace_cmd_group_usage, NULL, {
556                 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
557                   0 },
558                 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
559                   0 },
560                 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
561                   0 },
562                 NULL_CMD_STRUCT
563         }
564 };
565
566 int cmd_replace(int argc, char **argv)
567 {
568         return handle_command_group(&replace_cmd_group, argc, argv);
569 }