xref: /haiku/src/kits/storage/disk_device/DiskDevice.cpp (revision a7dde370f552f5376edbf25046ec9cf2ba8bbd1a)
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 			free(buffer);
429 
430 			buffer = malloc(neededSize);
431 
432 			if (buffer)
433 				bufferSize = neededSize;
434 			else
435 				error = B_NO_MEMORY;
436 		}
437 	} while (error == B_BUFFER_OVERFLOW);
438 
439 	// set result / cleanup on error
440 	if (error == B_OK)
441 		*data = (user_disk_device_data*)buffer;
442 	else
443 		free(buffer);
444 
445 	return error;
446 }
447 
448 
449 // _SetTo
450 status_t
451 BDiskDevice::_SetTo(partition_id id, bool deviceOnly, size_t neededSize)
452 {
453 	Unset();
454 
455 	// get the device data
456 	user_disk_device_data* data = NULL;
457 	status_t error = _GetData(id, deviceOnly, neededSize, &data);
458 
459 	// set the data
460 	if (error == B_OK)
461 		error = _SetTo(data);
462 
463 	// cleanup on error
464 	if (error != B_OK && data)
465 		free(data);
466 
467 	return error;
468 }
469 
470 
471 // _SetTo
472 status_t
473 BDiskDevice::_SetTo(user_disk_device_data* data)
474 {
475 	Unset();
476 
477 	if (!data)
478 		return B_BAD_VALUE;
479 
480 	fDeviceData = data;
481 
482 	status_t error = BPartition::_SetTo(this, NULL,
483 		&fDeviceData->device_partition_data);
484 	if (error != B_OK) {
485 		// If _SetTo() fails, the caller retains ownership of the supplied
486 		// data. So, unset fDeviceData before calling Unset().
487 		fDeviceData = NULL;
488 		Unset();
489 	}
490 
491 	return error;
492 }
493 
494 
495 // _Update
496 status_t
497 BDiskDevice::_Update(user_disk_device_data* data, bool* updated)
498 {
499 	if (!data || !fDeviceData || ID() != data->device_partition_data.id)
500 		return B_BAD_VALUE;
501 	bool _updated;
502 	if (!updated)
503 		updated = &_updated;
504 	*updated = false;
505 
506 	// clear the user_data fields first
507 	_ClearUserData(&data->device_partition_data);
508 
509 	// remove obsolete partitions
510 	status_t error = _RemoveObsoleteDescendants(&data->device_partition_data,
511 		updated);
512 	if (error != B_OK)
513 		return error;
514 
515 	// update existing partitions and add new ones
516 	error = BPartition::_Update(&data->device_partition_data, updated);
517 	if (error == B_OK) {
518 		user_disk_device_data* oldData = fDeviceData;
519 		fDeviceData = data;
520 		// check for changes
521 		if (data->device_flags != oldData->device_flags
522 			|| strcmp(data->path, oldData->path)) {
523 			*updated = true;
524 		}
525 		free(oldData);
526 	}
527 
528 	return error;
529 }
530 
531 
532 // _AcceptVisitor
533 bool
534 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level)
535 {
536 	return visitor->Visit(this);
537 }
538 
539 
540 // _ClearUserData
541 void
542 BDiskDevice::_ClearUserData(user_partition_data* data)
543 {
544 	data->user_data = NULL;
545 
546 	// recurse
547 	for (int i = 0; i < data->child_count; i++)
548 		_ClearUserData(data->children[i]);
549 }
550