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