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