xref: /haiku/src/add-ons/kernel/file_systems/exfat/Volume.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
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 //! Super block, 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 "CachedBlock.h"
25 #include "encodings.h"
26 #include "Inode.h"
27 
28 
29 //#define TRACE_EXFAT
30 #ifdef TRACE_EXFAT
31 #	define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x)
32 #else
33 #	define TRACE(x...) ;
34 #endif
35 #	define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x)
36 
37 
38 class DeviceOpener {
39 public:
40 								DeviceOpener(int fd, int mode);
41 								DeviceOpener(const char* device, int mode);
42 								~DeviceOpener();
43 
44 			int					Open(const char* device, int mode);
45 			int					Open(int fd, int mode);
46 			void*				InitCache(off_t numBlocks, uint32 blockSize);
47 			void				RemoveCache(bool allowWrites);
48 
49 			void				Keep();
50 
51 			int					Device() const { return fDevice; }
52 			int					Mode() const { return fMode; }
53 			bool				IsReadOnly() const
54 									{ return _IsReadOnly(fMode); }
55 
56 			status_t			GetSize(off_t* _size,
57 									uint32* _blockSize = NULL);
58 
59 private:
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 
71 DeviceOpener::DeviceOpener(const char* device, int mode)
72 	:
73 	fBlockCache(NULL)
74 {
75 	Open(device, mode);
76 }
77 
78 
79 DeviceOpener::DeviceOpener(int fd, int mode)
80 	:
81 	fBlockCache(NULL)
82 {
83 	Open(fd, mode);
84 }
85 
86 
87 DeviceOpener::~DeviceOpener()
88 {
89 	if (fDevice >= 0) {
90 		RemoveCache(false);
91 		close(fDevice);
92 	}
93 }
94 
95 
96 int
97 DeviceOpener::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 
128 int
129 DeviceOpener::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 
141 void*
142 DeviceOpener::InitCache(off_t numBlocks, uint32 blockSize)
143 {
144 	return fBlockCache = block_cache_create(fDevice, numBlocks, blockSize,
145 		IsReadOnly());
146 }
147 
148 
149 void
150 DeviceOpener::RemoveCache(bool allowWrites)
151 {
152 	if (fBlockCache == NULL)
153 		return;
154 
155 	block_cache_delete(fBlockCache, allowWrites);
156 	fBlockCache = NULL;
157 }
158 
159 
160 void
161 DeviceOpener::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 */
170 status_t
171 DeviceOpener::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 
202 class LabelVisitor : public EntryVisitor {
203 public:
204 								LabelVisitor(Volume* volume);
205 			bool				VisitLabel(struct exfat_entry*);
206 private:
207 			Volume*				fVolume;
208 };
209 
210 
211 LabelVisitor::LabelVisitor(Volume* volume)
212 	:
213 	fVolume(volume)
214 {
215 }
216 
217 
218 bool
219 LabelVisitor::VisitLabel(struct exfat_entry* entry)
220 {
221 	dprintf("LabelVisitor::VisitLabel()\n");
222 	char utfName[30];
223 	size_t utfLength = 30;
224 	unicode_to_utf8((const uchar*)entry->name_label.name,
225 		entry->name_label.length * 2, (uint8*)utfName, &utfLength);
226 	fVolume->SetName(utfName);
227 	return true;
228 }
229 
230 
231 //	#pragma mark -
232 
233 
234 bool
235 exfat_super_block::IsValid()
236 {
237 	// TODO: check some more values!
238 	if (strncmp(filesystem, EXFAT_SUPER_BLOCK_MAGIC, sizeof(filesystem)) != 0)
239 		return false;
240 	if (signature != 0xaa55)
241 		return false;
242 	if (jump_boot[0] != 0xeb || jump_boot[1] != 0x76 || jump_boot[2] != 0x90)
243 		return false;
244 	if (version_minor != 0 || version_major != 1)
245 		return false;
246 
247 	return true;
248 }
249 
250 
251 //	#pragma mark -
252 
253 
254 Volume::Volume(fs_volume* volume)
255 	:
256 	fFSVolume(volume),
257 	fFlags(0),
258 	fRootNode(NULL),
259 	fNextId(1)
260 {
261 	mutex_init(&fLock, "exfat volume");
262 	fInodesClusterTree = new InodesClusterTree;
263 	fInodesInoTree = new InodesInoTree;
264 	memset(fName, 0, sizeof(fName));
265 }
266 
267 
268 Volume::~Volume()
269 {
270 	TRACE("Volume destructor.\n");
271 	delete fInodesClusterTree;
272 	delete fInodesInoTree;
273 }
274 
275 
276 bool
277 Volume::IsValidSuperBlock()
278 {
279 	return fSuperBlock.IsValid();
280 }
281 
282 
283 const char*
284 Volume::Name() const
285 {
286 	return fName;
287 }
288 
289 
290 status_t
291 Volume::Mount(const char* deviceName, uint32 flags)
292 {
293 	flags |= B_MOUNT_READ_ONLY;
294 		// we only support read-only for now
295 
296 	if ((flags & B_MOUNT_READ_ONLY) != 0) {
297 		TRACE("Volume::Mount(): Read only\n");
298 	} else {
299 		TRACE("Volume::Mount(): Read write\n");
300 	}
301 
302 	DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0
303 		? O_RDONLY : O_RDWR);
304 	fDevice = opener.Device();
305 	if (fDevice < B_OK) {
306 		ERROR("Volume::Mount(): couldn't open device\n");
307 		return fDevice;
308 	}
309 
310 	if (opener.IsReadOnly())
311 		fFlags |= VOLUME_READ_ONLY;
312 
313 	// read the super block
314 	status_t status = Identify(fDevice, &fSuperBlock);
315 	if (status != B_OK) {
316 		ERROR("Volume::Mount(): Identify() failed\n");
317 		return status;
318 	}
319 
320 	fBlockSize = 1 << fSuperBlock.BlockShift();
321 	TRACE("block size %ld\n", fBlockSize);
322 	fEntriesPerBlock = (fBlockSize / sizeof(struct exfat_entry));
323 
324 	// check if the device size is large enough to hold the file system
325 	off_t diskSize;
326 	status = opener.GetSize(&diskSize);
327 	if (status != B_OK)
328 		return status;
329 	if (diskSize < (off_t)fSuperBlock.NumBlocks() << fSuperBlock.BlockShift())
330 		return B_BAD_VALUE;
331 
332 	fBlockCache = opener.InitCache(fSuperBlock.NumBlocks(), fBlockSize);
333 	if (fBlockCache == NULL)
334 		return B_ERROR;
335 
336 	TRACE("Volume::Mount(): Initialized block cache: %p\n", fBlockCache);
337 
338 	ino_t rootIno;
339 	// ready
340 	{
341 		Inode rootNode(this, fSuperBlock.RootDirCluster(), 0);
342 		rootIno = rootNode.ID();
343 	}
344 
345 	status = get_vnode(fFSVolume, rootIno, (void**)&fRootNode);
346 	if (status != B_OK) {
347 		ERROR("could not create root node: get_vnode() failed!\n");
348 		return status;
349 	}
350 
351 	TRACE("Volume::Mount(): Found root node: %lld (%s)\n", fRootNode->ID(),
352 		strerror(fRootNode->InitCheck()));
353 
354 	// all went fine
355 	opener.Keep();
356 
357 	DirectoryIterator iterator(fRootNode);
358 	LabelVisitor visitor(this);
359 	iterator.Iterate(visitor);
360 
361 	if (fName[0] == '\0') {
362 		// generate a more or less descriptive volume name
363 		off_t divisor = 1ULL << 40;
364 		char unit = 'T';
365 		if (diskSize < divisor) {
366 			divisor = 1UL << 30;
367 			unit = 'G';
368 			if (diskSize < divisor) {
369 				divisor = 1UL << 20;
370 				unit = 'M';
371 			}
372 		}
373 
374 		double size = double((10 * diskSize + divisor - 1) / divisor);
375 			// %g in the kernel does not support precision...
376 
377 		snprintf(fName, sizeof(fName), "%g %cB ExFAT Volume",
378 			size / 10, unit);
379 	}
380 
381 	return B_OK;
382 }
383 
384 
385 status_t
386 Volume::Unmount()
387 {
388 	TRACE("Volume::Unmount()\n");
389 
390 	TRACE("Volume::Unmount(): Putting root node\n");
391 	put_vnode(fFSVolume, RootNode()->ID());
392 	TRACE("Volume::Unmount(): Deleting the block cache\n");
393 	block_cache_delete(fBlockCache, !IsReadOnly());
394 	TRACE("Volume::Unmount(): Closing device\n");
395 	close(fDevice);
396 
397 	TRACE("Volume::Unmount(): Done\n");
398 	return B_OK;
399 }
400 
401 
402 status_t
403 Volume::LoadSuperBlock()
404 {
405 	CachedBlock cached(this);
406 	const uint8* block = cached.SetTo(EXFAT_SUPER_BLOCK_OFFSET / fBlockSize);
407 
408 	if (block == NULL)
409 		return B_IO_ERROR;
410 
411 	memcpy(&fSuperBlock, block + EXFAT_SUPER_BLOCK_OFFSET % fBlockSize,
412 		sizeof(fSuperBlock));
413 
414 	return B_OK;
415 }
416 
417 
418 status_t
419 Volume::ClusterToBlock(cluster_t cluster, fsblock_t &block)
420 {
421 	block = ((cluster - 2) << SuperBlock().BlocksPerClusterShift())
422 		+ SuperBlock().FirstDataBlock();
423 	TRACE("Volume::ClusterToBlock() cluster %lu %u %lu: %llu, %lu\n", cluster,
424 		SuperBlock().BlocksPerClusterShift(), SuperBlock().FirstDataBlock(),
425 		block, SuperBlock().FirstFatBlock());
426 	return B_OK;
427 }
428 
429 
430 cluster_t
431 Volume::NextCluster(cluster_t _cluster)
432 {
433 	uint32 clusterPerBlock = fBlockSize / sizeof(cluster_t);
434 	CachedBlock block(this);
435 	fsblock_t blockNum = SuperBlock().FirstFatBlock()
436 		+ _cluster / clusterPerBlock;
437 	cluster_t *cluster = (cluster_t *)block.SetTo(blockNum);
438 	cluster += _cluster % clusterPerBlock;
439 	TRACE("Volume::NextCluster() cluster %lu next %lu\n", _cluster, *cluster);
440 	return *cluster;
441 }
442 
443 
444 Inode*
445 Volume::FindInode(ino_t id)
446 {
447 	return fInodesInoTree->Lookup(id);
448 }
449 
450 
451 Inode*
452 Volume::FindInode(cluster_t cluster)
453 {
454 	return fInodesClusterTree->Lookup(cluster);
455 }
456 
457 
458 ino_t
459 Volume::GetIno(cluster_t cluster, uint32 offset, ino_t parent)
460 {
461 	struct node_key key;
462 	key.cluster = cluster;
463 	key.offset = offset;
464 	struct node* node = fNodeTree.Lookup(key);
465 	if (node != NULL) {
466 		TRACE("Volume::GetIno() cached cluster %lu offset %lu ino %" B_PRIdINO
467 			"\n", cluster, offset, node->ino);
468 		return node->ino;
469 	}
470 	node = new struct node();
471 	node->key = key;
472 	node->ino = _NextID();
473 	node->parent = parent;
474 	fNodeTree.Insert(node);
475 	fInoTree.Insert(node);
476 	TRACE("Volume::GetIno() new cluster %lu offset %lu ino %" B_PRIdINO "\n",
477 		cluster, offset, node->ino);
478 	return node->ino;
479 }
480 
481 
482 struct node_key*
483 Volume::GetNode(ino_t ino, ino_t &parent)
484 {
485 	struct node* node = fInoTree.Lookup(ino);
486 	if (node != NULL) {
487 		parent = node->parent;
488 		return &node->key;
489 	}
490 	return NULL;
491 }
492 
493 
494 //	#pragma mark - Disk scanning and initialization
495 
496 
497 /*static*/ status_t
498 Volume::Identify(int fd, exfat_super_block* superBlock)
499 {
500 	if (read_pos(fd, EXFAT_SUPER_BLOCK_OFFSET, superBlock,
501 			sizeof(exfat_super_block)) != sizeof(exfat_super_block))
502 		return B_IO_ERROR;
503 
504 	if (!superBlock->IsValid()) {
505 		ERROR("invalid super block!\n");
506 		return B_BAD_VALUE;
507 	}
508 
509 	return B_OK;
510 }
511 
512