1/*
2 * Copyright 2011, Jérôme Duval, korli@users.berlios.de.
3 * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de.
4 * This file may be used under the terms of the MIT License.
5 */
6
7
8//! Superblock, mounting, etc.
9
10
11#include "Volume.h"
12
13#include <errno.h>
14#include <new>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18
19#include <fs_cache.h>
20#include <fs_volume.h>
21
22#include <util/AutoLock.h>
23
24#include "BPlusTree.h"
25#include "CachedBlock.h"
26#include "Chunk.h"
27#include "Inode.h"
28
29
30//#define TRACE_BTRFS
31#ifdef TRACE_BTRFS
32#	define TRACE(x...) dprintf("\33[34mbtrfs:\33[0m " x)
33#else
34#	define TRACE(x...) ;
35#endif
36#	define ERROR(x...) dprintf("\33[34mbtrfs:\33[0m " x)
37
38
39class DeviceOpener {
40public:
41								DeviceOpener(int fd, int mode);
42								DeviceOpener(const char* device, int mode);
43								~DeviceOpener();
44
45			int					Open(const char* device, int mode);
46			int					Open(int fd, int mode);
47			void*				InitCache(off_t numBlocks, uint32 blockSize);
48			void				RemoveCache(bool allowWrites);
49
50			void				Keep();
51
52			int					Device() const { return fDevice; }
53			int					Mode() const { return fMode; }
54			bool				IsReadOnly() const
55									{ return _IsReadOnly(fMode); }
56
57			status_t			GetSize(off_t* _size, uint32* _blockSize = NULL);
58
59private:
60	static	bool				_IsReadOnly(int mode)
61									{ return (mode & O_RWMASK) == O_RDONLY;}
62	static	bool				_IsReadWrite(int mode)
63									{ return (mode & O_RWMASK) == O_RDWR;}
64
65			int					fDevice;
66			int					fMode;
67			void*				fBlockCache;
68};
69
70
71DeviceOpener::DeviceOpener(const char* device, int mode)
72	:
73	fBlockCache(NULL)
74{
75	Open(device, mode);
76}
77
78
79DeviceOpener::DeviceOpener(int fd, int mode)
80	:
81	fBlockCache(NULL)
82{
83	Open(fd, mode);
84}
85
86
87DeviceOpener::~DeviceOpener()
88{
89	if (fDevice >= 0) {
90		RemoveCache(false);
91		close(fDevice);
92	}
93}
94
95
96int
97DeviceOpener::Open(const char* device, int mode)
98{
99	fDevice = open(device, mode | O_NOCACHE);
100	if (fDevice < 0)
101		fDevice = errno;
102
103	if (fDevice < 0 && _IsReadWrite(mode)) {
104		// try again to open read-only (don't rely on a specific error code)
105		return Open(device, O_RDONLY | O_NOCACHE);
106	}
107
108	if (fDevice >= 0) {
109		// opening succeeded
110		fMode = mode;
111		if (_IsReadWrite(mode)) {
112			// check out if the device really allows for read/write access
113			device_geometry geometry;
114			if (!ioctl(fDevice, B_GET_GEOMETRY, &geometry)) {
115				if (geometry.read_only) {
116					// reopen device read-only
117					close(fDevice);
118					return Open(device, O_RDONLY | O_NOCACHE);
119				}
120			}
121		}
122	}
123
124	return fDevice;
125}
126
127
128int
129DeviceOpener::Open(int fd, int mode)
130{
131	fDevice = dup(fd);
132	if (fDevice < 0)
133		return errno;
134
135	fMode = mode;
136
137	return fDevice;
138}
139
140
141void*
142DeviceOpener::InitCache(off_t numBlocks, uint32 blockSize)
143{
144	return fBlockCache = block_cache_create(fDevice, numBlocks, blockSize,
145		IsReadOnly());
146}
147
148
149void
150DeviceOpener::RemoveCache(bool allowWrites)
151{
152	if (fBlockCache == NULL)
153		return;
154
155	block_cache_delete(fBlockCache, allowWrites);
156	fBlockCache = NULL;
157}
158
159
160void
161DeviceOpener::Keep()
162{
163	fDevice = -1;
164}
165
166
167/*!	Returns the size of the device in bytes. It uses B_GET_GEOMETRY
168	to compute the size, or fstat() if that failed.
169*/
170status_t
171DeviceOpener::GetSize(off_t* _size, uint32* _blockSize)
172{
173	device_geometry geometry;
174	if (ioctl(fDevice, B_GET_GEOMETRY, &geometry) < 0) {
175		// maybe it's just a file
176		struct stat stat;
177		if (fstat(fDevice, &stat) < 0)
178			return B_ERROR;
179
180		if (_size)
181			*_size = stat.st_size;
182		if (_blockSize)	// that shouldn't cause us any problems
183			*_blockSize = 512;
184
185		return B_OK;
186	}
187
188	if (_size) {
189		*_size = 1ULL * geometry.head_count * geometry.cylinder_count
190			* geometry.sectors_per_track * geometry.bytes_per_sector;
191	}
192	if (_blockSize)
193		*_blockSize = geometry.bytes_per_sector;
194
195	return B_OK;
196}
197
198
199//	#pragma mark -
200
201
202bool
203btrfs_super_block::IsValid()
204{
205	// TODO: check some more values!
206	if (strncmp(magic, BTRFS_SUPER_BLOCK_MAGIC, sizeof(magic)) != 0)
207		return false;
208
209	return true;
210}
211
212
213//	#pragma mark -
214
215
216Volume::Volume(fs_volume* volume)
217	:
218	fFSVolume(volume),
219	fFlags(0),
220	fChunk(NULL),
221	fChunkTree(NULL)
222{
223	mutex_init(&fLock, "btrfs volume");
224}
225
226
227Volume::~Volume()
228{
229	TRACE("Volume destructor.\n");
230}
231
232
233bool
234Volume::IsValidSuperBlock()
235{
236	return fSuperBlock.IsValid();
237}
238
239
240const char*
241Volume::Name() const
242{
243	if (fSuperBlock.label[0])
244		return fSuperBlock.label;
245
246	return fName;
247}
248
249
250status_t
251Volume::Mount(const char* deviceName, uint32 flags)
252{
253	flags |= B_MOUNT_READ_ONLY;
254		// we only support read-only for now
255
256	if ((flags & B_MOUNT_READ_ONLY) != 0) {
257		TRACE("Volume::Mount(): Read only\n");
258	} else {
259		TRACE("Volume::Mount(): Read write\n");
260	}
261
262	DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0
263		? O_RDONLY : O_RDWR);
264	fDevice = opener.Device();
265	if (fDevice < B_OK) {
266		ERROR("Volume::Mount(): couldn't open device\n");
267		return fDevice;
268	}
269
270	if (opener.IsReadOnly())
271		fFlags |= VOLUME_READ_ONLY;
272
273	// read the superblock
274	status_t status = Identify(fDevice, &fSuperBlock);
275	if (status != B_OK) {
276		ERROR("Volume::Mount(): Identify() failed\n");
277		return status;
278	}
279
280	fBlockSize = fSuperBlock.BlockSize();
281	TRACE("block size %ld\n", fBlockSize);
282
283	uint8* start = (uint8*)&fSuperBlock.system_chunk_array[0];
284	uint8* end = (uint8*)&fSuperBlock.system_chunk_array[2048];
285	while (start < end) {
286		struct btrfs_key* key = (struct btrfs_key*)start;
287		TRACE("system_chunk_array object_id 0x%llx offset 0x%llx type 0x%x\n",
288			key->ObjectID(), key->Offset(), key->Type());
289		if (key->Type() != BTRFS_KEY_TYPE_CHUNK_ITEM) {
290			break;
291		}
292
293		struct btrfs_chunk* chunk = (struct btrfs_chunk*)(key + 1);
294		fChunk = new(std::nothrow) Chunk(chunk, key->Offset());
295		if (fChunk == NULL)
296			return B_ERROR;
297		start += sizeof(struct btrfs_key) + fChunk->Size();
298	}
299
300	TRACE("Volume::Mount() generation: %lld\n",	fSuperBlock.Generation());
301	fsblock_t physical = 0;
302	FindBlock(fSuperBlock.Root(), physical);
303	TRACE("Volume::Mount() root: %lld (physical %lld)\n",
304		fSuperBlock.Root(), physical);
305	FindBlock(fSuperBlock.ChunkRoot(), physical);
306	TRACE("Volume::Mount() chunk_root: %lld (physical %lld)\n",
307		fSuperBlock.ChunkRoot(), physical);
308	FindBlock(fSuperBlock.LogRoot(), physical);
309	TRACE("Volume::Mount() log_root: %lld (physical %lld)\n",
310		fSuperBlock.LogRoot(), physical);
311
312	// check if the device size is large enough to hold the file system
313	off_t diskSize;
314	status = opener.GetSize(&diskSize);
315	if (status != B_OK)
316		return status;
317	if (diskSize < (off_t)fSuperBlock.TotalSize())
318		return B_BAD_VALUE;
319
320	fBlockCache = opener.InitCache(fSuperBlock.TotalSize() / fBlockSize,
321		fBlockSize);
322	if (fBlockCache == NULL)
323		return B_ERROR;
324
325	TRACE("Volume::Mount(): Initialized block cache: %p\n", fBlockCache);
326
327	fChunkTree = new(std::nothrow) BPlusTree(this, fSuperBlock.ChunkRoot());
328	if (fChunkTree == NULL)
329		return B_NO_MEMORY;
330
331	FindBlock(fSuperBlock.Root(), physical);
332	TRACE("Volume::Mount() root: %lld (physical %lld)\n",
333		fSuperBlock.Root(), physical);
334	FindBlock(fSuperBlock.ChunkRoot(), physical);
335	TRACE("Volume::Mount() chunk_root: %lld (physical %lld)\n",
336		fSuperBlock.ChunkRoot(), physical);
337	FindBlock(fSuperBlock.LogRoot(), physical);
338	TRACE("Volume::Mount() log_root: %lld (physical %lld)\n",
339		fSuperBlock.LogRoot(), physical);
340
341	fRootTree = new(std::nothrow) BPlusTree(this, fSuperBlock.Root());
342	if (fRootTree == NULL)
343		return B_NO_MEMORY;
344	TRACE("Volume::Mount(): Searching extent root\n");
345	struct btrfs_key search_key;
346	search_key.SetOffset(0);
347	search_key.SetType(BTRFS_KEY_TYPE_ROOT_ITEM);
348	search_key.SetObjectID(BTRFS_OBJECT_ID_EXTENT_TREE);
349	struct btrfs_root *root;
350	if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) {
351		ERROR("Volume::Mount(): Couldn't find extent root\n");
352		return B_ERROR;
353	}
354	TRACE("Volume::Mount(): Found extent root: %lld\n", root->BlockNum());
355	fExtentTree = new(std::nothrow) BPlusTree(this, root->BlockNum());
356	free(root);
357	if (fExtentTree == NULL)
358		return B_NO_MEMORY;
359
360	search_key.SetOffset(0);
361	search_key.SetObjectID(BTRFS_OBJECT_ID_FS_TREE);
362	if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) {
363		ERROR("Volume::Mount(): Couldn't find fs root\n");
364		return B_ERROR;
365	}
366	TRACE("Volume::Mount(): Found fs root: %lld\n", root->BlockNum());
367	fFSTree = new(std::nothrow) BPlusTree(this, root->BlockNum());
368	free(root);
369	if (fFSTree == NULL)
370		return B_NO_MEMORY;
371
372	search_key.SetOffset(0);
373	search_key.SetObjectID(BTRFS_OBJECT_ID_DEV_TREE);
374	if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) {
375		ERROR("Volume::Mount(): Couldn't find dev root\n");
376		return B_ERROR;
377	}
378	TRACE("Volume::Mount(): Found dev root: %lld\n", root->BlockNum());
379	fDevTree = new(std::nothrow) BPlusTree(this, root->BlockNum());
380	free(root);
381	if (fDevTree == NULL)
382		return B_NO_MEMORY;
383
384	search_key.SetOffset(0);
385	search_key.SetObjectID(BTRFS_OBJECT_ID_CHECKSUM_TREE);
386	if (fRootTree->FindNext(search_key, (void**)&root) != B_OK) {
387		ERROR("Volume::Mount(): Couldn't find checksum root\n");
388		return B_ERROR;
389	}
390	TRACE("Volume::Mount(): Found checksum root: %lld\n", root->BlockNum());
391	fChecksumTree = new(std::nothrow) BPlusTree(this, root->BlockNum());
392	free(root);
393	if (fChecksumTree == NULL)
394		return B_NO_MEMORY;
395
396	// ready
397	status = get_vnode(fFSVolume, BTRFS_OBJECT_ID_CHUNK_TREE,
398		(void**)&fRootNode);
399	if (status != B_OK) {
400		ERROR("could not create root node: get_vnode() failed!\n");
401		return status;
402	}
403
404	TRACE("Volume::Mount(): Found root node: %lld (%s)\n", fRootNode->ID(),
405		strerror(fRootNode->InitCheck()));
406
407	// all went fine
408	opener.Keep();
409
410	if (!fSuperBlock.label[0]) {
411		// generate a more or less descriptive volume name
412		off_t divisor = 1ULL << 40;
413		char unit = 'T';
414		if (diskSize < divisor) {
415			divisor = 1UL << 30;
416			unit = 'G';
417			if (diskSize < divisor) {
418				divisor = 1UL << 20;
419				unit = 'M';
420			}
421		}
422
423		double size = double((10 * diskSize + divisor - 1) / divisor);
424			// %g in the kernel does not support precision...
425
426		snprintf(fName, sizeof(fName), "%g %cB Btrfs Volume",
427			size / 10, unit);
428	}
429
430	return B_OK;
431}
432
433
434status_t
435Volume::Unmount()
436{
437	TRACE("Volume::Unmount()\n");
438	delete fExtentTree;
439	delete fChecksumTree;
440	delete fFSTree;
441	delete fDevTree;
442	fExtentTree = NULL;
443	fChecksumTree = NULL;
444	fFSTree = NULL;
445	fDevTree = NULL;
446
447	TRACE("Volume::Unmount(): Putting root node\n");
448	put_vnode(fFSVolume, RootNode()->ID());
449	TRACE("Volume::Unmount(): Deleting the block cache\n");
450	block_cache_delete(fBlockCache, !IsReadOnly());
451	TRACE("Volume::Unmount(): Closing device\n");
452	close(fDevice);
453
454	TRACE("Volume::Unmount(): Done\n");
455	return B_OK;
456}
457
458
459status_t
460Volume::LoadSuperBlock()
461{
462	CachedBlock cached(this);
463	const uint8* block = cached.SetTo(BTRFS_SUPER_BLOCK_OFFSET / fBlockSize);
464
465	if (block == NULL)
466		return B_IO_ERROR;
467
468	memcpy(&fSuperBlock, block + BTRFS_SUPER_BLOCK_OFFSET % fBlockSize,
469		sizeof(fSuperBlock));
470
471	return B_OK;
472}
473
474
475status_t
476Volume::FindBlock(off_t logical, fsblock_t &physicalBlock)
477{
478	off_t physical;
479	status_t status = FindBlock(logical, physical);
480	if (status != B_OK)
481		return status;
482	physicalBlock = physical / fBlockSize;
483	return B_OK;
484}
485
486
487status_t
488Volume::FindBlock(off_t logical, off_t &physical)
489{
490	if (fChunkTree == NULL
491		|| (logical >= fChunk->Offset() && logical < fChunk->End())) {
492		// try with fChunk
493		return fChunk->FindBlock(logical, physical);
494	}
495
496	struct btrfs_key search_key;
497	search_key.SetOffset(logical);
498	search_key.SetType(BTRFS_KEY_TYPE_CHUNK_ITEM);
499	search_key.SetObjectID(BTRFS_OBJECT_ID_CHUNK_TREE);
500	struct btrfs_chunk *chunk;
501	size_t chunk_length;
502	status_t status = fChunkTree->FindPrevious(search_key, (void**)&chunk,
503		&chunk_length);
504	if (status != B_OK)
505		return status;
506
507	Chunk _chunk(chunk, search_key.Offset());
508	free(chunk);
509	status = _chunk.FindBlock(logical, physical);
510	if (status != B_OK)
511			return status;
512	TRACE("Volume::FindBlock(): logical: %lld, physical: %lld\n", logical,
513		physical);
514	return B_OK;
515}
516
517
518//	#pragma mark - Disk scanning and initialization
519
520
521/*static*/ status_t
522Volume::Identify(int fd, btrfs_super_block* superBlock)
523{
524	if (read_pos(fd, BTRFS_SUPER_BLOCK_OFFSET, superBlock,
525			sizeof(btrfs_super_block)) != sizeof(btrfs_super_block))
526		return B_IO_ERROR;
527
528	if (!superBlock->IsValid()) {
529		ERROR("invalid superblock!\n");
530		return B_BAD_VALUE;
531	}
532
533	return B_OK;
534}
535
536