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 : 51 fDeviceData(NULL) 52 { 53 } 54 55 56 // destructor 57 /*! \brief Frees all resources associated with this object. 58 */ 59 BDiskDevice::~BDiskDevice() 60 { 61 CancelModifications(); 62 } 63 64 65 // HasMedia 66 /*! \brief Returns whether the device contains a media. 67 \return \c true, if the device contains a media, \c false otherwise. 68 */ 69 bool 70 BDiskDevice::HasMedia() const 71 { 72 return fDeviceData 73 && (fDeviceData->device_flags & B_DISK_DEVICE_HAS_MEDIA) != 0; 74 } 75 76 77 // IsRemovableMedia 78 /*! \brief Returns whether the device media are removable. 79 \return \c true, if the device media are removable, \c false otherwise. 80 */ 81 bool 82 BDiskDevice::IsRemovableMedia() const 83 { 84 return fDeviceData 85 && (fDeviceData->device_flags & B_DISK_DEVICE_REMOVABLE) != 0; 86 } 87 88 89 // IsReadOnlyMedia 90 bool 91 BDiskDevice::IsReadOnlyMedia() const 92 { 93 return fDeviceData 94 && (fDeviceData->device_flags & B_DISK_DEVICE_READ_ONLY) != 0; 95 } 96 97 98 // IsWriteOnceMedia 99 bool 100 BDiskDevice::IsWriteOnceMedia() const 101 { 102 return fDeviceData 103 && (fDeviceData->device_flags & B_DISK_DEVICE_WRITE_ONCE) != 0; 104 } 105 106 107 // Eject 108 /*! \brief Eject the device's media. 109 110 The device media must, of course, be removable, and the device must 111 support ejecting the media. 112 113 \param update If \c true, Update() is invoked after successful ejection. 114 \return 115 - \c B_OK: Everything went fine. 116 - \c B_NO_INIT: The device is not properly initialized. 117 - \c B_BAD_VALUE: The device media is not removable. 118 - other error codes 119 */ 120 status_t 121 BDiskDevice::Eject(bool update) 122 { 123 if (fDeviceData == NULL) 124 return B_NO_INIT; 125 126 // check whether the device media is removable 127 if (!IsRemovableMedia()) 128 return B_BAD_VALUE; 129 130 // open, eject and close the device 131 int fd = open(fDeviceData->path, O_RDONLY); 132 if (fd < 0) 133 return errno; 134 135 status_t status = B_OK; 136 if (ioctl(fd, B_EJECT_DEVICE) != 0) 137 status = errno; 138 139 close(fd); 140 141 if (status == B_OK && update) 142 status = Update(); 143 144 return status; 145 } 146 147 148 // SetTo 149 status_t 150 BDiskDevice::SetTo(partition_id id) 151 { 152 return _SetTo(id, true, 0); 153 } 154 155 156 // Update 157 /*! \brief Updates the object to reflect the latest changes to the device. 158 159 Note, that subobjects (BSessions, BPartitions) may be deleted during this 160 operation. It is also possible, that the device doesn't exist anymore -- 161 e.g. if it is hot-pluggable. Then an error is returned and the object is 162 uninitialized. 163 164 \param updated Pointer to a bool variable which shall be set to \c true, 165 if the object needed to be updated and to \c false otherwise. 166 May be \c NULL. Is not touched, if the method fails. 167 \return \c B_OK, if the update went fine, another error code otherwise. 168 */ 169 status_t 170 BDiskDevice::Update(bool* updated) 171 { 172 if (InitCheck() != B_OK) 173 return InitCheck(); 174 175 // get the device data 176 user_disk_device_data* data = NULL; 177 status_t error = _GetData(ID(), true, 0, &data); 178 179 // set the data 180 if (error == B_OK) 181 error = _Update(data, updated); 182 183 // cleanup on error 184 if (error != B_OK && data) 185 free(data); 186 187 return error; 188 } 189 190 191 // Unset 192 void 193 BDiskDevice::Unset() 194 { 195 BPartition::_Unset(); 196 free(fDeviceData); 197 fDeviceData = NULL; 198 } 199 200 201 // InitCheck 202 status_t 203 BDiskDevice::InitCheck() const 204 { 205 return fDeviceData ? B_OK : B_NO_INIT; 206 } 207 208 209 // GetPath 210 status_t 211 BDiskDevice::GetPath(BPath* path) const 212 { 213 if (!path || !fDeviceData) 214 return B_BAD_VALUE; 215 return path->SetTo(fDeviceData->path); 216 } 217 218 219 // IsModified 220 bool 221 BDiskDevice::IsModified() const 222 { 223 if (InitCheck() != B_OK) 224 return false; 225 226 struct IsModifiedVisitor : public BDiskDeviceVisitor { 227 virtual bool Visit(BDiskDevice* device) 228 { 229 return Visit(device, 0); 230 } 231 232 virtual bool Visit(BPartition* partition, int32 level) 233 { 234 return partition->_IsModified(); 235 } 236 } visitor; 237 238 return (VisitEachDescendant(&visitor) != NULL); 239 } 240 241 242 // PrepareModifications 243 /*! \brief Initializes the partition hierarchy for modifications. 244 * 245 * Subsequent modifications are performed on so-called \a shadow structure 246 * and not written to device until \ref CommitModifications is called. 247 * 248 * \note This call locks the device. You need to call \ref CommitModifications 249 * or \ref CancelModifications to unlock it. 250 */ 251 status_t 252 BDiskDevice::PrepareModifications() 253 { 254 TRACE("%p->BDiskDevice::PrepareModifications()\n", this); 255 256 // check initialization 257 status_t error = InitCheck(); 258 if (error != B_OK) { 259 TRACE(" InitCheck() failed\n"); 260 return error; 261 } 262 if (fDelegate) { 263 TRACE(" already prepared!\n"); 264 return B_BAD_VALUE; 265 } 266 267 // make sure the disk system add-ons are loaded 268 error = DiskSystemAddOnManager::Default()->LoadDiskSystems(); 269 if (error != B_OK) { 270 TRACE(" failed to load disk systems\n"); 271 return error; 272 } 273 274 // recursively create the delegates 275 error = _CreateDelegates(); 276 277 // init them 278 if (error == B_OK) 279 error = _InitDelegates(); 280 else 281 TRACE(" failed to create delegates\n"); 282 283 // delete all of them, if something went wrong 284 if (error != B_OK) { 285 TRACE(" failed to init delegates\n"); 286 _DeleteDelegates(); 287 DiskSystemAddOnManager::Default()->UnloadDiskSystems(); 288 } 289 290 return error; 291 } 292 293 294 // CommitModifications 295 /*! \brief Commits modifications to device. 296 * 297 * Creates a set of jobs that perform all the changes done after 298 * \ref PrepareModifications. The changes are then written to device. 299 * Deletes and recreates all BPartition children to apply the changes, 300 * so cached pointers to BPartition objects cannot be used after this 301 * call. 302 * Unlocks the device for further use. 303 */ 304 status_t 305 BDiskDevice::CommitModifications(bool synchronously, 306 BMessenger progressMessenger, bool receiveCompleteProgressUpdates) 307 { 308 // TODO: Support parameters! 309 status_t error = InitCheck(); 310 if (error != B_OK) 311 return error; 312 313 if (!fDelegate) 314 return B_BAD_VALUE; 315 316 // generate jobs 317 DiskDeviceJobQueue jobQueue; 318 error = DiskDeviceJobGenerator(this, &jobQueue).GenerateJobs(); 319 320 // do the jobs 321 if (error == B_OK) 322 error = jobQueue.Execute(); 323 324 _DeleteDelegates(); 325 DiskSystemAddOnManager::Default()->UnloadDiskSystems(); 326 327 if (error == B_OK) 328 error = _SetTo(ID(), true, 0); 329 330 return error; 331 } 332 333 334 // CancelModifications 335 /*! \brief Cancels all modifications performed on the device. 336 * 337 * Nothing is written on the device and it is unlocked for further use. 338 */ 339 status_t 340 BDiskDevice::CancelModifications() 341 { 342 status_t error = InitCheck(); 343 if (error != B_OK) 344 return error; 345 346 if (!fDelegate) 347 return B_BAD_VALUE; 348 349 _DeleteDelegates(); 350 DiskSystemAddOnManager::Default()->UnloadDiskSystems(); 351 352 if (error == B_OK) 353 error = _SetTo(ID(), true, 0); 354 355 return error; 356 } 357 358 359 /*! \brief Returns whether or not this device is a virtual device backed 360 up by a file. 361 */ 362 bool 363 BDiskDevice::IsFile() const 364 { 365 return fDeviceData 366 && (fDeviceData->device_flags & B_DISK_DEVICE_IS_FILE) != 0; 367 } 368 369 370 /*! \brief Retrieves the path of the file backing up the disk device.*/ 371 status_t 372 BDiskDevice::GetFilePath(BPath* path) const 373 { 374 if (path == NULL) 375 return B_BAD_VALUE; 376 if (!IsFile()) 377 return B_BAD_TYPE; 378 379 char pathBuffer[B_PATH_NAME_LENGTH]; 380 status_t status = _kern_get_file_disk_device_path( 381 fDeviceData->device_partition_data.id, pathBuffer, sizeof(pathBuffer)); 382 if (status != B_OK) 383 return status; 384 385 return path->SetTo(pathBuffer); 386 } 387 388 389 // copy constructor 390 /*! \brief Privatized copy constructor to avoid usage. 391 */ 392 BDiskDevice::BDiskDevice(const BDiskDevice&) 393 { 394 } 395 396 397 // = 398 /*! \brief Privatized assignment operator to avoid usage. 399 */ 400 BDiskDevice& 401 BDiskDevice::operator=(const BDiskDevice&) 402 { 403 return *this; 404 } 405 406 407 // _GetData 408 status_t 409 BDiskDevice::_GetData(partition_id id, bool deviceOnly, size_t neededSize, 410 user_disk_device_data** data) 411 { 412 // get the device data 413 void* buffer = NULL; 414 size_t bufferSize = 0; 415 if (neededSize > 0) { 416 // allocate initial buffer 417 buffer = malloc(neededSize); 418 if (!buffer) 419 return B_NO_MEMORY; 420 bufferSize = neededSize; 421 } 422 423 status_t error = B_OK; 424 do { 425 error = _kern_get_disk_device_data(id, deviceOnly, 426 (user_disk_device_data*)buffer, bufferSize, &neededSize); 427 if (error == B_BUFFER_OVERFLOW) { 428 // buffer to small re-allocate it 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 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