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