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