xref: /haiku/src/kits/storage/disk_device/DiskDevice.cpp (revision b55a57da7173b9af0432bd3e148d03f06161d036)
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 			if (buffer)
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 if (buffer)
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