4cc32df202d890e1890831175a78c9bec5a1a519
[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         struct btrfs_ioctl_dev_replace_args args = {0};
81
82         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
83         ioctl(dev_replace_cancel_fd, BTRFS_IOC_DEV_REPLACE, &args);
84 }
85
86 static int dev_replace_handle_sigint(int fd)
87 {
88         struct sigaction sa = {
89                 .sa_handler = fd == -1 ? SIG_DFL : dev_replace_sigint_handler
90         };
91
92         dev_replace_cancel_fd = fd;
93         return sigaction(SIGINT, &sa, NULL);
94 }
95
96 static const char *const cmd_start_replace_usage[] = {
97         "btrfs replace start srcdev|devid targetdev [-Bfr] mount_point",
98         "Replace device of a btrfs filesystem.",
99         "On a live filesystem, duplicate the data to the target device which",
100         "is currently stored on the source device. If the source device is not",
101         "available anymore, or if the -r option is set, the data is built",
102         "only using the RAID redundancy mechanisms. After completion of the",
103         "operation, the source device is removed from the filesystem.",
104         "If the srcdev is a numerical value, it is assumed to be the device id",
105         "of the filesystem which is mounted at mount_point, otherwise it is",
106         "the path to the source device. If the source device is disconnected,",
107         "from the system, you have to use the devid parameter format.",
108         "The targetdev needs to be same size or larger than the srcdev.",
109         "",
110         "-r     only read from srcdev if no other zero-defect mirror exists",
111         "       (enable this if your drive has lots of read errors, the access",
112         "       would be very slow)",
113         "-f     force using and overwriting targetdev even if it looks like",
114         "       containing a valid btrfs filesystem. A valid filesystem is",
115         "       assumed if a btrfs superblock is found which contains a",
116         "       correct checksum. Devices which are currently mounted are",
117         "       never allowed to be used as the targetdev",
118         "-B     do not background",
119         NULL
120 };
121
122 static int cmd_start_replace(int argc, char **argv)
123 {
124         struct btrfs_ioctl_dev_replace_args start_args = {0};
125         struct btrfs_ioctl_dev_replace_args status_args = {0};
126         int ret;
127         int i;
128         int c;
129         int fdmnt = -1;
130         int fdsrcdev = -1;
131         int fddstdev = -1;
132         char *path;
133         char *srcdev;
134         char *dstdev;
135         int avoid_reading_from_srcdev = 0;
136         int force_using_targetdev = 0;
137         u64 total_devs = 1;
138         struct btrfs_fs_devices *fs_devices_mnt = NULL;
139         struct stat st;
140         u64 dstdev_block_count;
141         int do_not_background = 0;
142         int mixed = 0;
143
144         while ((c = getopt(argc, argv, "Brf")) != -1) {
145                 switch (c) {
146                 case 'B':
147                         do_not_background = 1;
148                         break;
149                 case 'r':
150                         avoid_reading_from_srcdev = 1;
151                         break;
152                 case 'f':
153                         force_using_targetdev = 1;
154                         break;
155                 case '?':
156                 default:
157                         usage(cmd_start_replace_usage);
158                 }
159         }
160
161         start_args.start.cont_reading_from_srcdev_mode =
162                 avoid_reading_from_srcdev ?
163                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID :
164                  BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS;
165         if (check_argc_exact(argc - optind, 3))
166                 usage(cmd_start_replace_usage);
167         path = argv[optind + 2];
168         fdmnt = open_file_or_dir(path);
169         if (fdmnt < 0) {
170                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
171                         path, strerror(errno));
172                 goto leave_with_error;
173         }
174
175         /* check for possible errors before backgrounding */
176         status_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
177         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &status_args);
178         if (ret) {
179                 fprintf(stderr,
180                         "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
181                         path, strerror(errno),
182                         replace_dev_result2string(status_args.result));
183                 goto leave_with_error;
184         }
185
186         if (status_args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
187                 fprintf(stderr,
188                         "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
189                         path, replace_dev_result2string(status_args.result));
190                 goto leave_with_error;
191         }
192
193         if (status_args.status.replace_state ==
194             BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED) {
195                 fprintf(stderr,
196                         "ERROR: btrfs replace on \"%s\" already started!\n",
197                         path);
198                 goto leave_with_error;
199         }
200
201         srcdev = argv[optind];
202         dstdev = argv[optind + 1];
203
204         if (is_numerical(srcdev)) {
205                 struct btrfs_ioctl_fs_info_args fi_args;
206                 struct btrfs_ioctl_dev_info_args *di_args = NULL;
207
208                 if (atoi(srcdev) == 0) {
209                         fprintf(stderr, "Error: Failed to parse the numerical devid value '%s'\n",
210                                 srcdev);
211                         goto leave_with_error;
212                 }
213                 start_args.start.srcdevid = (__u64)atoi(srcdev);
214
215                 ret = get_fs_info(fdmnt, path, &fi_args, &di_args);
216                 if (ret) {
217                         fprintf(stderr, "ERROR: getting dev info for devstats failed: "
218                                         "%s\n", strerror(-ret));
219                         free(di_args);
220                         goto leave_with_error;
221                 }
222                 if (!fi_args.num_devices) {
223                         fprintf(stderr, "ERROR: no devices found\n");
224                         free(di_args);
225                         goto leave_with_error;
226                 }
227
228                 for (i = 0; i < fi_args.num_devices; i++)
229                         if (start_args.start.srcdevid == di_args[i].devid)
230                                 break;
231                 free(di_args);
232                 if (i == fi_args.num_devices) {
233                         fprintf(stderr, "Error: '%s' is not a valid devid for filesystem '%s'\n",
234                                 srcdev, path);
235                         goto leave_with_error;
236                 }
237         } else {
238                 fdsrcdev = open(srcdev, O_RDWR);
239                 if (fdsrcdev < 0) {
240                         fprintf(stderr, "Error: Unable to open device '%s'\n",
241                                 srcdev);
242                         goto leave_with_error;
243                 }
244                 ret = fstat(fdsrcdev, &st);
245                 if (ret) {
246                         fprintf(stderr, "Error: Unable to stat '%s'\n", srcdev);
247                         goto leave_with_error;
248                 }
249                 if (!S_ISBLK(st.st_mode)) {
250                         fprintf(stderr, "Error: '%s' is not a block device\n",
251                                 srcdev);
252                         goto leave_with_error;
253                 }
254                 strncpy((char *)start_args.start.srcdev_name, srcdev,
255                         BTRFS_DEVICE_PATH_NAME_MAX);
256                 close(fdsrcdev);
257                 fdsrcdev = -1;
258                 start_args.start.srcdevid = 0;
259         }
260
261         ret = check_mounted(dstdev);
262         if (ret < 0) {
263                 fprintf(stderr, "Error checking %s mount status\n", dstdev);
264                 goto leave_with_error;
265         }
266         if (ret == 1) {
267                 fprintf(stderr,
268                         "Error, target device %s is in use and currently mounted!\n",
269                         dstdev);
270                 goto leave_with_error;
271         }
272         fddstdev = open(dstdev, O_RDWR);
273         if (fddstdev < 0) {
274                 fprintf(stderr, "Unable to open %s\n", dstdev);
275                 goto leave_with_error;
276         }
277         ret = btrfs_scan_one_device(fddstdev, dstdev, &fs_devices_mnt,
278                                     &total_devs, BTRFS_SUPER_INFO_OFFSET);
279         if (ret >= 0 && !force_using_targetdev) {
280                 fprintf(stderr,
281                         "Error, target device %s contains filesystem, use '-f' to force overwriting.\n",
282                         dstdev);
283                 goto leave_with_error;
284         }
285         ret = fstat(fddstdev, &st);
286         if (ret) {
287                 fprintf(stderr, "Error: Unable to stat '%s'\n", dstdev);
288                 goto leave_with_error;
289         }
290         if (!S_ISBLK(st.st_mode)) {
291                 fprintf(stderr, "Error: '%s' is not a block device\n", dstdev);
292                 goto leave_with_error;
293         }
294         strncpy((char *)start_args.start.tgtdev_name, dstdev,
295                 BTRFS_DEVICE_PATH_NAME_MAX);
296         if (btrfs_prepare_device(fddstdev, dstdev, 1, &dstdev_block_count, 0,
297                                  &mixed, 0)) {
298                 fprintf(stderr, "Error: Failed to prepare device '%s'\n",
299                         dstdev);
300                 goto leave_with_error;
301         }
302         close(fddstdev);
303         fddstdev = -1;
304
305         dev_replace_handle_sigint(fdmnt);
306         if (!do_not_background) {
307                 if (daemon(0, 0) < 0) {
308                         fprintf(stderr, "ERROR, backgrounding failed: %s\n",
309                                 strerror(errno));
310                         goto leave_with_error;
311                 }
312         }
313
314         start_args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_START;
315         ret = ioctl(fdmnt, BTRFS_IOC_DEV_REPLACE, &start_args);
316         if (do_not_background) {
317                 if (ret) {
318                         fprintf(stderr,
319                                 "ERROR: ioctl(DEV_REPLACE_START) failed on \"%s\": %s, %s\n",
320                                 path, strerror(errno),
321                                 replace_dev_result2string(start_args.result));
322                         goto leave_with_error;
323                 }
324
325                 if (start_args.result !=
326                     BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
327                         fprintf(stderr,
328                                 "ERROR: ioctl(DEV_REPLACE_START) on \"%s\" returns error: %s\n",
329                                 path,
330                                 replace_dev_result2string(start_args.result));
331                         goto leave_with_error;
332                 }
333         }
334         close(fdmnt);
335         return 0;
336
337 leave_with_error:
338         if (fdmnt != -1)
339                 close(fdmnt);
340         if (fdsrcdev != -1)
341                 close(fdsrcdev);
342         if (fddstdev != -1)
343                 close(fddstdev);
344         return -1;
345 }
346
347 static const char *const cmd_status_replace_usage[] = {
348         "btrfs replace status mount_point [-1]",
349         "Print status and progress information of a running device replace",
350         "operation",
351         "",
352         "-1     print once instead of print continously until the replace",
353         "       operation finishes (or is canceled)",
354         NULL
355 };
356
357 static int cmd_status_replace(int argc, char **argv)
358 {
359         int fd;
360         int e;
361         int c;
362         char *path;
363         int once = 0;
364         int ret;
365
366         while ((c = getopt(argc, argv, "1")) != -1) {
367                 switch (c) {
368                 case '1':
369                         once = 1;
370                         break;
371                 case '?':
372                 default:
373                         usage(cmd_status_replace_usage);
374                 }
375         }
376
377         if (check_argc_exact(argc - optind, 1))
378                 usage(cmd_status_replace_usage);
379
380         path = argv[optind];
381         fd = open_file_or_dir(path);
382         e = errno;
383         if (fd < 0) {
384                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
385                         path, strerror(e));
386                 return -1;
387         }
388
389         ret = print_replace_status(fd, path, once);
390         close(fd);
391         return ret;
392 }
393
394 static int print_replace_status(int fd, const char *path, int once)
395 {
396         struct btrfs_ioctl_dev_replace_args args = {0};
397         struct btrfs_ioctl_dev_replace_status_params *status;
398         int ret;
399         int prevent_loop = 0;
400         int skip_stats;
401         int num_chars;
402         char string1[80];
403         char string2[80];
404         char string3[80];
405
406         for (;;) {
407                 args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_STATUS;
408                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
409                 if (ret) {
410                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
411                                 path, strerror(errno),
412                                 replace_dev_result2string(args.result));
413                         return ret;
414                 }
415
416                 status = &args.status;
417                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
418                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
419                                 path,
420                                 replace_dev_result2string(args.result));
421                         return -1;
422                 }
423
424                 skip_stats = 0;
425                 num_chars = 0;
426                 switch (status->replace_state) {
427                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
428                         num_chars =
429                                 printf("%s done",
430                                        progress2string(string3,
431                                                        sizeof(string3),
432                                                        status->progress_1000));
433                         break;
434                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
435                         prevent_loop = 1;
436                         printf("Started on %s, finished on %s",
437                                time2string(string1, sizeof(string1),
438                                            status->time_started),
439                                time2string(string2, sizeof(string2),
440                                            status->time_stopped));
441                         break;
442                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
443                         prevent_loop = 1;
444                         printf("Started on %s, canceled on %s at %s",
445                                time2string(string1, sizeof(string1),
446                                            status->time_started),
447                                time2string(string2, sizeof(string2),
448                                            status->time_stopped),
449                                progress2string(string3, sizeof(string3),
450                                                status->progress_1000));
451                         break;
452                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
453                         prevent_loop = 1;
454                         printf("Started on %s, suspended on %s at %s",
455                                time2string(string1, sizeof(string1),
456                                            status->time_started),
457                                time2string(string2, sizeof(string2),
458                                            status->time_stopped),
459                                progress2string(string3, sizeof(string3),
460                                                status->progress_1000));
461                         break;
462                 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
463                         prevent_loop = 1;
464                         skip_stats = 1;
465                         printf("Never started");
466                         break;
467                 default:
468                         prevent_loop = 1;
469                         assert(0);
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) {
480                         printf("\n");
481                         return 0;
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
532         while ((c = getopt(argc, argv, "")) != -1) {
533                 switch (c) {
534                 case '?':
535                 default:
536                         usage(cmd_cancel_replace_usage);
537                 }
538         }
539
540         if (check_argc_exact(argc - optind, 1))
541                 usage(cmd_cancel_replace_usage);
542
543         path = argv[optind];
544         fd = open_file_or_dir(path);
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         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
553         e = errno;
554         close(fd);
555         if (ret) {
556                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
557                         path, strerror(e),
558                         replace_dev_result2string(args.result));
559                 return ret;
560         }
561
562         return 0;
563 }
564
565 const struct cmd_group replace_cmd_group = {
566         replace_cmd_group_usage, NULL, {
567                 { "start", cmd_start_replace, cmd_start_replace_usage, NULL,
568                   0 },
569                 { "status", cmd_status_replace, cmd_status_replace_usage, NULL,
570                   0 },
571                 { "cancel", cmd_cancel_replace, cmd_cancel_replace_usage, NULL,
572                   0 },
573                 { 0, 0, 0, 0, 0 }
574         }
575 };
576
577 int cmd_replace(int argc, char **argv)
578 {
579         return handle_command_group(&replace_cmd_group, argc, argv);
580 }