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
33using std::set;
34using std::string;
35
36extern const char* __progname;
37
38
39typedef set<string> StringSet;
40
41// usage
42static 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
78const char* kAppName = __progname;
79
80static int sVolumeNameWidth = B_OS_NAME_LENGTH;
81static int sFSNameWidth = 25;
82
83
84static void
85print_usage(bool error)
86{
87	fprintf(error ? stderr : stdout, kUsage, kAppName);
88}
89
90
91static void
92print_usage_and_exit(bool error)
93{
94	print_usage(error);
95	exit(error ? 0 : 1);
96}
97
98
99static const char*
100size_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
123static status_t
124open_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
145struct 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		BString name = partition->ContentName();
167		if (name.IsEmpty())
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.String()) != toMount.end()) {
174			toMount.erase(name.String());
175			if (!partition->IsMounted())
176				mount = true;
177			else if (!silent)
178				fprintf(stderr, "Volume `%s' already mounted.\n", name.String());
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.String()) != toUnmount.end()) {
200			toUnmount.erase(name.String());
201			if (partition->IsMounted()) {
202				unmount = true;
203				mount = false;
204			} else if (!silent)
205				fprintf(stderr, "Volume `%s' not mounted.\n", name.String());
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.String(),
217						mountPoint.Path());
218				} else {
219					fprintf(stderr, "Failed to mount volume `%s': %s\n",
220						name.String(), 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.String());
230				} else {
231					fprintf(stderr, "Failed to unmount volume `%s': %s\n",
232						name.String(), 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
252struct 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		BString name = partition->ContentName();
273		if (name.IsEmpty()) {
274			name = partition->Name();
275			if (name.IsEmpty()) {
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.String(),
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
320class MountVolume : public BApplication {
321public:
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
331MountVolume::MountVolume()
332	:
333	BApplication("application/x-vnd.haiku-mountvolume")
334{
335}
336
337
338MountVolume::~MountVolume()
339{
340}
341
342
343void
344MountVolume::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
380void
381MountVolume::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
561void
562MountVolume::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
577int
578main()
579{
580	MountVolume mountVolume;
581	mountVolume.Run();
582	return 0;
583}
584
585