1/*
2 * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "Volume.h"
8
9#include <errno.h>
10#include <fcntl.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <sys/stat.h>
15
16#include <new>
17
18#include <fs_cache.h>
19
20#include <AutoDeleter.h>
21#include <util/AutoLock.h>
22
23#include "Block.h"
24#include "BlockAllocator.h"
25#include "checksumfs.h"
26#include "checksumfs_private.h"
27#include "DebugSupport.h"
28#include "Directory.h"
29#include "File.h"
30#include "SuperBlock.h"
31#include "SymLink.h"
32#include "Transaction.h"
33
34
35Volume::Volume(uint32 flags)
36	:
37	fFSVolume(NULL),
38	fFD(-1),
39	fFlags(B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | B_FS_HAS_MIME
40		| ((flags & B_FS_IS_READONLY) != 0 ? B_FS_IS_READONLY : 0)),
41	fBlockCache(NULL),
42	fTotalBlocks(0),
43	fName(NULL),
44	fBlockAllocator(NULL),
45	fRootDirectory(NULL)
46{
47	mutex_init(&fLock, "checksumfs volume");
48	mutex_init(&fTransactionLock, "checksumfs transaction");
49}
50
51
52Volume::~Volume()
53{
54	delete fRootDirectory;
55	delete fBlockAllocator;
56
57	if (fBlockCache != NULL)
58		block_cache_delete(fBlockCache, false);
59
60	if (fFD >= 0)
61		close(fFD);
62
63	free(fName);
64
65	mutex_destroy(&fTransactionLock);
66	mutex_destroy(&fLock);
67}
68
69
70status_t
71Volume::Init(const char* device)
72{
73	// open the device
74	if (!IsReadOnly()) {
75		fFD = open(device, O_RDWR);
76
77		// If opening read-write fails, we retry read-only.
78		if (fFD < 0)
79			fFlags |= B_FS_IS_READONLY;
80	}
81
82	if (IsReadOnly())
83		fFD = open(device, O_RDONLY);
84
85	if (fFD < 0)
86		return errno;
87
88	// get the size
89	struct stat st;
90	if (fstat(fFD, &st) < 0)
91		return errno;
92
93	off_t size;
94	switch (st.st_mode & S_IFMT) {
95		case S_IFREG:
96			size = st.st_size;
97			break;
98		case S_IFCHR:
99		case S_IFBLK:
100		{
101			device_geometry geometry;
102			if (ioctl(fFD, B_GET_GEOMETRY, &geometry, sizeof(geometry)) < 0)
103				return errno;
104
105			size = (off_t)geometry.bytes_per_sector * geometry.sectors_per_track
106				* geometry.cylinder_count * geometry.head_count;
107			break;
108		}
109		default:
110			return B_BAD_VALUE;
111	}
112
113	return _Init(size / B_PAGE_SIZE);
114}
115
116
117status_t
118Volume::Init(int fd, uint64 totalBlocks)
119{
120	fFD = dup(fd);
121	if (fFD < 0)
122		RETURN_ERROR(errno);
123
124	return _Init(totalBlocks);
125}
126
127
128status_t
129Volume::Mount(fs_volume* fsVolume)
130{
131	fFSVolume = fsVolume;
132
133	// load the superblock
134	Block block;
135	if (!block.GetReadable(this, kCheckSumFSSuperBlockOffset / B_PAGE_SIZE))
136		RETURN_ERROR(B_ERROR);
137
138	SuperBlock* superBlock = (SuperBlock*)block.Data();
139	if (!superBlock->Check(fTotalBlocks))
140		RETURN_ERROR(B_BAD_DATA);
141
142	// copy the volume name
143	fName = strdup(superBlock->Name());
144	if (fName == NULL)
145		RETURN_ERROR(B_NO_MEMORY);
146
147	// init the block allocator
148	status_t error = fBlockAllocator->Init(superBlock->BlockBitmap(),
149		superBlock->FreeBlocks());
150	if (error != B_OK)
151		RETURN_ERROR(error);
152
153	// load the root directory
154	Node* rootNode;
155	error = ReadNode(superBlock->RootDirectory(), rootNode);
156	if (error != B_OK)
157		RETURN_ERROR(error);
158
159	fRootDirectory = dynamic_cast<Directory*>(rootNode);
160	if (fRootDirectory == NULL) {
161		delete rootNode;
162		RETURN_ERROR(B_BAD_DATA);
163	}
164
165	error = PublishNode(fRootDirectory, 0);
166	if (error != B_OK) {
167		delete fRootDirectory;
168		fRootDirectory = NULL;
169		return error;
170	}
171
172	return B_OK;
173}
174
175
176void
177Volume::Unmount()
178{
179	status_t error = block_cache_sync(fBlockCache);
180	if (error != B_OK) {
181		dprintf("checksumfs: Error: Failed to sync block cache when "
182			"unmounting!\n");
183	}
184}
185
186
187status_t
188Volume::Initialize(const char* name)
189{
190	fName = strdup(name);
191	if (fName == NULL)
192		RETURN_ERROR(B_NO_MEMORY);
193
194	Transaction transaction(this);
195	status_t error = transaction.Start();
196	if (error != B_OK)
197		RETURN_ERROR(error);
198
199	error = fBlockAllocator->Initialize(transaction);
200	if (error != B_OK)
201		RETURN_ERROR(error);
202
203	// create the root directory
204	error = CreateDirectory(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH,
205		transaction, fRootDirectory);
206	if (error != B_OK)
207		RETURN_ERROR(error);
208
209	transaction.KeepNode(fRootDirectory);
210	fRootDirectory->SetHardLinks(1);
211
212	// write the superblock
213	Block block;
214	if (!block.GetZero(this, kCheckSumFSSuperBlockOffset / B_PAGE_SIZE,
215			transaction)) {
216		RETURN_ERROR(B_ERROR);
217	}
218
219	SuperBlock* superBlock = (SuperBlock*)block.Data();
220	superBlock->Initialize(this);
221
222	block.Put();
223
224	// commit the transaction and flush the block cache
225	error = transaction.Commit();
226	if (error != B_OK)
227		RETURN_ERROR(error);
228
229	return block_cache_sync(fBlockCache);
230}
231
232
233void
234Volume::GetInfo(fs_info& info)
235{
236	MutexLocker locker(fLock);
237
238	info.flags = fFlags;
239	info.block_size = B_PAGE_SIZE;
240	info.io_size = B_PAGE_SIZE * 16;	// random value
241	info.total_blocks = fTotalBlocks;
242	info.free_blocks = fBlockAllocator->FreeBlocks();
243	info.total_nodes = 1;	// phew, who cares?
244	info.free_nodes = info.free_blocks;
245	strlcpy(info.volume_name, fName, sizeof(info.volume_name));
246}
247
248
249status_t
250Volume::NewNode(Node* node)
251{
252	return new_vnode(fFSVolume, node->BlockIndex(), node, &gCheckSumFSVnodeOps);
253}
254
255
256status_t
257Volume::PublishNode(Node* node, uint32 flags)
258{
259	return publish_vnode(fFSVolume, node->BlockIndex(), node,
260		&gCheckSumFSVnodeOps, node->Mode(), flags);
261}
262
263
264status_t
265Volume::GetNode(uint64 blockIndex, Node*& _node)
266{
267	return get_vnode(fFSVolume, blockIndex, (void**)&_node);
268}
269
270
271status_t
272Volume::PutNode(Node* node)
273{
274	return put_vnode(fFSVolume, node->BlockIndex());
275}
276
277
278status_t
279Volume::RemoveNode(Node* node)
280{
281	return remove_vnode(fFSVolume, node->BlockIndex());
282}
283
284
285status_t
286Volume::UnremoveNode(Node* node)
287{
288	return unremove_vnode(fFSVolume, node->BlockIndex());
289}
290
291
292status_t
293Volume::ReadNode(uint64 blockIndex, Node*& _node)
294{
295	if (blockIndex == 0 || blockIndex >= fTotalBlocks)
296		return B_BAD_VALUE;
297
298	// load the node's block
299	Block block;
300	if (!block.GetReadable(this, blockIndex))
301		return B_ERROR;
302
303	checksumfs_node* nodeData = (checksumfs_node*)block.Data();
304
305	// create the Node object
306	Node* node;
307	switch (nodeData->mode & S_IFMT) {
308			// TODO: Don't directly access mode!
309		case S_IFDIR:
310			node = new(std::nothrow) Directory(this, blockIndex, *nodeData);
311			break;
312		case S_IFREG:
313			node = new(std::nothrow) File(this, blockIndex, *nodeData);
314			break;
315		case S_IFLNK:
316			node = new(std::nothrow) SymLink(this, blockIndex, *nodeData);
317			break;
318		default:
319			node = new(std::nothrow) Node(this, blockIndex, *nodeData);
320			break;
321	}
322
323	if (node == NULL)
324		return B_NO_MEMORY;
325
326	// TODO: Sanity check the node!
327
328	_node = node;
329	return B_OK;
330}
331
332
333status_t
334Volume::CreateDirectory(mode_t mode, Transaction& transaction,
335	Directory*& _directory)
336{
337	Directory* directory = new(std::nothrow) Directory(this,
338		(mode & S_IUMSK) | S_IFDIR);
339
340	status_t error = _CreateNode(directory, transaction);
341	if (error != B_OK)
342		return error;
343
344	_directory = directory;
345	return B_OK;
346}
347
348
349status_t
350Volume::CreateFile(mode_t mode, Transaction& transaction, File*& _file)
351{
352	File* file = new(std::nothrow) File(this, (mode & S_IUMSK) | S_IFREG);
353
354	status_t error = _CreateNode(file, transaction);
355	if (error != B_OK)
356		return error;
357
358	_file = file;
359	return B_OK;
360}
361
362
363status_t
364Volume::CreateSymLink(mode_t mode, Transaction& transaction, SymLink*& _symLink)
365{
366	SymLink* symLink = new(std::nothrow) SymLink(this,
367		(mode & S_IUMSK) | S_IFLNK);
368
369	status_t error = _CreateNode(symLink, transaction);
370	if (error != B_OK)
371		return error;
372
373	_symLink = symLink;
374	return B_OK;
375}
376
377
378status_t
379Volume::DeleteNode(Node* node)
380{
381	// let the node delete data associated with it
382	node->DeletingNode();
383
384	uint64 blockIndex = node->BlockIndex();
385
386	// delete the node itself
387	Transaction transaction(this);
388	status_t error = transaction.Start();
389	if (error == B_OK) {
390		error = fBlockAllocator->Free(node->BlockIndex(), 1, transaction);
391		if (error == B_OK) {
392			error = transaction.Commit();
393			if (error != B_OK) {
394				ERROR("Failed to commit transaction for deleting node at %"
395					B_PRIu64 "\n", blockIndex);
396			}
397		} else {
398			ERROR("Failed to free block for node at %" B_PRIu64 "\n",
399				blockIndex);
400		}
401	} else {
402		ERROR("Failed to start transaction for deleting node at %" B_PRIu64
403			"\n", blockIndex);
404	}
405
406	transaction.Abort();
407
408	delete node;
409
410	return error;
411}
412
413
414status_t
415Volume::SetName(const char* name)
416{
417	if (name == NULL || strlen(name) > kCheckSumFSNameLength)
418		return B_BAD_VALUE;
419
420	// clone the name
421	char* newName = strdup(name);
422	if (newName == NULL)
423		return B_NO_MEMORY;
424	MemoryDeleter newNameDeleter(newName);
425
426	// start a transaction
427	Transaction transaction(this);
428	status_t error = transaction.Start();
429	if (error != B_OK)
430		return error;
431
432	// we lock the volume now, to keep the locking order (transaction -> volume)
433	MutexLocker locker(fLock);
434
435	// update the superblock
436	Block block;
437	if (!block.GetWritable(this, kCheckSumFSSuperBlockOffset / B_PAGE_SIZE,
438			transaction)) {
439		return B_ERROR;
440	}
441
442	SuperBlock* superBlock = (SuperBlock*)block.Data();
443	superBlock->SetName(newName);
444
445	block.Put();
446
447	// commit the transaction
448	error = transaction.Commit();
449	if (error != B_OK)
450		return error;
451
452	// Everything went fine. We can replace the name. Since we still have the
453	// volume lock, there's no race condition.
454	free(fName);
455	fName = (char*)newNameDeleter.Detach();
456
457	return B_OK;
458}
459
460
461status_t
462Volume::_Init(uint64 totalBlocks)
463{
464	fTotalBlocks = totalBlocks;
465	if (fTotalBlocks * B_PAGE_SIZE < kCheckSumFSMinSize)
466		RETURN_ERROR(B_BAD_VALUE);
467
468	// create a block cache
469	fBlockCache = block_cache_create(fFD, fTotalBlocks, B_PAGE_SIZE,
470		IsReadOnly());
471	if (fBlockCache == NULL)
472		RETURN_ERROR(B_NO_MEMORY);
473
474	// create the block allocator
475	fBlockAllocator = new(std::nothrow) BlockAllocator(this);
476	if (fBlockAllocator == NULL)
477		RETURN_ERROR(B_NO_MEMORY);
478
479	return B_OK;
480}
481
482
483status_t
484Volume::_CreateNode(Node* node, Transaction& transaction)
485{
486	if (node == NULL)
487		return B_NO_MEMORY;
488
489	ObjectDeleter<Node> nodeDeleter(node);
490
491	// allocate a free block
492	AllocatedBlock allocatedBlock(fBlockAllocator, transaction);
493	status_t error = allocatedBlock.Allocate();
494	if (error != B_OK)
495		return error;
496
497	// clear the block
498	{
499		Block block;
500		if (!block.GetZero(this, allocatedBlock.Index(), transaction))
501			return B_ERROR;
502	}
503
504	node->SetBlockIndex(allocatedBlock.Index());
505
506	// attach the node to the transaction
507	error = transaction.AddNode(node, TRANSACTION_DELETE_NODE);
508	if (error != B_OK)
509		return error;
510
511	allocatedBlock.Detach();
512	nodeDeleter.Detach();
513
514	return B_OK;
515}
516