xref: /haiku/src/bin/mountvolume.cpp (revision 73ad2473e7874b3702cf5b0fdf4c81b747812ed9)
1 /*
2  * Copyright 2005-2007 Ingo Weinhold, bonefish@users.sf.net
3  * Copyright 2005-2013 Axel Dörfler, axeld@pinc-software.de
4  * Copyright 2009 Jonas Sundström, jonas@kirilla.se
5  *
6  * All rights reserved. Distributed under the terms of the MIT License.
7  */
8 
9 
10 #include <set>
11 #include <string>
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <termios.h>
17 
18 #include <Application.h>
19 #include <Path.h>
20 #include <String.h>
21 #include <fs_volume.h>
22 
23 #include <DiskDevice.h>
24 #include <DiskDevicePrivate.h>
25 #include <DiskDeviceRoster.h>
26 #include <DiskDeviceTypes.h>
27 #include <DiskDeviceList.h>
28 #include <Partition.h>
29 
30 #include <tracker_private.h>
31 
32 
33 using std::set;
34 using std::string;
35 
36 extern const char* __progname;
37 
38 
39 typedef set<string> StringSet;
40 
41 // usage
42 static const char* kUsage =
43 	"Usage: %s <options> [ <volume name> ... ]\n\n"
44 	"Mounts the volume with name <volume name>, if given. Lists info about\n"
45 	"mounted and mountable volumes and mounts/unmounts volumes.\n"
46 	"\n"
47 	"The terminology is actually not quite correct: By volumes only partitions\n"
48 	"living on disk devices are meant.\n"
49 	"\n"
50 	"Options:\n"
51 	"[general]\n"
52 	"  -s                    - silent; don't print info about (un)mounting\n"
53 	"  -h, --help            - print this info text\n"
54 	"\n"
55 	"[mounting]\n"
56 	"  -all                  - mount all mountable volumes\n"
57 	"  -allbfs               - mount all mountable BFS volumes\n"
58 	"  -allhfs               - mount all mountable HFS volumes\n"
59 	"  -alldos               - mount all mountable DOS volumes\n"
60 	"  -ro, -readonly        - mount volumes read-only\n"
61 	"  -u, -unmount <volume> - unmount the volume with the name <volume>\n"
62 	"  -open                 - opens the mounted volumes in Tracker\n"
63 	"\n"
64 	"[info]\n"
65 	"  -p, -l                - list all mounted and mountable volumes\n"
66 	"  -lh                   - list all existing volumes (incl. not-mountable "
67 		"ones)\n"
68 	"  -dd                   - list all disk existing devices\n"
69 	"\n"
70 	"[obsolete]\n"
71 	"  -r                    - ignored\n"
72 	"  -publishall           - ignored\n"
73 	"  -publishbfs           - ignored\n"
74 	"  -publishhfs           - ignored\n"
75 	"  -publishdos           - ignored\n";
76 
77 
78 const char* kAppName = __progname;
79 
80 static int sVolumeNameWidth = B_OS_NAME_LENGTH;
81 static int sFSNameWidth = 25;
82 
83 
84 static void
85 print_usage(bool error)
86 {
87 	fprintf(error ? stderr : stdout, kUsage, kAppName);
88 }
89 
90 
91 static void
92 print_usage_and_exit(bool error)
93 {
94 	print_usage(error);
95 	exit(error ? 0 : 1);
96 }
97 
98 
99 static const char*
100 size_string(int64 size)
101 {
102 	double blocks = size;
103 	static char string[64];
104 
105 	if (size < 1024)
106 		sprintf(string, "%" B_PRId64, size);
107 	else {
108 		const char* units[] = {"K", "M", "G", NULL};
109 		int32 i = -1;
110 
111 		do {
112 			blocks /= 1024.0;
113 			i++;
114 		} while (blocks >= 1024 && units[i + 1]);
115 
116 		snprintf(string, sizeof(string), "%.1f%s", blocks, units[i]);
117 	}
118 
119 	return string;
120 }
121 
122 
123 static status_t
124 open_in_tracker(BPartition* partition)
125 {
126 	BPath mountPoint;
127 	status_t status = partition->GetMountPoint(&mountPoint);
128 	if (status != B_OK)
129 		return status;
130 
131 	entry_ref ref;
132 	status = get_ref_for_path(mountPoint.Path(), &ref);
133 	if (status != B_OK)
134 		return status;
135 
136 	BMessage refs(B_REFS_RECEIVED);
137 	refs.AddRef("refs", &ref);
138 	return BMessenger(kTrackerSignature).SendMessage(&refs);
139 }
140 
141 
142 //	#pragma mark -
143 
144 
145 struct MountVisitor : public BDiskDeviceVisitor {
146 	MountVisitor()
147 		:
148 		silent(false),
149 		mountAll(false),
150 		mountBFS(false),
151 		mountHFS(false),
152 		mountDOS(false),
153 		readOnly(false),
154 		openInTracker(false)
155 	{
156 	}
157 
158 	virtual bool Visit(BDiskDevice* device)
159 	{
160 		return Visit(device, 0);
161 	}
162 
163 	virtual bool Visit(BPartition* partition, int32 level)
164 	{
165 		// get name and type
166 		const char* name = partition->ContentName();
167 		if (!name)
168 			name = partition->Name();
169 		const char* type = partition->ContentType();
170 
171 		// check whether to mount
172 		bool mount = false;
173 		if (name && toMount.find(name) != toMount.end()) {
174 			toMount.erase(name);
175 			if (!partition->IsMounted())
176 				mount = true;
177 			else if (!silent)
178 				fprintf(stderr, "Volume `%s' already mounted.\n", name);
179 		} else if (mountAll) {
180 			mount = true;
181 		} else if (mountBFS && type != NULL
182 			&& strcmp(type, kPartitionTypeBFS) == 0) {
183 			mount = true;
184 		} else if (mountHFS && type != NULL
185 			&& strcmp(type, kPartitionTypeHFS) == 0) {
186 			mount = true;
187 		} else if (mountDOS && type != NULL
188 			&& (strcmp(type, kPartitionTypeFAT12) == 0
189 				|| strcmp(type, kPartitionTypeFAT32) == 0)) {
190 			mount = true;
191 		}
192 
193 		// don't try to mount a partition twice
194 		if (partition->IsMounted())
195 			mount = false;
196 
197 		// check whether to unmount
198 		bool unmount = false;
199 		if (name && toUnmount.find(name) != toUnmount.end()) {
200 			toUnmount.erase(name);
201 			if (partition->IsMounted()) {
202 				unmount = true;
203 				mount = false;
204 			} else if (!silent)
205 				fprintf(stderr, "Volume `%s' not mounted.\n", name);
206 		}
207 
208 		// mount/unmount
209 		if (mount) {
210 			status_t error = partition->Mount(NULL,
211 				readOnly ? B_MOUNT_READ_ONLY : 0);
212 			if (!silent) {
213 				if (error >= B_OK) {
214 					BPath mountPoint;
215 					partition->GetMountPoint(&mountPoint);
216 					printf("Volume `%s' mounted successfully at '%s'.\n", name,
217 						mountPoint.Path());
218 				} else {
219 					fprintf(stderr, "Failed to mount volume `%s': %s\n",
220 						name, strerror(error));
221 				}
222 			}
223 			if (openInTracker && error == B_OK)
224 				open_in_tracker(partition);
225 		} else if (unmount) {
226 			status_t error = partition->Unmount();
227 			if (!silent) {
228 				if (error == B_OK) {
229 					printf("Volume `%s' unmounted successfully.\n", name);
230 				} else {
231 					fprintf(stderr, "Failed to unmount volume `%s': %s\n",
232 						name, strerror(error));
233 				}
234 			}
235 		}
236 
237 		return false;
238 	}
239 
240 	bool		silent;
241 	StringSet	toMount;
242 	StringSet	toUnmount;
243 	bool		mountAll;
244 	bool		mountBFS;
245 	bool		mountHFS;
246 	bool		mountDOS;
247 	bool		readOnly;
248 	bool		openInTracker;
249 };
250 
251 
252 struct PrintPartitionsVisitor : public BDiskDeviceVisitor {
253 	PrintPartitionsVisitor()
254 		: listMountablePartitions(false),
255 		  listAllPartitions(false)
256 	{
257 	}
258 
259 	bool IsUsed()
260 	{
261 		return listMountablePartitions || listAllPartitions;
262 	}
263 
264 	virtual bool Visit(BDiskDevice* device)
265 	{
266 		return Visit(device, 0);
267 	}
268 
269 	virtual bool Visit(BPartition* partition, int32 level)
270 	{
271 		// get name and type
272 		const char* name = partition->ContentName();
273 		if (name == NULL || name[0] == '\0') {
274 			name = partition->Name();
275 			if (name == NULL || name[0] == '\0') {
276 				if (partition->ContainsFileSystem())
277 					name = "<unnamed>";
278 				else
279 					name = "";
280 			}
281 		}
282 		const char* type = partition->ContentType();
283 		if (type == NULL)
284 			type = "<unknown>";
285 
286 		// shorten known types for display
287 		if (!strcmp(type, kPartitionTypeMultisession))
288 			type = "Multisession";
289 		else if (!strcmp(type, kPartitionTypeIntelExtended))
290 			type = "Intel Extended";
291 
292 		BPath path;
293 		partition->GetPath(&path);
294 
295 		// cut off beginning of the device path (if /dev/disk/)
296 		int32 skip = strlen("/dev/disk/");
297 		if (strncmp(path.Path(), "/dev/disk/", skip))
298 			skip = 0;
299 
300 		BPath mountPoint;
301 		if (partition->IsMounted())
302 			partition->GetMountPoint(&mountPoint);
303 
304 		printf("%-*s %-*s %8s %s%s(%s)\n", sVolumeNameWidth, name,
305 			sFSNameWidth, type, size_string(partition->Size()),
306 			partition->IsMounted() ? mountPoint.Path() : "",
307 			partition->IsMounted() ? "  " : "",
308 			path.Path() + skip);
309 		return false;
310 	}
311 
312 	bool listMountablePartitions;
313 	bool listAllPartitions;
314 };
315 
316 
317 //	#pragma mark -
318 
319 
320 class MountVolume : public BApplication {
321 public:
322 						MountVolume();
323 	virtual				~MountVolume();
324 
325 	virtual	void		RefsReceived(BMessage* message);
326 	virtual	void		ArgvReceived(int32 argc, char** argv);
327 	virtual	void		ReadyToRun();
328 };
329 
330 
331 MountVolume::MountVolume()
332 	:
333 	BApplication("application/x-vnd.haiku-mountvolume")
334 {
335 }
336 
337 
338 MountVolume::~MountVolume()
339 {
340 }
341 
342 
343 void
344 MountVolume::RefsReceived(BMessage* message)
345 {
346 	status_t status;
347 	int32 refCount;
348 	type_code typeFound;
349 
350 	status = message->GetInfo("refs", &typeFound, &refCount);
351 	if (status != B_OK || refCount < 1) {
352 		fprintf(stderr, "Failed to get info from entry_refs BMessage: %s\n",
353 			strerror(status));
354 		exit(1);
355 	}
356 
357 	entry_ref ref;
358 	BPath path;
359 
360 	int32 argc = refCount + 2;
361 	char** argv = new char*[argc + 1];
362 	argv[0] = strdup(kAppName);
363 	argv[1] = strdup("-open");
364 
365 	for (int32 i = 0; i < refCount; i++) {
366 		message->FindRef("refs", i, &ref);
367 		status = path.SetTo(&ref);
368 		if (status != B_OK) {
369 			fprintf(stderr, "Failed to get a path (%s) from entry (%s): %s\n",
370 				path.Path(), ref.name, strerror(status));
371 		}
372 		argv[2 + i] = strdup(path.Path());
373 	}
374 	argv[argc] = NULL;
375 
376 	ArgvReceived(argc, argv);
377 }
378 
379 
380 void
381 MountVolume::ArgvReceived(int32 argc, char** argv)
382 {
383 	MountVisitor mountVisitor;
384 	PrintPartitionsVisitor printPartitionsVisitor;
385 	bool listAllDevices = false;
386 
387 	if (argc < 2)
388 		printPartitionsVisitor.listMountablePartitions = true;
389 
390 	// parse arguments
391 
392 	for (int argi = 1; argi < argc; argi++) {
393 		const char* arg = argv[argi];
394 
395 		if (arg[0] != '\0' && arg[0] != '-') {
396 			mountVisitor.toMount.insert(arg);
397 		} else if (strcmp(arg, "-s") == 0) {
398 			mountVisitor.silent = true;
399 		} else if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
400 			print_usage_and_exit(false);
401 		} else if (strcmp(arg, "-all") == 0) {
402 			mountVisitor.mountAll = true;
403 		} else if (strcmp(arg, "-allbfs") == 0) {
404 			mountVisitor.mountBFS = true;
405 		} else if (strcmp(arg, "-allhfs") == 0) {
406 			mountVisitor.mountHFS = true;
407 		} else if (strcmp(arg, "-alldos") == 0) {
408 			mountVisitor.mountDOS = true;
409 		} else if (strcmp(arg, "-ro") == 0 || strcmp(arg, "-readonly") == 0) {
410 			mountVisitor.readOnly = true;
411 		} else if (strcmp(arg, "-u") == 0 || strcmp(arg, "-unmount") == 0) {
412 			argi++;
413 			if (argi >= argc)
414 				print_usage_and_exit(true);
415 			mountVisitor.toUnmount.insert(argv[argi]);
416 		} else if (strcmp(arg, "-open") == 0) {
417 			mountVisitor.openInTracker = true;
418 		} else if (strcmp(arg, "-p") == 0 || strcmp(arg, "-l") == 0) {
419 			printPartitionsVisitor.listMountablePartitions = true;
420 		} else if (strcmp(arg, "-lh") == 0) {
421 			printPartitionsVisitor.listAllPartitions = true;
422 		} else if (strcmp(arg, "-dd") == 0) {
423 			listAllDevices = true;
424 		} else if (strcmp(arg, "-r") == 0 || strcmp(arg, "-publishall") == 0
425 			|| strcmp(arg, "-publishbfs") == 0
426 			|| strcmp(arg, "-publishhfs") == 0
427 			|| strcmp(arg, "-publishdos") == 0) {
428 			// obsolete: ignore
429 		} else
430 			print_usage_and_exit(true);
431 	}
432 
433 	// get a disk device list
434 	BDiskDeviceList deviceList;
435 	status_t error = deviceList.Fetch();
436 	if (error != B_OK) {
437 		fprintf(stderr, "Failed to get the list of disk devices: %s",
438 			strerror(error));
439 		exit(1);
440 	}
441 
442 	// mount/unmount volumes
443 	deviceList.VisitEachMountablePartition(&mountVisitor);
444 
445 	BDiskDeviceRoster roster;
446 
447 	// try mount file images
448 	for (StringSet::iterator iterator = mountVisitor.toMount.begin();
449 			iterator != mountVisitor.toMount.end();) {
450 		const char* name = (*iterator).c_str();
451 		iterator++;
452 
453 		BEntry entry(name, true);
454 		if (!entry.Exists())
455 			continue;
456 
457 		// TODO: improve error messages
458 		BPath path;
459 		if (entry.GetPath(&path) != B_OK)
460 			continue;
461 
462 		partition_id id = -1;
463 		BDiskDevice device;
464 		BPartition* partition;
465 
466 		if (!strncmp(path.Path(), "/dev/", 5)) {
467 			// seems to be a device path
468 			if (roster.GetPartitionForPath(path.Path(), &device, &partition)
469 					!= B_OK)
470 				continue;
471 		} else {
472 			// a file with this name exists, so try to mount it
473 			id = roster.RegisterFileDevice(path.Path());
474 			if (id < 0)
475 				continue;
476 
477 			if (roster.GetPartitionWithID(id, &device, &partition) != B_OK) {
478 				roster.UnregisterFileDevice(id);
479 				continue;
480 			}
481 		}
482 
483 		status_t status = partition->Mount(NULL,
484 			mountVisitor.readOnly ? B_MOUNT_READ_ONLY : 0);
485 		if (!mountVisitor.silent) {
486 			if (status >= B_OK) {
487 				BPath mountPoint;
488 				partition->GetMountPoint(&mountPoint);
489 				printf("%s \"%s\" mounted successfully at \"%s\".\n",
490 					id < 0 ? "Device" : "Image", name, mountPoint.Path());
491 			}
492 		}
493 		if (status >= B_OK) {
494 			if (mountVisitor.openInTracker)
495 				open_in_tracker(partition);
496 
497 			// remove from list
498 			mountVisitor.toMount.erase(name);
499 		} else if (id >= 0)
500 			roster.UnregisterFileDevice(id);
501 	}
502 
503 	// TODO: support unmounting images by path!
504 
505 	// print errors for the volumes to mount/unmount, that weren't found
506 	if (!mountVisitor.silent) {
507 		for (StringSet::iterator it = mountVisitor.toMount.begin();
508 				it != mountVisitor.toMount.end(); it++) {
509 			fprintf(stderr, "Failed to mount volume `%s': Volume not found.\n",
510 				(*it).c_str());
511 		}
512 		for (StringSet::iterator it = mountVisitor.toUnmount.begin();
513 				it != mountVisitor.toUnmount.end(); it++) {
514 			fprintf(stderr, "Failed to unmount volume `%s': Volume not "
515 				"found.\n", (*it).c_str());
516 		}
517 	}
518 
519 	// update the disk device list
520 	error = deviceList.Fetch();
521 	if (error != B_OK) {
522 		fprintf(stderr, "Failed to update the list of disk devices: %s",
523 			strerror(error));
524 		exit(1);
525 	}
526 
527 	// print information
528 
529 	if (listAllDevices) {
530 		// TODO
531 	}
532 
533 	// determine width of the terminal in order to shrink the columns if needed
534 	if (isatty(STDOUT_FILENO)) {
535 		winsize size;
536 		if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &size, sizeof(winsize)) == 0) {
537 			if (size.ws_col < 95) {
538 				sVolumeNameWidth -= (95 - size.ws_col) / 2;
539 				sFSNameWidth -= (95 - size.ws_col) / 2;
540 			}
541 		}
542 	}
543 
544 	if (printPartitionsVisitor.IsUsed()) {
545 		printf("%-*s %-*s     Size Mounted At (Device)\n",
546 			sVolumeNameWidth, "Volume", sFSNameWidth, "File System");
547 		BString separator;
548 		separator.SetTo('-', sVolumeNameWidth + sFSNameWidth + 35);
549 		puts(separator.String());
550 
551 		if (printPartitionsVisitor.listAllPartitions)
552 			deviceList.VisitEachPartition(&printPartitionsVisitor);
553 		else
554 			deviceList.VisitEachMountablePartition(&printPartitionsVisitor);
555 	}
556 
557 	exit(0);
558 }
559 
560 
561 void
562 MountVolume::ReadyToRun()
563 {
564 	// We will only get here if we were launched without any arguments or
565 	// startup messages
566 
567 	extern int __libc_argc;
568 	extern char** __libc_argv;
569 
570 	ArgvReceived(__libc_argc, __libc_argv);
571 }
572 
573 
574 //	#pragma mark -
575 
576 
577 int
578 main()
579 {
580 	MountVolume mountVolume;
581 	mountVolume.Run();
582 	return 0;
583 }
584 
585