xref: /haiku/src/kits/storage/disk_device/DiskDevice.cpp (revision 020cbad9d40235a2c50a81a42d69912a5ff8fbc4)
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 <disk_device_manager/ddm_userland_interface.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);
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);
76 }
77 
78 
79 // IsReadOnlyMedia
80 bool
81 BDiskDevice::IsReadOnlyMedia() const
82 {
83 	return (fDeviceData
84 		&& fDeviceData->device_flags & B_DISK_DEVICE_READ_ONLY);
85 }
86 
87 
88 // IsWriteOnceMedia
89 bool
90 BDiskDevice::IsWriteOnceMedia() const
91 {
92 	return (fDeviceData
93 		&& fDeviceData->device_flags & B_DISK_DEVICE_WRITE_ONCE);
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 /*	// get path
114 	const char* path = Path();
115 	status_t error = (path ? B_OK : B_NO_INIT);
116 	// check whether the device media is removable
117 	if (error == B_OK && !IsRemovable())
118 		error = B_BAD_VALUE;
119 	// open, eject and close the device
120 	if (error == B_OK) {
121 		int fd = open(path, O_RDONLY);
122 		if (fd < 0 || ioctl(fd, B_EJECT_DEVICE) != 0)
123 			error = errno;
124 		if (fd >= 0)
125 			close(fd);
126 	}
127 	if (error == B_OK && update)
128 		error = Update();
129 	return error;
130 */
131 	// not implemented
132 	return B_ERROR;
133 }
134 
135 
136 // SetTo
137 status_t
138 BDiskDevice::SetTo(partition_id id)
139 {
140 	return _SetTo(id, true, 0);
141 }
142 
143 
144 // Update
145 /*!	\brief Updates the object to reflect the latest changes to the device.
146 
147 	Note, that subobjects (BSessions, BPartitions) may be deleted during this
148 	operation. It is also possible, that the device doesn't exist anymore --
149 	e.g. if it is hot-pluggable. Then an error is returned and the object is
150 	uninitialized.
151 
152 	\param updated Pointer to a bool variable which shall be set to \c true,
153 		   if the object needed to be updated and to \c false otherwise.
154 		   May be \c NULL. Is not touched, if the method fails.
155 	\return \c B_OK, if the update went fine, another error code otherwise.
156 */
157 status_t
158 BDiskDevice::Update(bool* updated)
159 {
160 	if (InitCheck() != B_OK)
161 		return InitCheck();
162 
163 	// get the device data
164 	user_disk_device_data* data = NULL;
165 	status_t error = _GetData(ID(), true, 0, &data);
166 
167 	// set the data
168 	if (error == B_OK)
169 		error = _Update(data, updated);
170 
171 	// cleanup on error
172 	if (error != B_OK && data)
173 		free(data);
174 
175 	return error;
176 }
177 
178 
179 // Unset
180 void
181 BDiskDevice::Unset()
182 {
183 	BPartition::_Unset();
184 	free(fDeviceData);
185 	fDeviceData = NULL;
186 }
187 
188 
189 // InitCheck
190 status_t
191 BDiskDevice::InitCheck() const
192 {
193 	return (fDeviceData ? B_OK : B_NO_INIT);
194 }
195 
196 
197 // GetPath
198 status_t
199 BDiskDevice::GetPath(BPath* path) const
200 {
201 	if (!path || !fDeviceData)
202 		return B_BAD_VALUE;
203 	return path->SetTo(fDeviceData->path);
204 }
205 
206 
207 // IsModified
208 bool
209 BDiskDevice::IsModified() const
210 {
211 	if (InitCheck() != B_OK)
212 		return false;
213 
214 	struct IsModifiedVisitor : public BDiskDeviceVisitor {
215 		virtual bool Visit(BDiskDevice *device)
216 		{
217 			return Visit(device, 0);
218 		}
219 
220 		virtual bool Visit(BPartition *partition, int32 level)
221 		{
222 			return partition->_IsModified();
223 		}
224 	} visitor;
225 
226 	return (VisitEachDescendant(&visitor) != NULL);
227 }
228 
229 
230 // PrepareModifications
231 /*!	\brief Initializes the partition hierarchy for modifications.
232  *
233  * 	Subsequent modifications are performed on so-called \a shadow structure
234  * 	and not written to device until \ref CommitModifications is called.
235  *
236  * 	\note This call locks the device. You need to call \ref CommitModifications
237  * 		or \ref CancelModifications to unlock it.
238  */
239 status_t
240 BDiskDevice::PrepareModifications()
241 {
242 	// check initialization
243 	status_t error = InitCheck();
244 	if (error != B_OK)
245 		return error;
246 	if (fDelegate)
247 		return B_BAD_VALUE;
248 
249 	// make sure the disk system add-ons are loaded
250 	error = DiskSystemAddOnManager::Default()->LoadDiskSystems();
251 	if (error != B_OK)
252 		return error;
253 
254 	// recursively create the delegates
255 	error = _CreateDelegates();
256 
257 	// init them
258 	if (error == B_OK)
259 		error = _InitDelegates();
260 
261 	// delete all of them, if something went wrong
262 	if (error != B_OK) {
263 		_DeleteDelegates();
264 		DiskSystemAddOnManager::Default()->UnloadDiskSystems();
265 	}
266 
267 	return error;
268 }
269 
270 
271 // CommitModifications
272 /*!	\brief Commits modifications to device.
273  *
274  * 	Creates a set of jobs that perform all the changes done after
275  * 	\ref PrepareModifications. The changes are then written to device.
276  * 	Unlocks the device for further use.
277  */
278 status_t
279 BDiskDevice::CommitModifications(bool synchronously,
280 	BMessenger progressMessenger, bool receiveCompleteProgressUpdates)
281 {
282 // TODO: Support parameters!
283 	status_t error = InitCheck();
284 	if (error != B_OK)
285 		return error;
286 
287 	if (!fDelegate)
288 		return B_BAD_VALUE;
289 
290 	// generate jobs
291 	DiskDeviceJobQueue jobQueue;
292 	error = DiskDeviceJobGenerator(this, &jobQueue).GenerateJobs();
293 
294 	// do the jobs
295 	if (error == B_OK)
296 		error = jobQueue.Execute();
297 
298 	_DeleteDelegates();
299 	DiskSystemAddOnManager::Default()->UnloadDiskSystems();
300 
301 	if (error == B_OK)
302 		error = _SetTo(ID(), true, 0);
303 
304 	return error;
305 }
306 
307 
308 // CancelModifications
309 /*!	\brief Cancels all modifications performed on the device.
310  *
311  * 	Nothing is written on the device and it is unlocked for further use.
312  */
313 status_t
314 BDiskDevice::CancelModifications()
315 {
316 	status_t error = InitCheck();
317 	if (error != B_OK)
318 		return error;
319 
320 	if (!fDelegate)
321 		return B_BAD_VALUE;
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 // copy constructor
334 /*!	\brief Privatized copy constructor to avoid usage.
335 */
336 BDiskDevice::BDiskDevice(const BDiskDevice&)
337 {
338 }
339 
340 
341 // =
342 /*!	\brief Privatized assignment operator to avoid usage.
343 */
344 BDiskDevice&
345 BDiskDevice::operator=(const BDiskDevice&)
346 {
347 	return *this;
348 }
349 
350 
351 // _GetData
352 status_t
353 BDiskDevice::_GetData(partition_id id, bool deviceOnly, size_t neededSize,
354 	user_disk_device_data** data)
355 {
356 	// get the device data
357 	void* buffer = NULL;
358 	size_t bufferSize = 0;
359 	if (neededSize > 0) {
360 		// allocate initial buffer
361 		buffer = malloc(neededSize);
362 		if (!buffer)
363 			return B_NO_MEMORY;
364 		bufferSize = neededSize;
365 	}
366 
367 	status_t error = B_OK;
368 	do {
369 		error = _kern_get_disk_device_data(id, deviceOnly,
370 			(user_disk_device_data*)buffer, bufferSize, &neededSize);
371 		if (error == B_BUFFER_OVERFLOW) {
372 			// buffer to small re-allocate it
373 			if (buffer)
374 				free(buffer);
375 
376 			buffer = malloc(neededSize);
377 
378 			if (buffer)
379 				bufferSize = neededSize;
380 			else
381 				error = B_NO_MEMORY;
382 		}
383 	} while (error == B_BUFFER_OVERFLOW);
384 
385 	// set result / cleanup on error
386 	if (error == B_OK)
387 		*data = (user_disk_device_data*)buffer;
388 	else if (buffer)
389 		free(buffer);
390 
391 	return error;
392 }
393 
394 
395 // _SetTo
396 status_t
397 BDiskDevice::_SetTo(partition_id id, bool deviceOnly, size_t neededSize)
398 {
399 	Unset();
400 
401 	// get the device data
402 	user_disk_device_data* data = NULL;
403 	status_t error = _GetData(id, deviceOnly, neededSize, &data);
404 
405 	// set the data
406 	if (error == B_OK)
407 		error = _SetTo(data);
408 
409 	// cleanup on error
410 	if (error != B_OK && data)
411 		free(data);
412 
413 	return error;
414 }
415 
416 
417 // _SetTo
418 status_t
419 BDiskDevice::_SetTo(user_disk_device_data* data)
420 {
421 	Unset();
422 
423 	if (!data)
424 		return B_BAD_VALUE;
425 
426 	fDeviceData = data;
427 
428 	status_t error = BPartition::_SetTo(this, NULL,
429 		&fDeviceData->device_partition_data);
430 	if (error != B_OK) {
431 		// If _SetTo() fails, the caller retains ownership of the supplied
432 		// data. So, unset fDeviceData before calling Unset().
433 		fDeviceData = NULL;
434 		Unset();
435 	}
436 
437 	return error;
438 }
439 
440 
441 // _Update
442 status_t
443 BDiskDevice::_Update(user_disk_device_data* data, bool* updated)
444 {
445 	if (!data || !fDeviceData || ID() != data->device_partition_data.id)
446 		return B_BAD_VALUE;
447 	bool _updated;
448 	if (!updated)
449 		updated = &_updated;
450 	*updated = false;
451 
452 	// clear the user_data fields first
453 	_ClearUserData(&data->device_partition_data);
454 
455 	// remove obsolete partitions
456 	status_t error = _RemoveObsoleteDescendants(&data->device_partition_data,
457 		updated);
458 	if (error != B_OK)
459 		return error;
460 
461 	// update existing partitions and add new ones
462 	error = BPartition::_Update(&data->device_partition_data, updated);
463 	if (error == B_OK) {
464 		user_disk_device_data* oldData = fDeviceData;
465 		fDeviceData = data;
466 		// check for changes
467 		if (data->device_flags != oldData->device_flags
468 			|| strcmp(data->path, oldData->path)) {
469 			*updated = true;
470 		}
471 		free(oldData);
472 	}
473 
474 	return error;
475 }
476 
477 
478 // _AcceptVisitor
479 bool
480 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level)
481 {
482 	return visitor->Visit(this);
483 }
484 
485 
486 // _ClearUserData
487 void
488 BDiskDevice::_ClearUserData(user_partition_data* data)
489 {
490 	data->user_data = NULL;
491 
492 	// recurse
493 	for (int i = 0; i < data->child_count; i++)
494 		_ClearUserData(data->children[i]);
495 }
496