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 */
BDiskDevice()49 BDiskDevice::BDiskDevice()
50 :
51 fDeviceData(NULL)
52 {
53 }
54
55
56 // destructor
57 /*! \brief Frees all resources associated with this object.
58 */
~BDiskDevice()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
HasMedia() const70 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
IsRemovableMedia() const82 BDiskDevice::IsRemovableMedia() const
83 {
84 return fDeviceData
85 && (fDeviceData->device_flags & B_DISK_DEVICE_REMOVABLE) != 0;
86 }
87
88
89 // IsReadOnlyMedia
90 bool
IsReadOnlyMedia() const91 BDiskDevice::IsReadOnlyMedia() const
92 {
93 return fDeviceData
94 && (fDeviceData->device_flags & B_DISK_DEVICE_READ_ONLY) != 0;
95 }
96
97
98 // IsWriteOnceMedia
99 bool
IsWriteOnceMedia() const100 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
Eject(bool update)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
SetTo(partition_id id)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
Update(bool * updated)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
Unset()193 BDiskDevice::Unset()
194 {
195 BPartition::_Unset();
196 free(fDeviceData);
197 fDeviceData = NULL;
198 }
199
200
201 // InitCheck
202 status_t
InitCheck() const203 BDiskDevice::InitCheck() const
204 {
205 return fDeviceData ? B_OK : B_NO_INIT;
206 }
207
208
209 // GetPath
210 status_t
GetPath(BPath * path) const211 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
IsModified() const221 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
PrepareModifications()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
CommitModifications(bool synchronously,BMessenger progressMessenger,bool receiveCompleteProgressUpdates)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
CancelModifications()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
IsFile() const363 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
GetFilePath(BPath * path) const372 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 */
BDiskDevice(const BDiskDevice &)392 BDiskDevice::BDiskDevice(const BDiskDevice&)
393 {
394 }
395
396
397 // =
398 /*! \brief Privatized assignment operator to avoid usage.
399 */
400 BDiskDevice&
operator =(const BDiskDevice &)401 BDiskDevice::operator=(const BDiskDevice&)
402 {
403 return *this;
404 }
405
406
407 // _GetData
408 status_t
_GetData(partition_id id,bool deviceOnly,size_t neededSize,user_disk_device_data ** data)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
_SetTo(partition_id id,bool deviceOnly,size_t neededSize)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
_SetTo(user_disk_device_data * data)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
_Update(user_disk_device_data * data,bool * updated)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
_AcceptVisitor(BDiskDeviceVisitor * visitor,int32 level)535 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level)
536 {
537 return visitor->Visit(this);
538 }
539
540
541 // _ClearUserData
542 void
_ClearUserData(user_partition_data * data)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