xref: /haiku/src/kits/storage/disk_device/DiskDevice.cpp (revision 4f2fd49bdc6078128b1391191e4edac647044c3d)
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 /*!	\class BDiskDevice
33 	\brief A BDiskDevice object represents a storage device.
34 */
35 
36 
37 // constructor
38 /*!	\brief Creates an uninitialized BDiskDevice object.
39 */
40 BDiskDevice::BDiskDevice()
41 	: fDeviceData(NULL)
42 {
43 }
44 
45 
46 // destructor
47 /*!	\brief Frees all resources associated with this object.
48 */
49 BDiskDevice::~BDiskDevice()
50 {
51 	CancelModifications();
52 }
53 
54 
55 // HasMedia
56 /*!	\brief Returns whether the device contains a media.
57 	\return \c true, if the device contains a media, \c false otherwise.
58 */
59 bool
60 BDiskDevice::HasMedia() const
61 {
62 	return fDeviceData
63 		&& (fDeviceData->device_flags & B_DISK_DEVICE_HAS_MEDIA) != 0;
64 }
65 
66 
67 // IsRemovableMedia
68 /*!	\brief Returns whether the device media are removable.
69 	\return \c true, if the device media are removable, \c false otherwise.
70 */
71 bool
72 BDiskDevice::IsRemovableMedia() const
73 {
74 	return fDeviceData
75 		&& (fDeviceData->device_flags & B_DISK_DEVICE_REMOVABLE) != 0;
76 }
77 
78 
79 // IsReadOnlyMedia
80 bool
81 BDiskDevice::IsReadOnlyMedia() const
82 {
83 	return fDeviceData
84 		&& (fDeviceData->device_flags & B_DISK_DEVICE_READ_ONLY) != 0;
85 }
86 
87 
88 // IsWriteOnceMedia
89 bool
90 BDiskDevice::IsWriteOnceMedia() const
91 {
92 	return fDeviceData
93 		&& (fDeviceData->device_flags & B_DISK_DEVICE_WRITE_ONCE) != 0;
94 }
95 
96 
97 // Eject
98 /*!	\brief Eject the device's media.
99 
100 	The device media must, of course, be removable, and the device must
101 	support ejecting the media.
102 
103 	\param update If \c true, Update() is invoked after successful ejection.
104 	\return
105 	- \c B_OK: Everything went fine.
106 	- \c B_NO_INIT: The device is not properly initialized.
107 	- \c B_BAD_VALUE: The device media is not removable.
108 	- other error codes
109 */
110 status_t
111 BDiskDevice::Eject(bool update)
112 {
113 	if (fDeviceData == NULL)
114 		return B_NO_INIT;
115 
116 	// check whether the device media is removable
117 	if (!IsRemovableMedia())
118 		return B_BAD_VALUE;
119 
120 	// open, eject and close the device
121 	int fd = open(fDeviceData->path, O_RDONLY);
122 	if (fd < 0)
123 		return errno;
124 
125 	status_t status = B_OK;
126 	if (ioctl(fd, B_EJECT_DEVICE) != 0)
127 		status = errno;
128 
129 	close(fd);
130 
131 	if (status == B_OK && update)
132 		status = Update();
133 
134 	return status;
135 }
136 
137 
138 // SetTo
139 status_t
140 BDiskDevice::SetTo(partition_id id)
141 {
142 	return _SetTo(id, true, 0);
143 }
144 
145 
146 // Update
147 /*!	\brief Updates the object to reflect the latest changes to the device.
148 
149 	Note, that subobjects (BSessions, BPartitions) may be deleted during this
150 	operation. It is also possible, that the device doesn't exist anymore --
151 	e.g. if it is hot-pluggable. Then an error is returned and the object is
152 	uninitialized.
153 
154 	\param updated Pointer to a bool variable which shall be set to \c true,
155 		   if the object needed to be updated and to \c false otherwise.
156 		   May be \c NULL. Is not touched, if the method fails.
157 	\return \c B_OK, if the update went fine, another error code otherwise.
158 */
159 status_t
160 BDiskDevice::Update(bool* updated)
161 {
162 	if (InitCheck() != B_OK)
163 		return InitCheck();
164 
165 	// get the device data
166 	user_disk_device_data* data = NULL;
167 	status_t error = _GetData(ID(), true, 0, &data);
168 
169 	// set the data
170 	if (error == B_OK)
171 		error = _Update(data, updated);
172 
173 	// cleanup on error
174 	if (error != B_OK && data)
175 		free(data);
176 
177 	return error;
178 }
179 
180 
181 // Unset
182 void
183 BDiskDevice::Unset()
184 {
185 	BPartition::_Unset();
186 	free(fDeviceData);
187 	fDeviceData = NULL;
188 }
189 
190 
191 // InitCheck
192 status_t
193 BDiskDevice::InitCheck() const
194 {
195 	return fDeviceData ? B_OK : B_NO_INIT;
196 }
197 
198 
199 // GetPath
200 status_t
201 BDiskDevice::GetPath(BPath* path) const
202 {
203 	if (!path || !fDeviceData)
204 		return B_BAD_VALUE;
205 	return path->SetTo(fDeviceData->path);
206 }
207 
208 
209 // IsModified
210 bool
211 BDiskDevice::IsModified() const
212 {
213 	if (InitCheck() != B_OK)
214 		return false;
215 
216 	struct IsModifiedVisitor : public BDiskDeviceVisitor {
217 		virtual bool Visit(BDiskDevice *device)
218 		{
219 			return Visit(device, 0);
220 		}
221 
222 		virtual bool Visit(BPartition *partition, int32 level)
223 		{
224 			return partition->_IsModified();
225 		}
226 	} visitor;
227 
228 	return (VisitEachDescendant(&visitor) != NULL);
229 }
230 
231 
232 // PrepareModifications
233 /*!	\brief Initializes the partition hierarchy for modifications.
234  *
235  * 	Subsequent modifications are performed on so-called \a shadow structure
236  * 	and not written to device until \ref CommitModifications is called.
237  *
238  * 	\note This call locks the device. You need to call \ref CommitModifications
239  * 		or \ref CancelModifications to unlock it.
240  */
241 status_t
242 BDiskDevice::PrepareModifications()
243 {
244 	// check initialization
245 	status_t error = InitCheck();
246 	if (error != B_OK)
247 		return error;
248 	if (fDelegate)
249 		return B_BAD_VALUE;
250 
251 	// make sure the disk system add-ons are loaded
252 	error = DiskSystemAddOnManager::Default()->LoadDiskSystems();
253 	if (error != B_OK)
254 		return error;
255 
256 	// recursively create the delegates
257 	error = _CreateDelegates();
258 
259 	// init them
260 	if (error == B_OK)
261 		error = _InitDelegates();
262 
263 	// delete all of them, if something went wrong
264 	if (error != B_OK) {
265 		_DeleteDelegates();
266 		DiskSystemAddOnManager::Default()->UnloadDiskSystems();
267 	}
268 
269 	return error;
270 }
271 
272 
273 // CommitModifications
274 /*!	\brief Commits modifications to device.
275  *
276  * 	Creates a set of jobs that perform all the changes done after
277  * 	\ref PrepareModifications. The changes are then written to device.
278  * 	Deletes and recreates all BPartition children to apply the changes,
279  * 	so cached pointers to BPartition objects cannot be used after this
280  * 	call.
281  * 	Unlocks the device for further use.
282  */
283 status_t
284 BDiskDevice::CommitModifications(bool synchronously,
285 	BMessenger progressMessenger, bool receiveCompleteProgressUpdates)
286 {
287 // TODO: Support parameters!
288 	status_t error = InitCheck();
289 	if (error != B_OK)
290 		return error;
291 
292 	if (!fDelegate)
293 		return B_BAD_VALUE;
294 
295 	// generate jobs
296 	DiskDeviceJobQueue jobQueue;
297 	error = DiskDeviceJobGenerator(this, &jobQueue).GenerateJobs();
298 
299 	// do the jobs
300 	if (error == B_OK)
301 		error = jobQueue.Execute();
302 
303 	_DeleteDelegates();
304 	DiskSystemAddOnManager::Default()->UnloadDiskSystems();
305 
306 	if (error == B_OK)
307 		error = _SetTo(ID(), true, 0);
308 
309 	return error;
310 }
311 
312 
313 // CancelModifications
314 /*!	\brief Cancels all modifications performed on the device.
315  *
316  * 	Nothing is written on the device and it is unlocked for further use.
317  */
318 status_t
319 BDiskDevice::CancelModifications()
320 {
321 	status_t error = InitCheck();
322 	if (error != B_OK)
323 		return error;
324 
325 	if (!fDelegate)
326 		return B_BAD_VALUE;
327 
328 	_DeleteDelegates();
329 	DiskSystemAddOnManager::Default()->UnloadDiskSystems();
330 
331 	if (error == B_OK)
332 		error = _SetTo(ID(), true, 0);
333 
334 	return error;
335 }
336 
337 
338 /*!	\brief Returns whether or not this device is a virtual device backed
339 		up by a file.
340 */
341 bool
342 BDiskDevice::IsFile() const
343 {
344 	return fDeviceData
345 		&& (fDeviceData->device_flags & B_DISK_DEVICE_IS_FILE) != 0;
346 }
347 
348 
349 /*!	\brief Retrieves the path of the file backing up the disk device.*/
350 status_t
351 BDiskDevice::GetFilePath(BPath* path) const
352 {
353 	if (path == NULL)
354 		return B_BAD_VALUE;
355 	if (!IsFile())
356 		return B_BAD_TYPE;
357 
358 	char pathBuffer[B_PATH_NAME_LENGTH];
359 	status_t status = _kern_get_file_disk_device_path(
360 		fDeviceData->device_partition_data.id, pathBuffer, sizeof(pathBuffer));
361 	if (status != B_OK)
362 		return status;
363 
364 	return path->SetTo(pathBuffer);
365 }
366 
367 
368 // copy constructor
369 /*!	\brief Privatized copy constructor to avoid usage.
370 */
371 BDiskDevice::BDiskDevice(const BDiskDevice&)
372 {
373 }
374 
375 
376 // =
377 /*!	\brief Privatized assignment operator to avoid usage.
378 */
379 BDiskDevice&
380 BDiskDevice::operator=(const BDiskDevice&)
381 {
382 	return *this;
383 }
384 
385 
386 // _GetData
387 status_t
388 BDiskDevice::_GetData(partition_id id, bool deviceOnly, size_t neededSize,
389 	user_disk_device_data** data)
390 {
391 	// get the device data
392 	void* buffer = NULL;
393 	size_t bufferSize = 0;
394 	if (neededSize > 0) {
395 		// allocate initial buffer
396 		buffer = malloc(neededSize);
397 		if (!buffer)
398 			return B_NO_MEMORY;
399 		bufferSize = neededSize;
400 	}
401 
402 	status_t error = B_OK;
403 	do {
404 		error = _kern_get_disk_device_data(id, deviceOnly,
405 			(user_disk_device_data*)buffer, bufferSize, &neededSize);
406 		if (error == B_BUFFER_OVERFLOW) {
407 			// buffer to small re-allocate it
408 			if (buffer)
409 				free(buffer);
410 
411 			buffer = malloc(neededSize);
412 
413 			if (buffer)
414 				bufferSize = neededSize;
415 			else
416 				error = B_NO_MEMORY;
417 		}
418 	} while (error == B_BUFFER_OVERFLOW);
419 
420 	// set result / cleanup on error
421 	if (error == B_OK)
422 		*data = (user_disk_device_data*)buffer;
423 	else if (buffer)
424 		free(buffer);
425 
426 	return error;
427 }
428 
429 
430 // _SetTo
431 status_t
432 BDiskDevice::_SetTo(partition_id id, bool deviceOnly, size_t neededSize)
433 {
434 	Unset();
435 
436 	// get the device data
437 	user_disk_device_data* data = NULL;
438 	status_t error = _GetData(id, deviceOnly, neededSize, &data);
439 
440 	// set the data
441 	if (error == B_OK)
442 		error = _SetTo(data);
443 
444 	// cleanup on error
445 	if (error != B_OK && data)
446 		free(data);
447 
448 	return error;
449 }
450 
451 
452 // _SetTo
453 status_t
454 BDiskDevice::_SetTo(user_disk_device_data* data)
455 {
456 	Unset();
457 
458 	if (!data)
459 		return B_BAD_VALUE;
460 
461 	fDeviceData = data;
462 
463 	status_t error = BPartition::_SetTo(this, NULL,
464 		&fDeviceData->device_partition_data);
465 	if (error != B_OK) {
466 		// If _SetTo() fails, the caller retains ownership of the supplied
467 		// data. So, unset fDeviceData before calling Unset().
468 		fDeviceData = NULL;
469 		Unset();
470 	}
471 
472 	return error;
473 }
474 
475 
476 // _Update
477 status_t
478 BDiskDevice::_Update(user_disk_device_data* data, bool* updated)
479 {
480 	if (!data || !fDeviceData || ID() != data->device_partition_data.id)
481 		return B_BAD_VALUE;
482 	bool _updated;
483 	if (!updated)
484 		updated = &_updated;
485 	*updated = false;
486 
487 	// clear the user_data fields first
488 	_ClearUserData(&data->device_partition_data);
489 
490 	// remove obsolete partitions
491 	status_t error = _RemoveObsoleteDescendants(&data->device_partition_data,
492 		updated);
493 	if (error != B_OK)
494 		return error;
495 
496 	// update existing partitions and add new ones
497 	error = BPartition::_Update(&data->device_partition_data, updated);
498 	if (error == B_OK) {
499 		user_disk_device_data* oldData = fDeviceData;
500 		fDeviceData = data;
501 		// check for changes
502 		if (data->device_flags != oldData->device_flags
503 			|| strcmp(data->path, oldData->path)) {
504 			*updated = true;
505 		}
506 		free(oldData);
507 	}
508 
509 	return error;
510 }
511 
512 
513 // _AcceptVisitor
514 bool
515 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level)
516 {
517 	return visitor->Visit(this);
518 }
519 
520 
521 // _ClearUserData
522 void
523 BDiskDevice::_ClearUserData(user_partition_data* data)
524 {
525 	data->user_data = NULL;
526 
527 	// recurse
528 	for (int i = 0; i < data->child_count; i++)
529 		_ClearUserData(data->children[i]);
530 }
531