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