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 free(buffer); 429 430 buffer = malloc(neededSize); 431 432 if (buffer) 433 bufferSize = neededSize; 434 else 435 error = B_NO_MEMORY; 436 } 437 } while (error == B_BUFFER_OVERFLOW); 438 439 // set result / cleanup on error 440 if (error == B_OK) 441 *data = (user_disk_device_data*)buffer; 442 else 443 free(buffer); 444 445 return error; 446 } 447 448 449 // _SetTo 450 status_t 451 BDiskDevice::_SetTo(partition_id id, bool deviceOnly, size_t neededSize) 452 { 453 Unset(); 454 455 // get the device data 456 user_disk_device_data* data = NULL; 457 status_t error = _GetData(id, deviceOnly, neededSize, &data); 458 459 // set the data 460 if (error == B_OK) 461 error = _SetTo(data); 462 463 // cleanup on error 464 if (error != B_OK && data) 465 free(data); 466 467 return error; 468 } 469 470 471 // _SetTo 472 status_t 473 BDiskDevice::_SetTo(user_disk_device_data* data) 474 { 475 Unset(); 476 477 if (!data) 478 return B_BAD_VALUE; 479 480 fDeviceData = data; 481 482 status_t error = BPartition::_SetTo(this, NULL, 483 &fDeviceData->device_partition_data); 484 if (error != B_OK) { 485 // If _SetTo() fails, the caller retains ownership of the supplied 486 // data. So, unset fDeviceData before calling Unset(). 487 fDeviceData = NULL; 488 Unset(); 489 } 490 491 return error; 492 } 493 494 495 // _Update 496 status_t 497 BDiskDevice::_Update(user_disk_device_data* data, bool* updated) 498 { 499 if (!data || !fDeviceData || ID() != data->device_partition_data.id) 500 return B_BAD_VALUE; 501 bool _updated; 502 if (!updated) 503 updated = &_updated; 504 *updated = false; 505 506 // clear the user_data fields first 507 _ClearUserData(&data->device_partition_data); 508 509 // remove obsolete partitions 510 status_t error = _RemoveObsoleteDescendants(&data->device_partition_data, 511 updated); 512 if (error != B_OK) 513 return error; 514 515 // update existing partitions and add new ones 516 error = BPartition::_Update(&data->device_partition_data, updated); 517 if (error == B_OK) { 518 user_disk_device_data* oldData = fDeviceData; 519 fDeviceData = data; 520 // check for changes 521 if (data->device_flags != oldData->device_flags 522 || strcmp(data->path, oldData->path)) { 523 *updated = true; 524 } 525 free(oldData); 526 } 527 528 return error; 529 } 530 531 532 // _AcceptVisitor 533 bool 534 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level) 535 { 536 return visitor->Visit(this); 537 } 538 539 540 // _ClearUserData 541 void 542 BDiskDevice::_ClearUserData(user_partition_data* data) 543 { 544 data->user_data = NULL; 545 546 // recurse 547 for (int i = 0; i < data->child_count; i++) 548 _ClearUserData(data->children[i]); 549 } 550