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