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*/
49BDiskDevice::BDiskDevice()
50	:
51	fDeviceData(NULL)
52{
53}
54
55
56// destructor
57/*!	\brief Frees all resources associated with this object.
58*/
59BDiskDevice::~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*/
69bool
70BDiskDevice::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*/
81bool
82BDiskDevice::IsRemovableMedia() const
83{
84	return fDeviceData
85		&& (fDeviceData->device_flags & B_DISK_DEVICE_REMOVABLE) != 0;
86}
87
88
89// IsReadOnlyMedia
90bool
91BDiskDevice::IsReadOnlyMedia() const
92{
93	return fDeviceData
94		&& (fDeviceData->device_flags & B_DISK_DEVICE_READ_ONLY) != 0;
95}
96
97
98// IsWriteOnceMedia
99bool
100BDiskDevice::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*/
120status_t
121BDiskDevice::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
149status_t
150BDiskDevice::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*/
169status_t
170BDiskDevice::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
192void
193BDiskDevice::Unset()
194{
195	BPartition::_Unset();
196	free(fDeviceData);
197	fDeviceData = NULL;
198}
199
200
201// InitCheck
202status_t
203BDiskDevice::InitCheck() const
204{
205	return fDeviceData ? B_OK : B_NO_INIT;
206}
207
208
209// GetPath
210status_t
211BDiskDevice::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
220bool
221BDiskDevice::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 */
251status_t
252BDiskDevice::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 */
304status_t
305BDiskDevice::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 */
339status_t
340BDiskDevice::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*/
362bool
363BDiskDevice::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.*/
371status_t
372BDiskDevice::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*/
392BDiskDevice::BDiskDevice(const BDiskDevice&)
393{
394}
395
396
397// =
398/*!	\brief Privatized assignment operator to avoid usage.
399*/
400BDiskDevice&
401BDiskDevice::operator=(const BDiskDevice&)
402{
403	return *this;
404}
405
406
407// _GetData
408status_t
409BDiskDevice::_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
451status_t
452BDiskDevice::_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
473status_t
474BDiskDevice::_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
497status_t
498BDiskDevice::_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
534bool
535BDiskDevice::_AcceptVisitor(BDiskDeviceVisitor* visitor, int32 level)
536{
537	return visitor->Visit(this);
538}
539
540
541// _ClearUserData
542void
543BDiskDevice::_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