headers/bsd: Add sys/queue.h.
[haiku.git] / src / kits / storage / disk_device / DiskDevice.cpp
blobd3d0367f4993a91d17b250b5327612d0f2bfb416
1 /*
2 * Copyright 2003-2007, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
6 #include <DiskDevice.h>
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>
17 #include <DiskDevice.h>
18 #include <DiskDeviceVisitor.h>
19 #include <Drivers.h>
20 #include <Message.h>
21 #include <Path.h>
23 #include <syscalls.h>
24 #include <ddm_userland_interface_defs.h>
26 #include "DiskDeviceJob.h"
27 #include "DiskDeviceJobGenerator.h"
28 #include "DiskDeviceJobQueue.h"
29 #include <DiskSystemAddOnManager.h>
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
41 /*! \class BDiskDevice
42 \brief A BDiskDevice object represents a storage device.
46 // constructor
47 /*! \brief Creates an uninitialized BDiskDevice object.
49 BDiskDevice::BDiskDevice()
51 fDeviceData(NULL)
56 // destructor
57 /*! \brief Frees all resources associated with this object.
59 BDiskDevice::~BDiskDevice()
61 CancelModifications();
65 // HasMedia
66 /*! \brief Returns whether the device contains a media.
67 \return \c true, if the device contains a media, \c false otherwise.
69 bool
70 BDiskDevice::HasMedia() const
72 return fDeviceData
73 && (fDeviceData->device_flags & B_DISK_DEVICE_HAS_MEDIA) != 0;
77 // IsRemovableMedia
78 /*! \brief Returns whether the device media are removable.
79 \return \c true, if the device media are removable, \c false otherwise.
81 bool
82 BDiskDevice::IsRemovableMedia() const
84 return fDeviceData
85 && (fDeviceData->device_flags & B_DISK_DEVICE_REMOVABLE) != 0;
89 // IsReadOnlyMedia
90 bool
91 BDiskDevice::IsReadOnlyMedia() const
93 return fDeviceData
94 && (fDeviceData->device_flags & B_DISK_DEVICE_READ_ONLY) != 0;
98 // IsWriteOnceMedia
99 bool
100 BDiskDevice::IsWriteOnceMedia() const
102 return fDeviceData
103 && (fDeviceData->device_flags & B_DISK_DEVICE_WRITE_ONCE) != 0;
107 // Eject
108 /*! \brief Eject the device's media.
110 The device media must, of course, be removable, and the device must
111 support ejecting the media.
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
120 status_t
121 BDiskDevice::Eject(bool update)
123 if (fDeviceData == NULL)
124 return B_NO_INIT;
126 // check whether the device media is removable
127 if (!IsRemovableMedia())
128 return B_BAD_VALUE;
130 // open, eject and close the device
131 int fd = open(fDeviceData->path, O_RDONLY);
132 if (fd < 0)
133 return errno;
135 status_t status = B_OK;
136 if (ioctl(fd, B_EJECT_DEVICE) != 0)
137 status = errno;
139 close(fd);
141 if (status == B_OK && update)
142 status = Update();
144 return status;
148 // SetTo
149 status_t
150 BDiskDevice::SetTo(partition_id id)
152 return _SetTo(id, true, 0);
156 // Update
157 /*! \brief Updates the object to reflect the latest changes to the device.
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.
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.
169 status_t
170 BDiskDevice::Update(bool* updated)
172 if (InitCheck() != B_OK)
173 return InitCheck();
175 // get the device data
176 user_disk_device_data* data = NULL;
177 status_t error = _GetData(ID(), true, 0, &data);
179 // set the data
180 if (error == B_OK)
181 error = _Update(data, updated);
183 // cleanup on error
184 if (error != B_OK && data)
185 free(data);
187 return error;
191 // Unset
192 void
193 BDiskDevice::Unset()
195 BPartition::_Unset();
196 free(fDeviceData);
197 fDeviceData = NULL;
201 // InitCheck
202 status_t
203 BDiskDevice::InitCheck() const
205 return fDeviceData ? B_OK : B_NO_INIT;
209 // GetPath
210 status_t
211 BDiskDevice::GetPath(BPath* path) const
213 if (!path || !fDeviceData)
214 return B_BAD_VALUE;
215 return path->SetTo(fDeviceData->path);
219 // IsModified
220 bool
221 BDiskDevice::IsModified() const
223 if (InitCheck() != B_OK)
224 return false;
226 struct IsModifiedVisitor : public BDiskDeviceVisitor {
227 virtual bool Visit(BDiskDevice* device)
229 return Visit(device, 0);
232 virtual bool Visit(BPartition* partition, int32 level)
234 return partition->_IsModified();
236 } visitor;
238 return (VisitEachDescendant(&visitor) != NULL);
242 // PrepareModifications
243 /*! \brief Initializes the partition hierarchy for modifications.
245 * Subsequent modifications are performed on so-called \a shadow structure
246 * and not written to device until \ref CommitModifications is called.
248 * \note This call locks the device. You need to call \ref CommitModifications
249 * or \ref CancelModifications to unlock it.
251 status_t
252 BDiskDevice::PrepareModifications()
254 TRACE("%p->BDiskDevice::PrepareModifications()\n", this);
256 // check initialization
257 status_t error = InitCheck();
258 if (error != B_OK) {
259 TRACE(" InitCheck() failed\n");
260 return error;
262 if (fDelegate) {
263 TRACE(" already prepared!\n");
264 return B_BAD_VALUE;
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;
274 // recursively create the delegates
275 error = _CreateDelegates();
277 // init them
278 if (error == B_OK)
279 error = _InitDelegates();
280 else
281 TRACE(" failed to create delegates\n");
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();
290 return error;
294 // CommitModifications
295 /*! \brief Commits modifications to device.
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.
304 status_t
305 BDiskDevice::CommitModifications(bool synchronously,
306 BMessenger progressMessenger, bool receiveCompleteProgressUpdates)
308 // TODO: Support parameters!
309 status_t error = InitCheck();
310 if (error != B_OK)
311 return error;
313 if (!fDelegate)
314 return B_BAD_VALUE;
316 // generate jobs
317 DiskDeviceJobQueue jobQueue;
318 error = DiskDeviceJobGenerator(this, &jobQueue).GenerateJobs();
320 // do the jobs
321 if (error == B_OK)
322 error = jobQueue.Execute();
324 _DeleteDelegates();
325 DiskSystemAddOnManager::Default()->UnloadDiskSystems();
327 if (error == B_OK)
328 error = _SetTo(ID(), true, 0);
330 return error;
334 // CancelModifications
335 /*! \brief Cancels all modifications performed on the device.
337 * Nothing is written on the device and it is unlocked for further use.
339 status_t
340 BDiskDevice::CancelModifications()
342 status_t error = InitCheck();
343 if (error != B_OK)
344 return error;
346 if (!fDelegate)
347 return B_BAD_VALUE;
349 _DeleteDelegates();
350 DiskSystemAddOnManager::Default()->UnloadDiskSystems();
352 if (error == B_OK)
353 error = _SetTo(ID(), true, 0);
355 return error;
359 /*! \brief Returns whether or not this device is a virtual device backed
360 up by a file.
362 bool
363 BDiskDevice::IsFile() const
365 return fDeviceData
366 && (fDeviceData->device_flags & B_DISK_DEVICE_IS_FILE) != 0;
370 /*! \brief Retrieves the path of the file backing up the disk device.*/
371 status_t
372 BDiskDevice::GetFilePath(BPath* path) const
374 if (path == NULL)
375 return B_BAD_VALUE;
376 if (!IsFile())
377 return B_BAD_TYPE;
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;
385 return path->SetTo(pathBuffer);
389 // copy constructor
390 /*! \brief Privatized copy constructor to avoid usage.
392 BDiskDevice::BDiskDevice(const BDiskDevice&)
397 // =
398 /*! \brief Privatized assignment operator to avoid usage.
400 BDiskDevice&
401 BDiskDevice::operator=(const BDiskDevice&)
403 return *this;
407 // _GetData
408 status_t
409 BDiskDevice::_GetData(partition_id id, bool deviceOnly, size_t neededSize,
410 user_disk_device_data** data)
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;
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);
431 buffer = malloc(neededSize);
433 if (buffer)
434 bufferSize = neededSize;
435 else
436 error = B_NO_MEMORY;
438 } while (error == B_BUFFER_OVERFLOW);
440 // set result / cleanup on error
441 if (error == B_OK)
442 *data = (user_disk_device_data*)buffer;
443 else
444 free(buffer);
446 return error;
450 // _SetTo
451 status_t
452 BDiskDevice::_SetTo(partition_id id, bool deviceOnly, size_t neededSize)
454 Unset();
456 // get the device data
457 user_disk_device_data* data = NULL;
458 status_t error = _GetData(id, deviceOnly, neededSize, &data);
460 // set the data
461 if (error == B_OK)
462 error = _SetTo(data);
464 // cleanup on error
465 if (error != B_OK && data)
466 free(data);
468 return error;
472 // _SetTo
473 status_t
474 BDiskDevice::_SetTo(user_disk_device_data* data)
476 Unset();
478 if (!data)
479 return B_BAD_VALUE;
481 fDeviceData = data;
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();
492 return error;
496 // _Update
497 status_t
498 BDiskDevice::_Update(user_disk_device_data* data, bool* updated)
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;
507 // clear the user_data fields first
508 _ClearUserData(&data->device_partition_data);
510 // remove obsolete partitions
511 status_t error = _RemoveObsoleteDescendants(&data->device_partition_data,
512 updated);
513 if (error != B_OK)
514 return error;
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;
526 free(oldData);
529 return error;
533 // _AcceptVisitor
534 bool
535 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level)
537 return visitor->Visit(this);
541 // _ClearUserData
542 void
543 BDiskDevice::_ClearUserData(user_partition_data* data)
545 data->user_data = NULL;
547 // recurse
548 for (int i = 0; i < data->child_count; i++)
549 _ClearUserData(data->children[i]);