1 /* 2 * Copyright 2003-2007, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 #include <DiskDevice.h> 7 8 #include <ctype.h> 9 #include <errno.h> 10 #include <fcntl.h> 11 #include <new> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 #include <unistd.h> 16 17 #include <DiskDevice.h> 18 #include <DiskDeviceVisitor.h> 19 #include <Drivers.h> 20 #include <Message.h> 21 #include <Path.h> 22 23 #include <syscalls.h> 24 #include <ddm_userland_interface_defs.h> 25 26 #include "DiskDeviceJob.h" 27 #include "DiskDeviceJobGenerator.h" 28 #include "DiskDeviceJobQueue.h" 29 #include "DiskSystemAddOnManager.h" 30 31 32 //#define TRACE_DISK_DEVICE 33 #undef TRACE 34 #ifdef TRACE_DISK_DEVICE 35 # define TRACE(x...) printf(x) 36 #else 37 # define TRACE(x...) do {} while (false) 38 #endif 39 40 41 /*! \class BDiskDevice 42 \brief A BDiskDevice object represents a storage device. 43 */ 44 45 46 // constructor 47 /*! \brief Creates an uninitialized BDiskDevice object. 48 */ 49 BDiskDevice::BDiskDevice() 50 : fDeviceData(NULL) 51 { 52 } 53 54 55 // destructor 56 /*! \brief Frees all resources associated with this object. 57 */ 58 BDiskDevice::~BDiskDevice() 59 { 60 CancelModifications(); 61 } 62 63 64 // HasMedia 65 /*! \brief Returns whether the device contains a media. 66 \return \c true, if the device contains a media, \c false otherwise. 67 */ 68 bool 69 BDiskDevice::HasMedia() const 70 { 71 return fDeviceData 72 && (fDeviceData->device_flags & B_DISK_DEVICE_HAS_MEDIA) != 0; 73 } 74 75 76 // IsRemovableMedia 77 /*! \brief Returns whether the device media are removable. 78 \return \c true, if the device media are removable, \c false otherwise. 79 */ 80 bool 81 BDiskDevice::IsRemovableMedia() const 82 { 83 return fDeviceData 84 && (fDeviceData->device_flags & B_DISK_DEVICE_REMOVABLE) != 0; 85 } 86 87 88 // IsReadOnlyMedia 89 bool 90 BDiskDevice::IsReadOnlyMedia() const 91 { 92 return fDeviceData 93 && (fDeviceData->device_flags & B_DISK_DEVICE_READ_ONLY) != 0; 94 } 95 96 97 // IsWriteOnceMedia 98 bool 99 BDiskDevice::IsWriteOnceMedia() const 100 { 101 return fDeviceData 102 && (fDeviceData->device_flags & B_DISK_DEVICE_WRITE_ONCE) != 0; 103 } 104 105 106 // Eject 107 /*! \brief Eject the device's media. 108 109 The device media must, of course, be removable, and the device must 110 support ejecting the media. 111 112 \param update If \c true, Update() is invoked after successful ejection. 113 \return 114 - \c B_OK: Everything went fine. 115 - \c B_NO_INIT: The device is not properly initialized. 116 - \c B_BAD_VALUE: The device media is not removable. 117 - other error codes 118 */ 119 status_t 120 BDiskDevice::Eject(bool update) 121 { 122 if (fDeviceData == NULL) 123 return B_NO_INIT; 124 125 // check whether the device media is removable 126 if (!IsRemovableMedia()) 127 return B_BAD_VALUE; 128 129 // open, eject and close the device 130 int fd = open(fDeviceData->path, O_RDONLY); 131 if (fd < 0) 132 return errno; 133 134 status_t status = B_OK; 135 if (ioctl(fd, B_EJECT_DEVICE) != 0) 136 status = errno; 137 138 close(fd); 139 140 if (status == B_OK && update) 141 status = Update(); 142 143 return status; 144 } 145 146 147 // SetTo 148 status_t 149 BDiskDevice::SetTo(partition_id id) 150 { 151 return _SetTo(id, true, 0); 152 } 153 154 155 // Update 156 /*! \brief Updates the object to reflect the latest changes to the device. 157 158 Note, that subobjects (BSessions, BPartitions) may be deleted during this 159 operation. It is also possible, that the device doesn't exist anymore -- 160 e.g. if it is hot-pluggable. Then an error is returned and the object is 161 uninitialized. 162 163 \param updated Pointer to a bool variable which shall be set to \c true, 164 if the object needed to be updated and to \c false otherwise. 165 May be \c NULL. Is not touched, if the method fails. 166 \return \c B_OK, if the update went fine, another error code otherwise. 167 */ 168 status_t 169 BDiskDevice::Update(bool* updated) 170 { 171 if (InitCheck() != B_OK) 172 return InitCheck(); 173 174 // get the device data 175 user_disk_device_data* data = NULL; 176 status_t error = _GetData(ID(), true, 0, &data); 177 178 // set the data 179 if (error == B_OK) 180 error = _Update(data, updated); 181 182 // cleanup on error 183 if (error != B_OK && data) 184 free(data); 185 186 return error; 187 } 188 189 190 // Unset 191 void 192 BDiskDevice::Unset() 193 { 194 BPartition::_Unset(); 195 free(fDeviceData); 196 fDeviceData = NULL; 197 } 198 199 200 // InitCheck 201 status_t 202 BDiskDevice::InitCheck() const 203 { 204 return fDeviceData ? B_OK : B_NO_INIT; 205 } 206 207 208 // GetPath 209 status_t 210 BDiskDevice::GetPath(BPath* path) const 211 { 212 if (!path || !fDeviceData) 213 return B_BAD_VALUE; 214 return path->SetTo(fDeviceData->path); 215 } 216 217 218 // IsModified 219 bool 220 BDiskDevice::IsModified() const 221 { 222 if (InitCheck() != B_OK) 223 return false; 224 225 struct IsModifiedVisitor : public BDiskDeviceVisitor { 226 virtual bool Visit(BDiskDevice *device) 227 { 228 return Visit(device, 0); 229 } 230 231 virtual bool Visit(BPartition *partition, int32 level) 232 { 233 return partition->_IsModified(); 234 } 235 } visitor; 236 237 return (VisitEachDescendant(&visitor) != NULL); 238 } 239 240 241 // PrepareModifications 242 /*! \brief Initializes the partition hierarchy for modifications. 243 * 244 * Subsequent modifications are performed on so-called \a shadow structure 245 * and not written to device until \ref CommitModifications is called. 246 * 247 * \note This call locks the device. You need to call \ref CommitModifications 248 * or \ref CancelModifications to unlock it. 249 */ 250 status_t 251 BDiskDevice::PrepareModifications() 252 { 253 TRACE("%p->BDiskDevice::PrepareModifications()\n", this); 254 255 // check initialization 256 status_t error = InitCheck(); 257 if (error != B_OK) { 258 TRACE(" InitCheck() failed\n"); 259 return error; 260 } 261 if (fDelegate) { 262 TRACE(" already prepared!\n"); 263 return B_BAD_VALUE; 264 } 265 266 // make sure the disk system add-ons are loaded 267 error = DiskSystemAddOnManager::Default()->LoadDiskSystems(); 268 if (error != B_OK) { 269 TRACE(" failed to load disk systems\n"); 270 return error; 271 } 272 273 // recursively create the delegates 274 error = _CreateDelegates(); 275 276 // init them 277 if (error == B_OK) 278 error = _InitDelegates(); 279 else 280 TRACE(" failed to create delegates\n"); 281 282 // delete all of them, if something went wrong 283 if (error != B_OK) { 284 TRACE(" failed to init delegates\n"); 285 _DeleteDelegates(); 286 DiskSystemAddOnManager::Default()->UnloadDiskSystems(); 287 } 288 289 return error; 290 } 291 292 293 // CommitModifications 294 /*! \brief Commits modifications to device. 295 * 296 * Creates a set of jobs that perform all the changes done after 297 * \ref PrepareModifications. The changes are then written to device. 298 * Deletes and recreates all BPartition children to apply the changes, 299 * so cached pointers to BPartition objects cannot be used after this 300 * call. 301 * Unlocks the device for further use. 302 */ 303 status_t 304 BDiskDevice::CommitModifications(bool synchronously, 305 BMessenger progressMessenger, bool receiveCompleteProgressUpdates) 306 { 307 // TODO: Support parameters! 308 status_t error = InitCheck(); 309 if (error != B_OK) 310 return error; 311 312 if (!fDelegate) 313 return B_BAD_VALUE; 314 315 // generate jobs 316 DiskDeviceJobQueue jobQueue; 317 error = DiskDeviceJobGenerator(this, &jobQueue).GenerateJobs(); 318 319 // do the jobs 320 if (error == B_OK) 321 error = jobQueue.Execute(); 322 323 _DeleteDelegates(); 324 DiskSystemAddOnManager::Default()->UnloadDiskSystems(); 325 326 if (error == B_OK) 327 error = _SetTo(ID(), true, 0); 328 329 return error; 330 } 331 332 333 // CancelModifications 334 /*! \brief Cancels all modifications performed on the device. 335 * 336 * Nothing is written on the device and it is unlocked for further use. 337 */ 338 status_t 339 BDiskDevice::CancelModifications() 340 { 341 status_t error = InitCheck(); 342 if (error != B_OK) 343 return error; 344 345 if (!fDelegate) 346 return B_BAD_VALUE; 347 348 _DeleteDelegates(); 349 DiskSystemAddOnManager::Default()->UnloadDiskSystems(); 350 351 if (error == B_OK) 352 error = _SetTo(ID(), true, 0); 353 354 return error; 355 } 356 357 358 /*! \brief Returns whether or not this device is a virtual device backed 359 up by a file. 360 */ 361 bool 362 BDiskDevice::IsFile() const 363 { 364 return fDeviceData 365 && (fDeviceData->device_flags & B_DISK_DEVICE_IS_FILE) != 0; 366 } 367 368 369 /*! \brief Retrieves the path of the file backing up the disk device.*/ 370 status_t 371 BDiskDevice::GetFilePath(BPath* path) const 372 { 373 if (path == NULL) 374 return B_BAD_VALUE; 375 if (!IsFile()) 376 return B_BAD_TYPE; 377 378 char pathBuffer[B_PATH_NAME_LENGTH]; 379 status_t status = _kern_get_file_disk_device_path( 380 fDeviceData->device_partition_data.id, pathBuffer, sizeof(pathBuffer)); 381 if (status != B_OK) 382 return status; 383 384 return path->SetTo(pathBuffer); 385 } 386 387 388 // copy constructor 389 /*! \brief Privatized copy constructor to avoid usage. 390 */ 391 BDiskDevice::BDiskDevice(const BDiskDevice&) 392 { 393 } 394 395 396 // = 397 /*! \brief Privatized assignment operator to avoid usage. 398 */ 399 BDiskDevice& 400 BDiskDevice::operator=(const BDiskDevice&) 401 { 402 return *this; 403 } 404 405 406 // _GetData 407 status_t 408 BDiskDevice::_GetData(partition_id id, bool deviceOnly, size_t neededSize, 409 user_disk_device_data** data) 410 { 411 // get the device data 412 void* buffer = NULL; 413 size_t bufferSize = 0; 414 if (neededSize > 0) { 415 // allocate initial buffer 416 buffer = malloc(neededSize); 417 if (!buffer) 418 return B_NO_MEMORY; 419 bufferSize = neededSize; 420 } 421 422 status_t error = B_OK; 423 do { 424 error = _kern_get_disk_device_data(id, deviceOnly, 425 (user_disk_device_data*)buffer, bufferSize, &neededSize); 426 if (error == B_BUFFER_OVERFLOW) { 427 // buffer to small re-allocate it 428 if (buffer) 429 free(buffer); 430 431 buffer = malloc(neededSize); 432 433 if (buffer) 434 bufferSize = neededSize; 435 else 436 error = B_NO_MEMORY; 437 } 438 } while (error == B_BUFFER_OVERFLOW); 439 440 // set result / cleanup on error 441 if (error == B_OK) 442 *data = (user_disk_device_data*)buffer; 443 else if (buffer) 444 free(buffer); 445 446 return error; 447 } 448 449 450 // _SetTo 451 status_t 452 BDiskDevice::_SetTo(partition_id id, bool deviceOnly, size_t neededSize) 453 { 454 Unset(); 455 456 // get the device data 457 user_disk_device_data* data = NULL; 458 status_t error = _GetData(id, deviceOnly, neededSize, &data); 459 460 // set the data 461 if (error == B_OK) 462 error = _SetTo(data); 463 464 // cleanup on error 465 if (error != B_OK && data) 466 free(data); 467 468 return error; 469 } 470 471 472 // _SetTo 473 status_t 474 BDiskDevice::_SetTo(user_disk_device_data* data) 475 { 476 Unset(); 477 478 if (!data) 479 return B_BAD_VALUE; 480 481 fDeviceData = data; 482 483 status_t error = BPartition::_SetTo(this, NULL, 484 &fDeviceData->device_partition_data); 485 if (error != B_OK) { 486 // If _SetTo() fails, the caller retains ownership of the supplied 487 // data. So, unset fDeviceData before calling Unset(). 488 fDeviceData = NULL; 489 Unset(); 490 } 491 492 return error; 493 } 494 495 496 // _Update 497 status_t 498 BDiskDevice::_Update(user_disk_device_data* data, bool* updated) 499 { 500 if (!data || !fDeviceData || ID() != data->device_partition_data.id) 501 return B_BAD_VALUE; 502 bool _updated; 503 if (!updated) 504 updated = &_updated; 505 *updated = false; 506 507 // clear the user_data fields first 508 _ClearUserData(&data->device_partition_data); 509 510 // remove obsolete partitions 511 status_t error = _RemoveObsoleteDescendants(&data->device_partition_data, 512 updated); 513 if (error != B_OK) 514 return error; 515 516 // update existing partitions and add new ones 517 error = BPartition::_Update(&data->device_partition_data, updated); 518 if (error == B_OK) { 519 user_disk_device_data* oldData = fDeviceData; 520 fDeviceData = data; 521 // check for changes 522 if (data->device_flags != oldData->device_flags 523 || strcmp(data->path, oldData->path)) { 524 *updated = true; 525 } 526 free(oldData); 527 } 528 529 return error; 530 } 531 532 533 // _AcceptVisitor 534 bool 535 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level) 536 { 537 return visitor->Visit(this); 538 } 539 540 541 // _ClearUserData 542 void 543 BDiskDevice::_ClearUserData(user_partition_data* data) 544 { 545 data->user_data = NULL; 546 547 // recurse 548 for (int i = 0; i < data->child_count; i++) 549 _ClearUserData(data->children[i]); 550 } 551