xref: /haiku/src/kits/storage/disk_device/DiskDevice.cpp (revision e9c4d47ad719d6fd67cd9b75b41ebbec563e7a79)
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);
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  * 	Deletes and recreates all BPartition children to apply the changes,
277  * 	so cached pointers to BPartition objects cannot be used after this
278  * 	call.
279  * 	Unlocks the device for further use.
280  */
281 status_t
282 BDiskDevice::CommitModifications(bool synchronously,
283 	BMessenger progressMessenger, bool receiveCompleteProgressUpdates)
284 {
285 // TODO: Support parameters!
286 	status_t error = InitCheck();
287 	if (error != B_OK)
288 		return error;
289 
290 	if (!fDelegate)
291 		return B_BAD_VALUE;
292 
293 	// generate jobs
294 	DiskDeviceJobQueue jobQueue;
295 	error = DiskDeviceJobGenerator(this, &jobQueue).GenerateJobs();
296 
297 	// do the jobs
298 	if (error == B_OK)
299 		error = jobQueue.Execute();
300 
301 	_DeleteDelegates();
302 	DiskSystemAddOnManager::Default()->UnloadDiskSystems();
303 
304 	if (error == B_OK)
305 		error = _SetTo(ID(), true, 0);
306 
307 	return error;
308 }
309 
310 
311 // CancelModifications
312 /*!	\brief Cancels all modifications performed on the device.
313  *
314  * 	Nothing is written on the device and it is unlocked for further use.
315  */
316 status_t
317 BDiskDevice::CancelModifications()
318 {
319 	status_t error = InitCheck();
320 	if (error != B_OK)
321 		return error;
322 
323 	if (!fDelegate)
324 		return B_BAD_VALUE;
325 
326 	_DeleteDelegates();
327 	DiskSystemAddOnManager::Default()->UnloadDiskSystems();
328 
329 	if (error == B_OK)
330 		error = _SetTo(ID(), true, 0);
331 
332 	return error;
333 }
334 
335 
336 // copy constructor
337 /*!	\brief Privatized copy constructor to avoid usage.
338 */
339 BDiskDevice::BDiskDevice(const BDiskDevice&)
340 {
341 }
342 
343 
344 // =
345 /*!	\brief Privatized assignment operator to avoid usage.
346 */
347 BDiskDevice&
348 BDiskDevice::operator=(const BDiskDevice&)
349 {
350 	return *this;
351 }
352 
353 
354 // _GetData
355 status_t
356 BDiskDevice::_GetData(partition_id id, bool deviceOnly, size_t neededSize,
357 	user_disk_device_data** data)
358 {
359 	// get the device data
360 	void* buffer = NULL;
361 	size_t bufferSize = 0;
362 	if (neededSize > 0) {
363 		// allocate initial buffer
364 		buffer = malloc(neededSize);
365 		if (!buffer)
366 			return B_NO_MEMORY;
367 		bufferSize = neededSize;
368 	}
369 
370 	status_t error = B_OK;
371 	do {
372 		error = _kern_get_disk_device_data(id, deviceOnly,
373 			(user_disk_device_data*)buffer, bufferSize, &neededSize);
374 		if (error == B_BUFFER_OVERFLOW) {
375 			// buffer to small re-allocate it
376 			if (buffer)
377 				free(buffer);
378 
379 			buffer = malloc(neededSize);
380 
381 			if (buffer)
382 				bufferSize = neededSize;
383 			else
384 				error = B_NO_MEMORY;
385 		}
386 	} while (error == B_BUFFER_OVERFLOW);
387 
388 	// set result / cleanup on error
389 	if (error == B_OK)
390 		*data = (user_disk_device_data*)buffer;
391 	else if (buffer)
392 		free(buffer);
393 
394 	return error;
395 }
396 
397 
398 // _SetTo
399 status_t
400 BDiskDevice::_SetTo(partition_id id, bool deviceOnly, size_t neededSize)
401 {
402 	Unset();
403 
404 	// get the device data
405 	user_disk_device_data* data = NULL;
406 	status_t error = _GetData(id, deviceOnly, neededSize, &data);
407 
408 	// set the data
409 	if (error == B_OK)
410 		error = _SetTo(data);
411 
412 	// cleanup on error
413 	if (error != B_OK && data)
414 		free(data);
415 
416 	return error;
417 }
418 
419 
420 // _SetTo
421 status_t
422 BDiskDevice::_SetTo(user_disk_device_data* data)
423 {
424 	Unset();
425 
426 	if (!data)
427 		return B_BAD_VALUE;
428 
429 	fDeviceData = data;
430 
431 	status_t error = BPartition::_SetTo(this, NULL,
432 		&fDeviceData->device_partition_data);
433 	if (error != B_OK) {
434 		// If _SetTo() fails, the caller retains ownership of the supplied
435 		// data. So, unset fDeviceData before calling Unset().
436 		fDeviceData = NULL;
437 		Unset();
438 	}
439 
440 	return error;
441 }
442 
443 
444 // _Update
445 status_t
446 BDiskDevice::_Update(user_disk_device_data* data, bool* updated)
447 {
448 	if (!data || !fDeviceData || ID() != data->device_partition_data.id)
449 		return B_BAD_VALUE;
450 	bool _updated;
451 	if (!updated)
452 		updated = &_updated;
453 	*updated = false;
454 
455 	// clear the user_data fields first
456 	_ClearUserData(&data->device_partition_data);
457 
458 	// remove obsolete partitions
459 	status_t error = _RemoveObsoleteDescendants(&data->device_partition_data,
460 		updated);
461 	if (error != B_OK)
462 		return error;
463 
464 	// update existing partitions and add new ones
465 	error = BPartition::_Update(&data->device_partition_data, updated);
466 	if (error == B_OK) {
467 		user_disk_device_data* oldData = fDeviceData;
468 		fDeviceData = data;
469 		// check for changes
470 		if (data->device_flags != oldData->device_flags
471 			|| strcmp(data->path, oldData->path)) {
472 			*updated = true;
473 		}
474 		free(oldData);
475 	}
476 
477 	return error;
478 }
479 
480 
481 // _AcceptVisitor
482 bool
483 BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level)
484 {
485 	return visitor->Visit(this);
486 }
487 
488 
489 // _ClearUserData
490 void
491 BDiskDevice::_ClearUserData(user_partition_data* data)
492 {
493 	data->user_data = NULL;
494 
495 	// recurse
496 	for (int i = 0; i < data->child_count; i++)
497 		_ClearUserData(data->children[i]);
498 }
499