btrfs-progs: show meaningful msgs for replace cmd upon raid56
[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 = NULL;
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                 if (errno == EINVAL)
176                         fprintf(stderr,
177                                 "ERROR: '%s' is not a mounted btrfs device\n",
178                                 path);
179                 else
180                         fprintf(stderr, "ERROR: can't access '%s': %s\n",
181                                 path, strerror(errno));
182                 goto leave_with_error;
183         }
184
185         /* check for possible errors before backgrounding */
186         status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
187         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
188         if (ret) {
189                 fprintf(stderr,
190                         "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
191                         path, strerror(errno),
192                         replace_dev_result2string(status_args.result));
193                 goto leave_with_error;
194         }
195
196         if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
197                 fprintf(stderr,
198                         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
199                         path, replace_dev_result2string(status_args.result));
200                 goto leave_with_error;
201         }
202
203         if (status_args.status.replace_state ==
204             BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
205                 fprintf(stderr,
206                         "ERROR: btrfs replace on \"%s\" already started!\n",
207                         path);
208                 goto leave_with_error;
209         }
210
211         srcdev = argv[optind];
212         dstdev = canonicalize_path(argv[optind + 1]);
213         if (!dstdev) {
214                 fprintf(stderr,
215                         "ERROR: Could not canonicalize path '%s': %s\n",
216                         argv[optind + 1], strerror(errno));
217         }
218
219         if (is_numerical(srcdev)) {
220                 struct btrfs_ioctl_fs_info_args fi_args;
221                 struct btrfs_ioctl_dev_info_args *di_args = NULL;
222
223                 start_args.start.srcdevid = arg_strtou64(srcdev);
224
225                 ret = get_fs_info(path, &fi_args, &di_args);
226                 if (ret) {
227                         fprintf(stderr, "ERROR: getting dev info for devstats failed: "
228                                         "%s\n", strerror(-ret));
229                         free(di_args);
230                         goto leave_with_error;
231                 }
232                 if (!fi_args.num_devices) {
233                         fprintf(stderr, "ERROR: no devices found\n");
234                         free(di_args);
235                         goto leave_with_error;
236                 }
237
238                 for (i = 0; i < fi_args.num_devices; i++)
239                         if (start_args.start.srcdevid == di_args[i].devid)
240                                 break;
241                 free(di_args);
242                 if (i == fi_args.num_devices) {
243                         fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
244                                 srcdev, path);
245                         goto leave_with_error;
246                 }
247         } else {
248                 fdsrcdev = open(srcdev, O_RDWR);
249                 if (fdsrcdev < 0) {
250                         fprintf(stderr, "Error: Unable to open device '%s'\n",
251                                 srcdev);
252                         fprintf(stderr, "\tTry using the devid instead of the path\n");
253                         goto leave_with_error;
254                 }
255                 ret = fstat(fdsrcdev, &st);
256                 if (ret) {
257                         fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
258                         goto leave_with_error;
259                 }
260                 if (!S_ISBLK(st.st_mode)) {
261                         fprintf(stderr, "Error: '%s' is not a block device\n",
262                                 srcdev);
263                         goto leave_with_error;
264                 }
265                 strncpy((char *)start_args.start.srcdev_name, srcdev,
266                         BTRFS_DEVICE_PATH_NAME_MAX);
267                 close(fdsrcdev);
268                 fdsrcdev = -1;
269                 start_args.start.srcdevid = 0;
270         }
271
272         ret = test_dev_for_mkfs(dstdev, force_using_targetdev, estr);
273         if (ret) {
274                 fprintf(stderr, "%s", estr);
275                 goto leave_with_error;
276         }
277         fddstdev = open(dstdev, O_RDWR);
278         if (fddstdev < 0) {
279                 fprintf(stderr, "Unable to open %s\n", dstdev);
280                 goto leave_with_error;
281         }
282         strncpy((char *)start_args.start.tgtdev_name, dstdev,
283                 BTRFS_DEVICE_PATH_NAME_MAX);
284         ret = btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
285                                  &mixed, 0);
286         if (ret)
287                 goto leave_with_error;
288
289         close(fddstdev);
290         fddstdev = -1;
291         free(dstdev);
292         dstdev = NULL;
293
294         dev_replace_handle_sigint(fdmnt);
295         if (!do_not_background) {
296                 if (daemon(0, 0) < 0) {
297                         fprintf(stderr, "ERROR, backgrounding failed: %s\n",
298                                 strerror(errno));
299                         goto leave_with_error;
300                 }
301         }
302
303         start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
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, %s\n",
309                                 path, strerror(errno),
310                                 replace_dev_result2string(start_args.result));
311
312                         if (errno == EOPNOTSUPP)
313                                 fprintf(stderr,
314                                         "WARNING: dev_replace does not yet handle RAID5/6\n");
315
316                         goto leave_with_error;
317                 }
318
319                 if (start_args.result !=
320                     BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
321                         fprintf(stderr,
322                                 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
323                                 path,
324                                 replace_dev_result2string(start_args.result));
325                         goto leave_with_error;
326                 }
327         }
328         close_file_or_dir(fdmnt, dirstream);
329         return 0;
330
331 leave_with_error:
332         if (dstdev)
333                 free(dstdev);
334         if (fdmnt != -1)
335                 close(fdmnt);
336         if (fdsrcdev != -1)
337                 close(fdsrcdev);
338         if (fddstdev != -1)
339                 close(fddstdev);
340         return 1;
341 }
342
343 static const char *const cmd_status_replace_usage[] = {
344         "btrfs replace status [-1] <mount_point>",
345         "Print status and progress information of a running device replace",
346         "operation",
347         "",
348         "-1     print once instead of print continuously until the replace",
349         "       operation finishes (or is canceled)",
350         NULL
351 };
352
353 static int cmd_status_replace(int argc, char **argv)
354 {
355         int fd;
356         int e;
357         int c;
358         char *path;
359         int once = 0;
360         int ret;
361         DIR *dirstream = NULL;
362
363         while ((c = getopt(argc, argv, "1")) != -1) {
364                 switch (c) {
365                 case '1':
366                         once = 1;
367                         break;
368                 case '?':
369                 default:
370                         usage(cmd_status_replace_usage);
371                 }
372         }
373
374         if (check_argc_exact(argc - optind, 1))
375                 usage(cmd_status_replace_usage);
376
377         path = argv[optind];
378         fd = open_file_or_dir(path, &dirstream);
379         e = errno;
380         if (fd < 0) {
381                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
382                         path, strerror(e));
383                 return 1;
384         }
385
386         ret = print_replace_status(fd, path, once);
387         close_file_or_dir(fd, dirstream);
388         return !!ret;
389 }
390
391 static int print_replace_status(int fd, const char *path, int once)
392 {
393         struct btrfs_ioctl_dev_replace_args args = {0};
394         struct btrfs_ioctl_dev_replace_status_params *status;
395         int ret;
396         int prevent_loop = 0;
397         int skip_stats;
398         int num_chars;
399         char string1[80];
400         char string2[80];
401         char string3[80];
402
403         for (;;) {
404                 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
405                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
406                 if (ret) {
407                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
408                                 path, strerror(errno),
409                                 replace_dev_result2string(args.result));
410                         return ret;
411                 }
412
413                 status = &args.status;
414                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
415                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
416                                 path,
417                                 replace_dev_result2string(args.result));
418                         return -1;
419                 }
420
421                 skip_stats = 0;
422                 num_chars = 0;
423                 switch (status->replace_state) {
424                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
425                         num_chars =
426                                 printf("%s done",
427                                        progress2string(string3,
428                                                        sizeof(string3),
429                                                        status->progress_1000));
430                         break;
431                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
432                         prevent_loop = 1;
433                         printf("Started on %s, finished on %s",
434                                time2string(string1, sizeof(string1),
435                                            status->time_started),
436                                time2string(string2, sizeof(string2),
437                                            status->time_stopped));
438                         break;
439                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
440                         prevent_loop = 1;
441                         printf("Started on %s, canceled on %s at %s",
442                                time2string(string1, sizeof(string1),
443                                            status->time_started),
444                                time2string(string2, sizeof(string2),
445                                            status->time_stopped),
446                                progress2string(string3, sizeof(string3),
447                                                status->progress_1000));
448                         break;
449                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
450                         prevent_loop = 1;
451                         printf("Started on %s, suspended on %s at %s",
452                                time2string(string1, sizeof(string1),
453                                            status->time_started),
454                                time2string(string2, sizeof(string2),
455                                            status->time_stopped),
456                                progress2string(string3, sizeof(string3),
457                                                status->progress_1000));
458                         break;
459                 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
460                         prevent_loop = 1;
461                         skip_stats = 1;
462                         printf("Never started");
463                         break;
464                 default:
465                         prevent_loop = 1;
466                         fprintf(stderr,
467                                 "Unknown btrfs dev replace status:%llu",
468                                 status->replace_state);
469                         ret = -EINVAL;
470                         break;
471                 }
472
473                 if (!skip_stats)
474                         num_chars += printf(
475                                 ", %llu write errs, %llu uncorr. read errs",
476                                 (unsigned long long)status->num_write_errors,
477                                 (unsigned long long)
478                                  status->num_uncorrectable_read_errors);
479                 if (once || prevent_loop || ret) {
480                         printf("\n");
481                         return ret;
482                 }
483
484                 fflush(stdout);
485                 sleep(1);
486                 while (num_chars > 0) {
487                         putchar('\b');
488                         num_chars--;
489                 }
490         }
491
492         return 0;
493 }
494
495 static char *
496 time2string(char *buf, size_t s, __u64 t)
497 {
498         struct tm t_tm;
499         time_t t_time_t;
500
501         t_time_t = (time_t)t;
502         assert((__u64)t_time_t == t);
503         localtime_r(&t_time_t, &t_tm);
504         strftime(buf, s, "%e.%b %T", &t_tm);
505         return buf;
506 }
507
508 static char *
509 progress2string(char *buf, size_t s, int progress_1000)
510 {
511         snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
512         assert(s > 0);
513         buf[s - 1] = '\0';
514         return buf;
515 }
516
517 static const char *const cmd_cancel_replace_usage[] = {
518         "btrfs replace cancel <mount_point>",
519         "Cancel a running device replace operation.",
520         NULL
521 };
522
523 static int cmd_cancel_replace(int argc, char **argv)
524 {
525         struct btrfs_ioctl_dev_replace_args args = {0};
526         int ret;
527         int c;
528         int fd;
529         int e;
530         char *path;
531         DIR *dirstream = NULL;
532
533         while ((c = getopt(argc, argv, "")) != -1) {
534                 switch (c) {
535                 case '?':
536                 default:
537                         usage(cmd_cancel_replace_usage);
538                 }
539         }
540
541         if (check_argc_exact(argc - optind, 1))
542                 usage(cmd_cancel_replace_usage);
543
544         path = argv[optind];
545         fd = open_file_or_dir(path, &dirstream);
546         if (fd < 0) {
547                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
548                         path, strerror(errno));
549                 return 1;
550         }
551
552         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
553         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
554         e = errno;
555         close_file_or_dir(fd, dirstream);
556         if (ret) {
557                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
558                         path, strerror(e),
559                         replace_dev_result2string(args.result));
560                 return 1;
561         }
562         if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
563                 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
564                         path, replace_dev_result2string(args.result));
565                 return 2;
566         }
567         return 0;
568 }
569
570 const struct cmd_group replace_cmd_group = {
571         replace_cmd_group_usage, NULL, {
572                 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
573                   0 },
574                 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
575                   0 },
576                 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
577                   0 },
578                 NULL_CMD_STRUCT
579         }
580 };
581
582 int cmd_replace(int argc, char **argv)
583 {
584         return handle_command_group(&replace_cmd_group, argc, argv);
585 }