btrfs-progs: unified error handling in print_replace_status
[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         case BTRFS_IOCTL_DEV_REPLACE_RESULT_SCRUB_INPROGRESS:
57                 return "scrub is in progress";
58         default:
59                 return "<illegal result value>";
60         }
61 }
62
63 static const char * const replace_cmd_group_usage[] = {
64         "btrfs replace <command> [<args>]",
65         NULL
66 };
67
68 static int is_numerical(const char *str)
69 {
70         if (!(*str >= '0' && *str <= '9'))
71                 return 0;
72         while (*str >= '0' && *str <= '9')
73                 str++;
74         if (*str != '\0')
75                 return 0;
76         return 1;
77 }
78
79 static int dev_replace_cancel_fd = -1;
80 static void dev_replace_sigint_handler(int signal)
81 {
82         int ret;
83         struct btrfs_ioctl_dev_replace_args args = {0};
84
85         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
86         ret = ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
87         if (ret < 0)
88                 perror("Device replace cancel failed");
89 }
90
91 static int dev_replace_handle_sigint(int fd)
92 {
93         struct sigaction sa = {
94                 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
95         };
96
97         dev_replace_cancel_fd = fd;
98         return sigaction(SIGINT, &sa, NULL);
99 }
100
101 static const char *const cmd_replace_start_usage[] = {
102         "btrfs replace start [-Bfr] <srcdev>|<devid> <targetdev> <mount_point>",
103         "Replace device of a btrfs filesystem.",
104         "On a live filesystem, duplicate the data to the target device which",
105         "is currently stored on the source device. If the source device is not",
106         "available anymore, or if the -r option is set, the data is built",
107         "only using the RAID redundancy mechanisms. After completion of the",
108         "operation, the source device is removed from the filesystem.",
109         "If the <srcdev> is a numerical value, it is assumed to be the device id",
110         "of the filesystem which is mounted at <mount_point>, otherwise it is",
111         "the path to the source device. If the source device is disconnected,",
112         "from the system, you have to use the <devid> parameter format.",
113         "The <targetdev> needs to be same size or larger than the <srcdev>.",
114         "",
115         "-r     only read from <srcdev> if no other zero-defect mirror exists",
116         "       (enable this if your drive has lots of read errors, the access",
117         "       would be very slow)",
118         "-f     force using and overwriting <targetdev> even if it looks like",
119         "       containing a valid btrfs filesystem. A valid filesystem is",
120         "       assumed if a btrfs superblock is found which contains a",
121         "       correct checksum. Devices which are currently mounted are",
122         "       never allowed to be used as the <targetdev>",
123         "-B     do not background",
124         NULL
125 };
126
127 static int cmd_replace_start(int argc, char **argv)
128 {
129         struct btrfs_ioctl_dev_replace_args start_args = {0};
130         struct btrfs_ioctl_dev_replace_args status_args = {0};
131         int ret;
132         int i;
133         int c;
134         int fdmnt = -1;
135         int fddstdev = -1;
136         char *path;
137         char *srcdev;
138         char *dstdev = NULL;
139         int avoid_reading_from_srcdev = 0;
140         int force_using_targetdev = 0;
141         u64 dstdev_block_count;
142         int do_not_background = 0;
143         int mixed = 0;
144         DIR *dirstream = NULL;
145         u64 srcdev_size;
146         u64 dstdev_size;
147
148         while ((c = getopt(argc, argv, "Brf")) != -1) {
149                 switch (c) {
150                 case 'B':
151                         do_not_background = 1;
152                         break;
153                 case 'r':
154                         avoid_reading_from_srcdev = 1;
155                         break;
156                 case 'f':
157                         force_using_targetdev = 1;
158                         break;
159                 case '?':
160                 default:
161                         usage(cmd_replace_start_usage);
162                 }
163         }
164
165         start_args.start.cont_reading_from_srcdev_mode =
166                 avoid_reading_from_srcdev ?
167                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
168                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
169         if (check_argc_exact(argc - optind, 3))
170                 usage(cmd_replace_start_usage);
171         path = argv[optind + 2];
172
173         fdmnt = open_path_or_dev_mnt(path, &dirstream);
174
175         if (fdmnt < 0) {
176                 if (errno == EINVAL)
177                         fprintf(stderr,
178                                 "ERROR: '%s' is not a mounted btrfs device\n",
179                                 path);
180                 else
181                         fprintf(stderr, "ERROR: can't access '%s': %s\n",
182                                 path, strerror(errno));
183                 goto leave_with_error;
184         }
185
186         /* check for possible errors before backgrounding */
187         status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
188         status_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
189         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
190         if (ret) {
191                 fprintf(stderr,
192                         "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
193                         path, strerror(errno));
194                 if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
195                         fprintf(stderr, ", %s\n",
196                                 replace_dev_result2string(status_args.result));
197                 else
198                         fprintf(stderr, "\n");
199                 goto leave_with_error;
200         }
201
202         if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
203                 fprintf(stderr,
204                         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
205                         path, replace_dev_result2string(status_args.result));
206                 goto leave_with_error;
207         }
208
209         if (status_args.status.replace_state ==
210             BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
211                 fprintf(stderr,
212                         "ERROR: btrfs replace on \"%s\" already started!\n",
213                         path);
214                 goto leave_with_error;
215         }
216
217         srcdev = argv[optind];
218         dstdev = canonicalize_path(argv[optind + 1]);
219         if (!dstdev) {
220                 fprintf(stderr,
221                         "ERROR: Could not canonicalize path '%s': %s\n",
222                         argv[optind + 1], strerror(errno));
223                 goto leave_with_error;
224         }
225
226         if (is_numerical(srcdev)) {
227                 struct btrfs_ioctl_fs_info_args fi_args;
228                 struct btrfs_ioctl_dev_info_args *di_args = NULL;
229
230                 start_args.start.srcdevid = arg_strtou64(srcdev);
231
232                 ret = get_fs_info(path, &fi_args, &di_args);
233                 if (ret) {
234                         fprintf(stderr, "ERROR: getting dev info for devstats failed: "
235                                         "%s\n", strerror(-ret));
236                         free(di_args);
237                         goto leave_with_error;
238                 }
239                 if (!fi_args.num_devices) {
240                         fprintf(stderr, "ERROR: no devices found\n");
241                         free(di_args);
242                         goto leave_with_error;
243                 }
244
245                 for (i = 0; i < fi_args.num_devices; i++)
246                         if (start_args.start.srcdevid == di_args[i].devid)
247                                 break;
248                 free(di_args);
249                 if (i == fi_args.num_devices) {
250                         fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
251                                 srcdev, path);
252                         goto leave_with_error;
253                 }
254                 srcdev_size = di_args[i].total_bytes;
255         } else if (is_block_device(srcdev) > 0) {
256                 strncpy((char *)start_args.start.srcdev_name, srcdev,
257                         BTRFS_DEVICE_PATH_NAME_MAX);
258                 start_args.start.srcdevid = 0;
259                 srcdev_size = get_partition_size(srcdev);
260         } else {
261                 fprintf(stderr, "ERROR: source device must be a block device or a devid\n");
262                 goto leave_with_error;
263         }
264
265         ret = test_dev_for_mkfs(dstdev, force_using_targetdev);
266         if (ret)
267                 goto leave_with_error;
268
269         dstdev_size = get_partition_size(dstdev);
270         if (srcdev_size > dstdev_size) {
271                 fprintf(stderr, "ERROR: target device smaller than source device (required %llu bytes)\n",
272                         srcdev_size);
273                 goto leave_with_error;
274         }
275
276         fddstdev = open(dstdev, O_RDWR);
277         if (fddstdev < 0) {
278                 fprintf(stderr, "Unable to open %s\n", dstdev);
279                 goto leave_with_error;
280         }
281         strncpy((char *)start_args.start.tgtdev_name, dstdev,
282                 BTRFS_DEVICE_PATH_NAME_MAX);
283         ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
284                                  &mixed, 0);
285         if (ret)
286                 goto leave_with_error;
287
288         close(fddstdev);
289         fddstdev = -1;
290         free(dstdev);
291         dstdev = NULL;
292
293         dev_replace_handle_sigint(fdmnt);
294         if (!do_not_background) {
295                 if (daemon(0, 0) < 0) {
296                         fprintf(stderr, "ERROR, backgrounding failed: %s\n",
297                                 strerror(errno));
298                         goto leave_with_error;
299                 }
300         }
301
302         start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
303         start_args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
304         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
305         if (do_not_background) {
306                 if (ret) {
307                         fprintf(stderr,
308                                 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s",
309                                 path, strerror(errno));
310                         if (start_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
311                                 fprintf(stderr, ", %s\n",
312                                         replace_dev_result2string(start_args.result));
313                         else
314                                 fprintf(stderr, "\n");
315
316                         if (errno == EOPNOTSUPP)
317                                 fprintf(stderr,
318                                         "WARNING: dev_replace does not yet handle RAID5/6\n");
319
320                         goto leave_with_error;
321                 }
322
323                 if (start_args.result !=
324                     BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
325                         fprintf(stderr,
326                                 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
327                                 path,
328                                 replace_dev_result2string(start_args.result));
329                         goto leave_with_error;
330                 }
331         }
332         close_file_or_dir(fdmnt, dirstream);
333         return 0;
334
335 leave_with_error:
336         if (dstdev)
337                 free(dstdev);
338         if (fdmnt != -1)
339                 close(fdmnt);
340         if (fddstdev != -1)
341                 close(fddstdev);
342         return 1;
343 }
344
345 static const char *const cmd_replace_status_usage[] = {
346         "btrfs replace status [-1] <mount_point>",
347         "Print status and progress information of a running device replace",
348         "operation",
349         "",
350         "-1     print once instead of print continuously until the replace",
351         "       operation finishes (or is canceled)",
352         NULL
353 };
354
355 static int cmd_replace_status(int argc, char **argv)
356 {
357         int fd;
358         int e;
359         int c;
360         char *path;
361         int once = 0;
362         int ret;
363         DIR *dirstream = NULL;
364
365         while ((c = getopt(argc, argv, "1")) != -1) {
366                 switch (c) {
367                 case '1':
368                         once = 1;
369                         break;
370                 case '?':
371                 default:
372                         usage(cmd_replace_status_usage);
373                 }
374         }
375
376         if (check_argc_exact(argc - optind, 1))
377                 usage(cmd_replace_status_usage);
378
379         path = argv[optind];
380         fd = open_file_or_dir(path, &dirstream);
381         e = errno;
382         if (fd < 0) {
383                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
384                         path, strerror(e));
385                 return 1;
386         }
387
388         ret = print_replace_status(fd, path, once);
389         close_file_or_dir(fd, dirstream);
390         return !!ret;
391 }
392
393 static int print_replace_status(int fd, const char *path, int once)
394 {
395         struct btrfs_ioctl_dev_replace_args args = {0};
396         struct btrfs_ioctl_dev_replace_status_params *status;
397         int ret;
398         int prevent_loop = 0;
399         int skip_stats;
400         int num_chars;
401         char string1[80];
402         char string2[80];
403         char string3[80];
404
405         for (;;) {
406                 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
407                 args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
408                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
409                 if (ret) {
410                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s",
411                                 path, strerror(errno));
412                         if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
413                                 fprintf(stderr, ", %s\n",
414                                         replace_dev_result2string(args.result));
415                         else
416                                 fprintf(stderr, "\n");
417                         return ret;
418                 }
419
420                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
421                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
422                                 path,
423                                 replace_dev_result2string(args.result));
424                         return -1;
425                 }
426
427                 status = &args.status;
428
429                 skip_stats = 0;
430                 num_chars = 0;
431                 switch (status->replace_state) {
432                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
433                         num_chars =
434                                 printf("%s done",
435                                        progress2string(string3,
436                                                        sizeof(string3),
437                                                        status->progress_1000));
438                         break;
439                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
440                         prevent_loop = 1;
441                         printf("Started on %s, finished on %s",
442                                time2string(string1, sizeof(string1),
443                                            status->time_started),
444                                time2string(string2, sizeof(string2),
445                                            status->time_stopped));
446                         break;
447                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
448                         prevent_loop = 1;
449                         printf("Started on %s, canceled on %s at %s",
450                                time2string(string1, sizeof(string1),
451                                            status->time_started),
452                                time2string(string2, sizeof(string2),
453                                            status->time_stopped),
454                                progress2string(string3, sizeof(string3),
455                                                status->progress_1000));
456                         break;
457                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
458                         prevent_loop = 1;
459                         printf("Started on %s, suspended on %s at %s",
460                                time2string(string1, sizeof(string1),
461                                            status->time_started),
462                                time2string(string2, sizeof(string2),
463                                            status->time_stopped),
464                                progress2string(string3, sizeof(string3),
465                                                status->progress_1000));
466                         break;
467                 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
468                         prevent_loop = 1;
469                         skip_stats = 1;
470                         printf("Never started");
471                         break;
472                 default:
473                         fprintf(stderr,
474         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" got unknown status: %llu\n",
475                                         path, status->replace_state);
476                         return -EINVAL;
477                 }
478
479                 if (!skip_stats)
480                         num_chars += printf(
481                                 ", %llu write errs, %llu uncorr. read errs",
482                                 (unsigned long long)status->num_write_errors,
483                                 (unsigned long long)
484                                  status->num_uncorrectable_read_errors);
485                 if (once || prevent_loop) {
486                         printf("\n");
487                         break;
488                 }
489
490                 fflush(stdout);
491                 sleep(1);
492                 while (num_chars > 0) {
493                         putchar('\b');
494                         num_chars--;
495                 }
496         }
497
498         return 0;
499 }
500
501 static char *
502 time2string(char *buf, size_t s, __u64 t)
503 {
504         struct tm t_tm;
505         time_t t_time_t;
506
507         t_time_t = (time_t)t;
508         assert((__u64)t_time_t == t);
509         localtime_r(&t_time_t, &t_tm);
510         strftime(buf, s, "%e.%b %T", &t_tm);
511         return buf;
512 }
513
514 static char *
515 progress2string(char *buf, size_t s, int progress_1000)
516 {
517         snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
518         assert(s > 0);
519         buf[s - 1] = '\0';
520         return buf;
521 }
522
523 static const char *const cmd_replace_cancel_usage[] = {
524         "btrfs replace cancel <mount_point>",
525         "Cancel a running device replace operation.",
526         NULL
527 };
528
529 static int cmd_replace_cancel(int argc, char **argv)
530 {
531         struct btrfs_ioctl_dev_replace_args args = {0};
532         int ret;
533         int c;
534         int fd;
535         int e;
536         char *path;
537         DIR *dirstream = NULL;
538
539         while ((c = getopt(argc, argv, "")) != -1) {
540                 switch (c) {
541                 case '?':
542                 default:
543                         usage(cmd_replace_cancel_usage);
544                 }
545         }
546
547         if (check_argc_exact(argc - optind, 1))
548                 usage(cmd_replace_cancel_usage);
549
550         path = argv[optind];
551         fd = open_file_or_dir(path, &dirstream);
552         if (fd < 0) {
553                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
554                         path, strerror(errno));
555                 return 1;
556         }
557
558         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
559         args.result = BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT;
560         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
561         e = errno;
562         close_file_or_dir(fd, dirstream);
563         if (ret) {
564                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s",
565                         path, strerror(e));
566                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_RESULT)
567                         fprintf(stderr, ", %s\n",
568                                 replace_dev_result2string(args.result));
569                 else
570                         fprintf(stderr, "\n");
571                 return 1;
572         }
573         if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
574                 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
575                         path, replace_dev_result2string(args.result));
576                 return 2;
577         }
578         return 0;
579 }
580
581 static const char replace_cmd_group_info[] =
582 "replace a device in the filesystem";
583
584 const struct cmd_group replace_cmd_group = {
585         replace_cmd_group_usage, replace_cmd_group_info, {
586                 { "start", cmd_replace_start, cmd_replace_start_usage, NULL,
587                   0 },
588                 { "status", cmd_replace_status, cmd_replace_status_usage, NULL,
589                   0 },
590                 { "cancel", cmd_replace_cancel, cmd_replace_cancel_usage, NULL,
591                   0 },
592                 NULL_CMD_STRUCT
593         }
594 };
595
596 int cmd_replace(int argc, char **argv)
597 {
598         return handle_command_group(&replace_cmd_group, argc, argv);
599 }