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