btrfs-progs: canonicalize pathnames for device commands
[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                         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         return 0;
325
326 leave_with_error:
327         if (dstdev)
328                 free(dstdev);
329         if (fdmnt != -1)
330                 close(fdmnt);
331         if (fdsrcdev != -1)
332                 close(fdsrcdev);
333         if (fddstdev != -1)
334                 close(fddstdev);
335         return 1;
336 }
337
338 static const char *const cmd_status_replace_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_status_replace(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_status_replace_usage);
366                 }
367         }
368
369         if (check_argc_exact(argc - optind, 1))
370                 usage(cmd_status_replace_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                 ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
401                 if (ret) {
402                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) failed on \"%s\": %s, %s\n",
403                                 path, strerror(errno),
404                                 replace_dev_result2string(args.result));
405                         return ret;
406                 }
407
408                 status = &args.status;
409                 if (args.result != BTRFS_IOCTL_DEV_REPLACE_RESULT_NO_ERROR) {
410                         fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_STATUS) on \"%s\" returns error: %s\n",
411                                 path,
412                                 replace_dev_result2string(args.result));
413                         return -1;
414                 }
415
416                 skip_stats = 0;
417                 num_chars = 0;
418                 switch (status->replace_state) {
419                 case BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED:
420                         num_chars =
421                                 printf("%s done",
422                                        progress2string(string3,
423                                                        sizeof(string3),
424                                                        status->progress_1000));
425                         break;
426                 case BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED:
427                         prevent_loop = 1;
428                         printf("Started on %s, finished on %s",
429                                time2string(string1, sizeof(string1),
430                                            status->time_started),
431                                time2string(string2, sizeof(string2),
432                                            status->time_stopped));
433                         break;
434                 case BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED:
435                         prevent_loop = 1;
436                         printf("Started on %s, canceled on %s at %s",
437                                time2string(string1, sizeof(string1),
438                                            status->time_started),
439                                time2string(string2, sizeof(string2),
440                                            status->time_stopped),
441                                progress2string(string3, sizeof(string3),
442                                                status->progress_1000));
443                         break;
444                 case BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED:
445                         prevent_loop = 1;
446                         printf("Started on %s, suspended on %s at %s",
447                                time2string(string1, sizeof(string1),
448                                            status->time_started),
449                                time2string(string2, sizeof(string2),
450                                            status->time_stopped),
451                                progress2string(string3, sizeof(string3),
452                                                status->progress_1000));
453                         break;
454                 case BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED:
455                         prevent_loop = 1;
456                         skip_stats = 1;
457                         printf("Never started");
458                         break;
459                 default:
460                         prevent_loop = 1;
461                         fprintf(stderr,
462                                 "Unknown btrfs dev replace status:%llu",
463                                 status->replace_state);
464                         ret = -EINVAL;
465                         break;
466                 }
467
468                 if (!skip_stats)
469                         num_chars += printf(
470                                 ", %llu write errs, %llu uncorr. read errs",
471                                 (unsigned long long)status->num_write_errors,
472                                 (unsigned long long)
473                                  status->num_uncorrectable_read_errors);
474                 if (once || prevent_loop || ret) {
475                         printf("\n");
476                         return ret;
477                 }
478
479                 fflush(stdout);
480                 sleep(1);
481                 while (num_chars > 0) {
482                         putchar('\b');
483                         num_chars--;
484                 }
485         }
486
487         return 0;
488 }
489
490 static char *
491 time2string(char *buf, size_t s, __u64 t)
492 {
493         struct tm t_tm;
494         time_t t_time_t;
495
496         t_time_t = (time_t)t;
497         assert((__u64)t_time_t == t);
498         localtime_r(&t_time_t, &t_tm);
499         strftime(buf, s, "%e.%b %T", &t_tm);
500         return buf;
501 }
502
503 static char *
504 progress2string(char *buf, size_t s, int progress_1000)
505 {
506         snprintf(buf, s, "%d.%01d%%", progress_1000 / 10, progress_1000 % 10);
507         assert(s > 0);
508         buf[s - 1] = '\0';
509         return buf;
510 }
511
512 static const char *const cmd_cancel_replace_usage[] = {
513         "btrfs replace cancel <mount_point>",
514         "Cancel a running device replace operation.",
515         NULL
516 };
517
518 static int cmd_cancel_replace(int argc, char **argv)
519 {
520         struct btrfs_ioctl_dev_replace_args args = {0};
521         int ret;
522         int c;
523         int fd;
524         int e;
525         char *path;
526         DIR *dirstream = NULL;
527
528         while ((c = getopt(argc, argv, "")) != -1) {
529                 switch (c) {
530                 case '?':
531                 default:
532                         usage(cmd_cancel_replace_usage);
533                 }
534         }
535
536         if (check_argc_exact(argc - optind, 1))
537                 usage(cmd_cancel_replace_usage);
538
539         path = argv[optind];
540         fd = open_file_or_dir(path, &dirstream);
541         if (fd < 0) {
542                 fprintf(stderr, "ERROR: can't access \"%s\": %s\n",
543                         path, strerror(errno));
544                 return 1;
545         }
546
547         args.cmd = BTRFS_IOCTL_DEV_REPLACE_CMD_CANCEL;
548         ret = ioctl(fd, BTRFS_IOC_DEV_REPLACE, &args);
549         e = errno;
550         close_file_or_dir(fd, dirstream);
551         if (ret) {
552                 fprintf(stderr, "ERROR: ioctl(DEV_REPLACE_CANCEL) failed on \"%s\": %s, %s\n",
553                         path, strerror(e),
554                         replace_dev_result2string(args.result));
555                 return 1;
556         }
557         if (args.result == BTRFS_IOCTL_DEV_REPLACE_RESULT_NOT_STARTED) {
558                 printf("INFO: ioctl(DEV_REPLACE_CANCEL)\"%s\": %s\n",
559                         path, replace_dev_result2string(args.result));
560                 return 2;
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                 NULL_CMD_STRUCT
574         }
575 };
576
577 int cmd_replace(int argc, char **argv)
578 {
579         return handle_command_group(&replace_cmd_group, argc, argv);
580 }