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