1 /* media.c -- functions to manage local filesystems */
2
3 /*
4 * This file is part of CliFM
5 *
6 * Copyright (C) 2016-2021, L. Abramovich <johndoe.arch@outlook.com>
7 * All rights reserved.
8
9 * CliFM is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * CliFM is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * MA 02110-1301, USA.
23 */
24
25 #include "helpers.h"
26
27 #include <stdio.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <sys/stat.h>
32 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
33 #include <sys/mount.h>
34 #include <sys/sysctl.h>
35 #endif
36 #include <dirent.h>
37
38 //#include <libudev.h>
39
40 #include "aux.h"
41 #include "readline.h"
42 #include "navigation.h"
43 #include "exec.h"
44 #include "listing.h"
45 #include "jump.h"
46 #include "checks.h"
47 #include "history.h"
48
49 /* Information about devices */
50 struct mnt_t {
51 char *mnt; /* Mountpoint */
52 char *dev; /* Device name (ex: /dev/sda1) */
53 char *label; /* Device label */
54 };
55
56 struct mnt_t *media = (struct mnt_t *)NULL;
57 size_t mp_n = 0;
58
59 /*
60 #ifdef __linux__
61 static struct udev_device*
62 get_child(struct udev* udev, struct udev_device* parent, const char* subsystem)
63 {
64 struct udev_device* child = NULL;
65 struct udev_enumerate *enumerate = udev_enumerate_new(udev);
66
67 udev_enumerate_add_match_parent(enumerate, parent);
68 udev_enumerate_add_match_subsystem(enumerate, subsystem);
69 udev_enumerate_scan_devices(enumerate);
70
71 struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
72 struct udev_list_entry *entry;
73
74 udev_list_entry_foreach(entry, devices) {
75 const char *path = udev_list_entry_get_name(entry);
76 child = udev_device_new_from_syspath(udev, path);
77 break;
78 }
79
80 udev_enumerate_unref(enumerate);
81 return child;
82 }
83
84 // Return an array of block devices partitions
85 static char **
86 get_block_devices(void)
87 {
88 struct udev* udev = udev_new();
89 struct udev_enumerate* enumerate = udev_enumerate_new(udev);
90
91 udev_enumerate_add_match_property(enumerate, "DEVTYPE", "partition");
92 udev_enumerate_scan_devices(enumerate);
93
94 struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
95 struct udev_list_entry *entry;
96
97 char **mps = (char **)NULL;
98 size_t n = 0;
99
100 udev_list_entry_foreach(entry, devices) {
101 const char* path = udev_list_entry_get_name(entry);
102 struct udev_device* scsi = udev_device_new_from_syspath(udev, path);
103
104 struct udev_device* block = get_child(udev, scsi, "block");
105
106 const char *dev = udev_device_get_devnode(block);
107 mps = (char **)xrealloc(mps, (n + 2) * sizeof(char *));
108 mps[n++] = savestring(dev, strlen(dev));
109
110 if (block)
111 udev_device_unref(block);
112
113 udev_device_unref(scsi);
114 }
115 mps[n] = (char *)NULL;
116
117 udev_enumerate_unref(enumerate);
118 udev_unref(udev);
119
120 return mps;
121 }
122 #endif // __linux__
123 */
124
125 #ifdef __linux__
126 static char **
get_block_devices(void)127 get_block_devices(void)
128 {
129 struct dirent **blockdev = (struct dirent **)NULL;
130 int block_n = scandir("/dev", &blockdev, NULL, alphasort);
131 if (block_n == - 1)
132 return (char **)NULL;
133
134 char **bd = (char **)NULL;
135 size_t i = 0, n = 0;
136 for (; (int)i < block_n; i++) {
137 #ifndef _DIRENT_HAVE_D_TYPE
138 char bpath[PATH_MAX];
139 snprintf(bpath, PATH_MAX, "/dev/%s", blockdev[i]->d_name);
140 struct stat a;
141 if (stat(bpath, &a) == -1) {
142 free(blockdev[i]);
143 continue;
144 }
145 if (!S_ISBLK(a.st_mode)) {
146 #else
147 if (blockdev[i]->d_type != DT_BLK) {
148 #endif /* !_DIRENT_HAVE_D_TYPE */
149 free(blockdev[i]);
150 continue;
151 }
152
153 char *name = blockdev[i]->d_name;
154
155 /* Skip /dev/ram and /dev/loop devices */
156 if ((*name == 'l' && strncmp(name, "loop", 4) == 0)
157 || (*name == 'r' && strncmp(name, "ram", 3) == 0)) {
158 free(blockdev[i]);
159 continue;
160 }
161
162 /* Get only partition names, normally ending with a number */
163 size_t blen = strlen(name);
164 if (name[blen - 1] < '1' || name[blen - 1] > '9') {
165 free(blockdev[i]);
166 continue;
167 }
168
169 bd = (char **)xrealloc(bd, (n + 2) * sizeof(char *));
170 bd[n] = (char *)xnmalloc(blen + 6, sizeof(char *));
171 sprintf(bd[n], "/dev/%s", name);
172 bd[++n] = (char *)NULL;
173 free(blockdev[i]);
174 }
175
176 free(blockdev);
177 return bd;
178 }
179
180 static int
181 unmount_dev(size_t i, const int n)
182 {
183 if (xargs.mount_cmd == UNSET) {
184 fprintf(stderr, "%s: No mount application found. Install either "
185 "udevil or udisks2\n", PROGRAM_NAME);
186 return EXIT_FAILURE;
187 }
188
189 if ((unsigned int)n + (unsigned int)1 < (unsigned int)1 || n + 1 > (int)i) {
190 fprintf(stderr, _("%s: %d: Invalid ELN\n"), PROGRAM_NAME, n + 1);
191 return EXIT_FAILURE;
192 }
193
194 char *mnt = media[n].mnt;
195 int exit_status = EXIT_SUCCESS;
196
197 /* Get out of mountpoint before unmounting */
198 size_t mlen = strlen(mnt);
199 if (strncmp(mnt, ws[cur_ws].path, mlen) == 0) {
200 char *cmd[] = {"b", NULL};
201 if (back_function(cmd) == EXIT_FAILURE)
202 cd_function(NULL, CD_PRINT_ERROR);
203 exit_status = (-1);
204 }
205
206 char *cmd[] = {xargs.mount_cmd == MNT_UDISKS2 ? "udisksctl" : "udevil",
207 "unmount", "-b", media[n].dev, NULL};
208 if (launch_execve(cmd, FOREGROUND, E_NOFLAG) != EXIT_SUCCESS)
209 exit_status = EXIT_FAILURE;
210
211 if (xargs.mount_cmd == MNT_UDEVIL)
212 printf(_("%s: Unmounted %s\n"), PROGRAM_NAME, media[n].dev);
213 return exit_status;
214 }
215
216 static char *
217 get_dev_label(void)
218 {
219 #define DISK_LABELS_PATH "/dev/disk/by-label"
220
221 size_t n = mp_n;
222 struct dirent **labels = (struct dirent **)NULL;
223 int ln = scandir(DISK_LABELS_PATH, &labels, NULL, alphasort);
224 if (ln == - 1)
225 return (char *)NULL;
226
227 char *label = (char *)NULL;
228 int i = 0;
229 for (; i < ln; i++) {
230 if (label) {
231 free(labels[i]);
232 continue;
233 }
234
235 char *name = labels[i]->d_name;
236 char lpath[PATH_MAX];
237 snprintf(lpath, PATH_MAX, "%s/%s", DISK_LABELS_PATH, name);
238 char *rpath = realpath(lpath, NULL);
239 if (!rpath) {
240 free(labels[i]);
241 continue;
242 }
243
244 int ret = strcmp(rpath, media[n].dev);
245 free(rpath);
246 if (ret == 0) {
247 /* Device label is encoded using hex. Let's decode it */
248 char *p = strchr(name, '\\');
249 if (p && *(p + 1) == 'x') {
250 char pp = 0;
251 pp = (char)(from_hex(*(p + 2)) << 4 | from_hex(*(p + 3)));
252 *p = pp;
253 strcpy(p + 1, p + 4);
254 }
255 label = savestring(name, strlen(name));
256 }
257
258 free(labels[i]);
259 }
260
261 free(labels);
262 return label;
263 }
264
265 static void
266 list_unmounted_devs(void)
267 {
268 size_t k = mp_n;
269 char **unm_devs = (char **)NULL;
270 unm_devs = get_block_devices();
271
272 if (!unm_devs)
273 return;
274
275 printf(_("\n%sUnmounted devices%s\n\n"), BOLD, df_c);
276 int i = 0;
277 for (; unm_devs[i]; i++) {
278 int skip = 0;
279 size_t j = 0;
280 // Skip already mounted devices
281 for (; j < k; j++) {
282 if (strcmp(media[j].dev, unm_devs[i]) == 0)
283 skip = 1;
284 }
285 if (skip) {
286 free(unm_devs[i]);
287 continue;
288 }
289
290 media = (struct mnt_t *)xrealloc(media,
291 (mp_n + 2) * sizeof(struct mnt_t));
292 media[mp_n].dev = savestring(unm_devs[i], strlen(unm_devs[i]));
293 media[mp_n].mnt = (char *)NULL;
294
295 media[mp_n].label = get_dev_label();
296
297 if (media[mp_n].label)
298 printf("%s%zu %s%s [%s%s%s]\n", el_c, mp_n + 1, df_c,
299 media[mp_n].dev, mi_c, media[mp_n].label, df_c);
300 else
301 printf("%s%zu %s%s\n", el_c, mp_n + 1, df_c, media[mp_n].dev);
302 mp_n++;
303 free(unm_devs[i]);
304 }
305 free(unm_devs);
306
307 media[mp_n].dev = (char *)NULL;
308 media[mp_n].mnt = (char *)NULL;
309 media[mp_n].label = (char *)NULL;
310 }
311 #endif /* __linux__ */
312
313 static int
314 list_mounted_devs(int mode)
315 {
316 FILE *mp_fp = fopen("/proc/mounts", "r");
317 if (!mp_fp) {
318 fprintf(stderr, "%s: mp: fopen: /proc/mounts: %s\n",
319 PROGRAM_NAME, strerror(errno));
320 return EXIT_FAILURE;
321 }
322
323 size_t line_size = 0;
324 char *line = (char *)NULL;
325
326 while (getline(&line, &line_size, mp_fp) > 0) {
327 /* Do not list all mountpoints, but only those corresponding
328 * to a block device (/dev) */
329 if (strncmp(line, "/dev/", 5) == 0) {
330 char *str = (char *)NULL;
331 size_t counter = 0;
332
333 /* use strtok() to split LINE into tokens using space as
334 * IFS */
335 str = strtok(line, " ");
336 size_t dev_len = strlen(str);
337
338 media = (struct mnt_t *)xrealloc(media,
339 (mp_n + 2) * sizeof(struct mnt_t));
340 media[mp_n].dev = savestring(str, dev_len);
341 media[mp_n].label = (char *)NULL;
342 /* Print only the first two fileds of each /proc/mounts
343 * line */
344 while (str && counter < 2) {
345 if (counter == 1) { /* 1 == second field */
346 /* /proc/mounts encode special chars as octal.
347 * Let's decode it */
348 char *p = strchr(str, '\\');
349 if (p && *(p + 1)) {
350 char *q = p++;
351 char pp = 0;
352 p += 3;
353 pp = *p;
354 *p = '\0';
355 int d = read_octal(q + 1);
356 *p = pp;
357 *q = (char)d;
358 strcpy(q + 1, q + 4);
359 }
360
361 if (mode == MEDIA_LIST) {
362 printf("%s%zu%s %s%s%s [%s]\n", el_c, mp_n + 1,
363 df_c, (access(str, R_OK | X_OK) == 0) ? di_c : nd_c,
364 str, df_c, media[mp_n].dev);
365 } else {
366 printf("%s%zu%s %s [%s%s%s]\n", el_c, mp_n + 1,
367 df_c, media[mp_n].dev,
368 (access(str, R_OK | X_OK) == 0) ? di_c
369 : nd_c, str, df_c);
370 }
371
372 /* Store the second field (mountpoint) into an
373 * array */
374 media[mp_n++].mnt = savestring(str, strlen(str));
375 }
376
377 str = strtok(NULL, " ,");
378 counter++;
379 }
380 }
381 }
382
383 free(line);
384 line = (char *)NULL;
385 fclose(mp_fp);
386
387 media[mp_n].dev = (char *)NULL;
388 media[mp_n].mnt = (char *)NULL;
389 media[mp_n].label = (char *)NULL;
390
391 return EXIT_SUCCESS;
392 }
393
394 /* Mount device and store mountpoint */
395 static int
396 mount_dev(int n)
397 {
398 if (xargs.mount_cmd == UNSET) {
399 fprintf(stderr, "%s: No mount application found. Install either "
400 "udevil or udisks2\n", PROGRAM_NAME);
401 return EXIT_FAILURE;
402 }
403
404 char file[PATH_MAX];
405 snprintf(file, PATH_MAX, "%s/clifm.XXXXXX", P_tmpdir);
406
407 int fd = mkstemp(file);
408 if (fd == -1)
409 return EXIT_FAILURE;
410
411 int stdout_bk = dup(STDOUT_FILENO); /* Save original stdout */
412 dup2(fd, STDOUT_FILENO); /* Redirect stdout to the desired file */
413 close(fd);
414
415 if (xargs.mount_cmd == MNT_UDISKS2) {
416 char *cmd[] = {"udisksctl", "mount", "-b", media[n].dev, NULL};
417 launch_execve(cmd, FOREGROUND, E_NOFLAG);
418 } else {
419 char *cmd[] = {"udevil", "mount", media[n].dev, NULL};
420 launch_execve(cmd, FOREGROUND, E_NOFLAG);
421 }
422
423 dup2(stdout_bk, STDOUT_FILENO); /* Restore original stdout */
424 close(stdout_bk);
425
426 FILE *fp = open_fstream_r(file, &fd);
427 if (!fp) {
428 unlink(file);
429 return EXIT_FAILURE;
430 }
431
432 char out_line[PATH_MAX];
433 if (fgets(out_line, (int)sizeof(out_line), fp) == NULL) {
434 close_fstream(fp, fd);
435 unlink(file);
436 fprintf(stderr, _("%s: Error getting output from mount command\n"), PROGRAM_NAME);
437 return EXIT_FAILURE;
438 }
439
440 close_fstream(fp, fd);
441 unlink(file);
442
443 /* Recover the mountpoint used by the mounting command */
444 char *p = strstr(out_line, " at ");
445 if (!p || *(p + 4) != '/') {
446 fprintf(stderr, _("%s: Error retrieving mountpoint\n"), PROGRAM_NAME);
447 return EXIT_FAILURE;
448 }
449 p += 4;
450
451 size_t plen = strlen(p);
452 if (p[plen - 1] == '\n')
453 p[plen - 1] = '\0';
454
455 media[n].mnt = savestring(p, strlen(p));
456
457 return EXIT_SUCCESS;
458 }
459
460 static void
461 free_media(void)
462 {
463 int i = (int)mp_n;
464 while (--i >= 0) {
465 free(media[i].mnt);
466 free(media[i].dev);
467 free(media[i].label);
468 }
469 free(media);
470 media = (struct mnt_t *)NULL;
471 }
472
473 /* If MODE is MEDIA_MOUNT (used by the 'media' command) list mounted and
474 * unmounted devices allowing the user to mount or unmount any of them.
475 * If MODE is rather MEDIA_LIST (used by the 'mp' command), just list
476 * available mountpoints and allow the user to cd into the selected one */
477 int
478 media_menu(int mode)
479 {
480 #if defined(__HAIKU__)
481 fprintf(stderr, _("%s: %s: This feature is not available on Haiku\n"),
482 PROGRAM_NAME, mode == MEDIA_LIST ? _("Mountpoints") : _("Media"));
483 return EXIT_FAILURE;
484 #endif
485
486 #ifndef __linux__
487 if (mode == MEDIA_MOUNT) {
488 fprintf(stderr, _("%s: media: Function only available on Linux "
489 "systems\n"), PROGRAM_NAME);
490 return EXIT_FAILURE;
491 }
492 #endif
493
494 if (mode == MEDIA_MOUNT && xargs.mount_cmd == UNSET) {
495 fprintf(stderr, _("%s: No mount command found. Install either"
496 "udisks2 or udevil\n"), PROGRAM_NAME);
497 return EXIT_FAILURE;
498 }
499
500 #ifdef __linux__
501 printf("%s%s%s\n\n", BOLD, mode == MEDIA_LIST ? _("Mountpoints")
502 : _("Mounted devices"), df_c);
503 #else
504 printf(_("%sMountpoints%s\n\n"), BOLD, df_c);
505 #endif
506
507 media = (struct mnt_t *)xnmalloc(1, sizeof(struct mnt_t));
508 mp_n = 0;
509 int exit_status = EXIT_SUCCESS;
510
511 #ifdef __linux__
512 if (list_mounted_devs(mode) == EXIT_FAILURE) {
513 free(media);
514 media = (struct mnt_t *)NULL;
515 return EXIT_FAILURE;
516 }
517
518 size_t k = mp_n;
519
520 if (mode == MEDIA_MOUNT)
521 list_unmounted_devs();
522
523 #elif defined(__FreeBSD__) || defined(__OpenBSD__)
524 struct statfs *fslist;
525 mp_n = (size_t)getmntinfo(&fslist, MNT_NOWAIT);
526 #elif defined(__NetBSD__)
527 struct statvfs *fslist;
528 mp_n = (size_t)getmntinfo(&fslist, MNT_NOWAIT);
529 #endif /* __linux__ */
530
531 /* This should never happen: There should always be a mountpoint,
532 * at least "/" */
533 // cppcheck-suppress knownConditionTrueFalse
534 if (mp_n == 0) {
535 #ifdef __linux__
536 printf(_("%s: There are no available %s\n"), mode == MEDIA_LIST ? "mp"
537 : "media", mode == MEDIA_LIST ? _("mountpoints") : _("devices"));
538 #else
539 fputs(_("mp: There are no available mountpoints\n"), stdout);
540 #endif
541 return EXIT_SUCCESS;
542 }
543
544 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
545 int i, j;
546 for (i = j = 0; i < (int)mp_n; i++) {
547 /* Do not list all mountpoints, but only those corresponding
548 * to a block device (/dev) */
549 if (strncmp(fslist[i].f_mntfromname, "/dev/", 5) == 0) {
550 printf("%s%d%s %s%s%s (%s)\n", el_c, j + 1, df_c,
551 (access(fslist[i].f_mntonname, R_OK | X_OK) == 0)
552 ? di_c : nd_c, fslist[i].f_mntonname,
553 df_c, fslist[i].f_mntfromname);
554 /* Store the mountpoint into the mounpoints struct */
555 media = (struct mnt_t *)xrealloc(media,
556 (j + 2) * sizeof(struct mnt_t));
557 media[j].mnt = savestring(fslist[i].f_mntonname,
558 strlen(fslist[i].f_mntonname));
559 media[j].label = (char *)NULL;
560 media[j++].dev = (char *)NULL;
561 }
562 }
563
564 media[j].dev = (char *)NULL;
565 media[j].mnt = (char *)NULL;
566 media[j].label = (char *)NULL;
567 /* Update filesystem counter as it would be used to free() the
568 * mountpoints entries later (below) */
569 mp_n = (size_t)j;
570 #endif
571
572 putchar('\n');
573 int n = -1;
574 /* Ask the user and mount/unmount or chdir into the selected
575 * device/mountpoint */
576 puts(_("Enter 'q' to quit"));
577
578 char *input = (char *)NULL;
579 while (!input) {
580 #ifdef __linux__
581 if (mode == MEDIA_LIST)
582 input = rl_no_hist(_("Choose a mountpoint: "));
583 else
584 input = rl_no_hist(_("Choose a mountpoint/device: "));
585 #else
586 input = rl_no_hist(_("Choose a mountpoint: "));
587 #endif
588 if (!input)
589 continue;
590
591 if (!*input) {
592 free(input);
593 input = (char *)NULL;
594 continue;
595 }
596
597 if (*input == 'q' && *(input + 1) == '\0')
598 goto EXIT;
599
600 int atoi_num = atoi(input);
601 if (atoi_num <= 0 || atoi_num > (int)mp_n) {
602 fprintf(stderr, "%s: %s: Invalid ELN\n", PROGRAM_NAME, input);
603 free(input);
604 input = (char *)NULL;
605 continue;
606 }
607
608 n = atoi_num - 1;
609 }
610
611 if (n == -1)
612 goto EXIT;
613
614 #ifdef __linux__
615 if (mode == MEDIA_MOUNT) {
616 if (!media[n].mnt) {
617 /* The device is unmounted: mount it */
618 if (mount_dev(n) == EXIT_FAILURE) {
619 exit_status = EXIT_FAILURE;
620 goto EXIT;
621 }
622 } else {
623 /* The device is mounted: unmount it */
624 int ret = unmount_dev(k, n);
625 if (ret == EXIT_FAILURE)
626 exit_status = EXIT_FAILURE;
627 goto EXIT;
628 }
629 }
630 #endif /* __linux__ */
631
632 if (xchdir(media[n].mnt, SET_TITLE) != EXIT_SUCCESS) {
633 fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, media[n].mnt, strerror(errno));
634 exit_status = EXIT_FAILURE;
635 goto EXIT;
636 }
637
638 free(ws[cur_ws].path);
639 ws[cur_ws].path = savestring(media[n].mnt, strlen(media[n].mnt));
640
641 if (autols) {
642 free_dirlist();
643 if (list_dir() != EXIT_SUCCESS)
644 exit_status = EXIT_FAILURE;
645 }
646
647 add_to_dirhist(ws[cur_ws].path);
648 add_to_jumpdb(ws[cur_ws].path);
649
650 EXIT:
651 free(input);
652 free_media();
653 return exit_status;
654 }
655