xref: /haiku/src/add-ons/kernel/file_systems/exfat/Volume.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
1 /*
2  * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2011, Jérôme Duval, korli@users.berlios.de.
4  * Copyright 2014 Haiku, Inc. All rights reserved.
5  *
6  * Distributed under the terms of the MIT License.
7  *
8  * Authors:
9  *		Axel Dörfler, axeld@pinc-software.de
10  *		Jérôme Duval, korli@users.berlios.de
11  *		John Scipione, jscipione@gmail.com
12  */
13 
14 
15 //! Superblock, mounting, etc.
16 
17 
18 #include "Volume.h"
19 
20 #include <errno.h>
21 #include <unistd.h>
22 #include <new>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include <fs_cache.h>
28 #include <fs_volume.h>
29 
30 #include <util/AutoLock.h>
31 
32 #include "CachedBlock.h"
33 #include "Inode.h"
34 #include "Utility.h"
35 
36 
37 //#define TRACE_EXFAT
38 #ifdef TRACE_EXFAT
39 #	define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x)
40 #else
41 #	define TRACE(x...) ;
42 #endif
43 #	define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x)
44 
45 
46 class DeviceOpener {
47 public:
48 								DeviceOpener(int fd, int mode);
49 								DeviceOpener(const char* device, int mode);
50 								~DeviceOpener();
51 
52 			int					Open(const char* device, int mode);
53 			int					Open(int fd, int mode);
54 			void*				InitCache(off_t numBlocks, uint32 blockSize);
55 			void				RemoveCache(bool allowWrites);
56 
57 			void				Keep();
58 
59 			int					Device() const { return fDevice; }
60 			int					Mode() const { return fMode; }
61 			bool				IsReadOnly() const
62 									{ return _IsReadOnly(fMode); }
63 
64 			status_t			GetSize(off_t* _size,
65 									uint32* _blockSize = NULL);
66 
67 private:
68 	static	bool				_IsReadOnly(int mode)
69 									{ return (mode & O_RWMASK) == O_RDONLY;}
70 	static	bool				_IsReadWrite(int mode)
71 									{ return (mode & O_RWMASK) == O_RDWR;}
72 
73 			int					fDevice;
74 			int					fMode;
75 			void*				fBlockCache;
76 };
77 
78 
79 //	#pragma mark - DeviceOpener
80 
81 
82 DeviceOpener::DeviceOpener(const char* device, int mode)
83 	:
84 	fBlockCache(NULL)
85 {
86 	Open(device, mode);
87 }
88 
89 
90 DeviceOpener::DeviceOpener(int fd, int mode)
91 	:
92 	fBlockCache(NULL)
93 {
94 	Open(fd, mode);
95 }
96 
97 
98 DeviceOpener::~DeviceOpener()
99 {
100 	if (fDevice >= 0) {
101 		RemoveCache(false);
102 		close(fDevice);
103 	}
104 }
105 
106 
107 int
108 DeviceOpener::Open(const char* device, int mode)
109 {
110 	fDevice = open(device, mode | O_NOCACHE);
111 	if (fDevice < 0)
112 		fDevice = errno;
113 
114 	if (fDevice < 0 && _IsReadWrite(mode)) {
115 		// try again to open read-only (don't rely on a specific error code)
116 		return Open(device, O_RDONLY | O_NOCACHE);
117 	}
118 
119 	if (fDevice >= 0) {
120 		// opening succeeded
121 		fMode = mode;
122 		if (_IsReadWrite(mode)) {
123 			// check out if the device really allows for read/write access
124 			device_geometry geometry;
125 			if (!ioctl(fDevice, B_GET_GEOMETRY, &geometry)) {
126 				if (geometry.read_only) {
127 					// reopen device read-only
128 					close(fDevice);
129 					return Open(device, O_RDONLY | O_NOCACHE);
130 				}
131 			}
132 		}
133 	}
134 
135 	return fDevice;
136 }
137 
138 
139 int
140 DeviceOpener::Open(int fd, int mode)
141 {
142 	fDevice = dup(fd);
143 	if (fDevice < 0)
144 		return errno;
145 
146 	fMode = mode;
147 
148 	return fDevice;
149 }
150 
151 
152 void*
153 DeviceOpener::InitCache(off_t numBlocks, uint32 blockSize)
154 {
155 	return fBlockCache = block_cache_create(fDevice, numBlocks, blockSize,
156 		IsReadOnly());
157 }
158 
159 
160 void
161 DeviceOpener::RemoveCache(bool allowWrites)
162 {
163 	if (fBlockCache == NULL)
164 		return;
165 
166 	block_cache_delete(fBlockCache, allowWrites);
167 	fBlockCache = NULL;
168 }
169 
170 
171 void
172 DeviceOpener::Keep()
173 {
174 	fDevice = -1;
175 }
176 
177 
178 /*!	Returns the size of the device in bytes. It uses B_GET_GEOMETRY
179 	to compute the size, or fstat() if that failed.
180 */
181 status_t
182 DeviceOpener::GetSize(off_t* _size, uint32* _blockSize)
183 {
184 	device_geometry geometry;
185 	if (ioctl(fDevice, B_GET_GEOMETRY, &geometry) < 0) {
186 		// maybe it's just a file
187 		struct stat stat;
188 		if (fstat(fDevice, &stat) < 0)
189 			return B_ERROR;
190 
191 		if (_size)
192 			*_size = stat.st_size;
193 		if (_blockSize)	// that shouldn't cause us any problems
194 			*_blockSize = 512;
195 
196 		return B_OK;
197 	}
198 
199 	if (_size) {
200 		*_size = 1ULL * geometry.head_count * geometry.cylinder_count
201 			* geometry.sectors_per_track * geometry.bytes_per_sector;
202 	}
203 	if (_blockSize)
204 		*_blockSize = geometry.bytes_per_sector;
205 
206 	return B_OK;
207 }
208 
209 
210 //	#pragma mark - LabelVisitor
211 
212 
213 class LabelVisitor : public EntryVisitor {
214 public:
215 								LabelVisitor(Volume* volume);
216 			bool				VisitLabel(struct exfat_entry*);
217 private:
218 			Volume*				fVolume;
219 };
220 
221 
222 LabelVisitor::LabelVisitor(Volume* volume)
223 	:
224 	fVolume(volume)
225 {
226 }
227 
228 
229 bool
230 LabelVisitor::VisitLabel(struct exfat_entry* entry)
231 {
232 	TRACE("LabelVisitor::VisitLabel()\n");
233 	char name[B_FILE_NAME_LENGTH];
234 	status_t result = get_volume_name(entry, name, sizeof(name));
235 	if (result != B_OK)
236 		return false;
237 
238 	fVolume->SetName(name);
239 	return true;
240 }
241 
242 
243 //	#pragma mark - exfat_super_block::IsValid()
244 
245 
246 bool
247 exfat_super_block::IsValid()
248 {
249 	// TODO: check some more values!
250 	if (strncmp(filesystem, EXFAT_SUPER_BLOCK_MAGIC, sizeof(filesystem)) != 0)
251 		return false;
252 	if (signature != 0xaa55)
253 		return false;
254 	if (jump_boot[0] != 0xeb || jump_boot[1] != 0x76 || jump_boot[2] != 0x90)
255 		return false;
256 	if (version_minor != 0 || version_major != 1)
257 		return false;
258 
259 	return true;
260 }
261 
262 
263 //	#pragma mark - Volume
264 
265 
266 Volume::Volume(fs_volume* volume)
267 	:
268 	fFSVolume(volume),
269 	fFlags(0),
270 	fRootNode(NULL),
271 	fNextId(1)
272 {
273 	mutex_init(&fLock, "exfat volume");
274 	fInodesClusterTree = new InodesClusterTree;
275 	fInodesInoTree = new InodesInoTree;
276 	memset(fName, 0, sizeof(fName));
277 }
278 
279 
280 Volume::~Volume()
281 {
282 	TRACE("Volume destructor.\n");
283 	delete fInodesClusterTree;
284 	delete fInodesInoTree;
285 }
286 
287 
288 bool
289 Volume::IsValidSuperBlock()
290 {
291 	return fSuperBlock.IsValid();
292 }
293 
294 
295 const char*
296 Volume::Name() const
297 {
298 	return fName;
299 }
300 
301 
302 status_t
303 Volume::Mount(const char* deviceName, uint32 flags)
304 {
305 	flags |= B_MOUNT_READ_ONLY;
306 		// we only support read-only for now
307 
308 	if ((flags & B_MOUNT_READ_ONLY) != 0) {
309 		TRACE("Volume::Mount(): Read only\n");
310 	} else {
311 		TRACE("Volume::Mount(): Read write\n");
312 	}
313 
314 	DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0
315 		? O_RDONLY : O_RDWR);
316 	fDevice = opener.Device();
317 	if (fDevice < B_OK) {
318 		ERROR("Volume::Mount(): couldn't open device\n");
319 		return fDevice;
320 	}
321 
322 	if (opener.IsReadOnly())
323 		fFlags |= VOLUME_READ_ONLY;
324 
325 	// read the superblock
326 	status_t status = Identify(fDevice, &fSuperBlock);
327 	if (status != B_OK) {
328 		ERROR("Volume::Mount(): Identify() failed\n");
329 		return status;
330 	}
331 
332 	fBlockSize = 1 << fSuperBlock.BlockShift();
333 	TRACE("block size %" B_PRIu32 "\n", fBlockSize);
334 	fEntriesPerBlock = (fBlockSize / sizeof(struct exfat_entry));
335 
336 	// check that the device is large enough to hold the partition
337 	off_t deviceSize;
338 	status = opener.GetSize(&deviceSize);
339 	if (status != B_OK)
340 		return status;
341 
342 	off_t partitionSize = (off_t)fSuperBlock.NumBlocks()
343 		<< fSuperBlock.BlockShift();
344 	if (deviceSize < partitionSize)
345 		return B_BAD_VALUE;
346 
347 	fBlockCache = opener.InitCache(fSuperBlock.NumBlocks(), fBlockSize);
348 	if (fBlockCache == NULL)
349 		return B_ERROR;
350 
351 	TRACE("Volume::Mount(): Initialized block cache: %p\n", fBlockCache);
352 
353 	ino_t rootIno;
354 	// ready
355 	{
356 		Inode rootNode(this, fSuperBlock.RootDirCluster(), 0);
357 		rootIno = rootNode.ID();
358 	}
359 
360 	status = get_vnode(fFSVolume, rootIno, (void**)&fRootNode);
361 	if (status != B_OK) {
362 		ERROR("could not create root node: get_vnode() failed!\n");
363 		return status;
364 	}
365 
366 	TRACE("Volume::Mount(): Found root node: %" B_PRIdINO " (%s)\n",
367 		fRootNode->ID(), strerror(fRootNode->InitCheck()));
368 
369 	// all went fine
370 	opener.Keep();
371 
372 	DirectoryIterator iterator(fRootNode);
373 	LabelVisitor visitor(this);
374 	iterator.Iterate(visitor);
375 
376 	if (fName[0] == '\0')
377 		get_default_volume_name(partitionSize, fName, sizeof(fName));
378 
379 	return B_OK;
380 }
381 
382 
383 status_t
384 Volume::Unmount()
385 {
386 	TRACE("Volume::Unmount()\n");
387 
388 	TRACE("Volume::Unmount(): Putting root node\n");
389 	put_vnode(fFSVolume, RootNode()->ID());
390 	TRACE("Volume::Unmount(): Deleting the block cache\n");
391 	block_cache_delete(fBlockCache, !IsReadOnly());
392 	TRACE("Volume::Unmount(): Closing device\n");
393 	close(fDevice);
394 
395 	TRACE("Volume::Unmount(): Done\n");
396 	return B_OK;
397 }
398 
399 
400 status_t
401 Volume::LoadSuperBlock()
402 {
403 	CachedBlock cached(this);
404 	const uint8* block = cached.SetTo(EXFAT_SUPER_BLOCK_OFFSET / fBlockSize);
405 
406 	if (block == NULL)
407 		return B_IO_ERROR;
408 
409 	memcpy(&fSuperBlock, block + EXFAT_SUPER_BLOCK_OFFSET % fBlockSize,
410 		sizeof(fSuperBlock));
411 
412 	return B_OK;
413 }
414 
415 
416 status_t
417 Volume::ClusterToBlock(cluster_t cluster, fsblock_t &block)
418 {
419 	if (cluster < 2)
420 		return B_BAD_VALUE;
421 	block = ((cluster - 2) << SuperBlock().BlocksPerClusterShift())
422 		+ SuperBlock().FirstDataBlock();
423 	TRACE("Volume::ClusterToBlock() cluster %" B_PRIu32 " %u %" B_PRIu32 ": %"
424 		B_PRIu64 ", %" B_PRIu32 "\n", cluster,
425 		SuperBlock().BlocksPerClusterShift(), SuperBlock().FirstDataBlock(),
426 		block, SuperBlock().FirstFatBlock());
427 	return B_OK;
428 }
429 
430 
431 cluster_t
432 Volume::NextCluster(cluster_t _cluster)
433 {
434 	uint32 clusterPerBlock = fBlockSize / sizeof(cluster_t);
435 	CachedBlock block(this);
436 	fsblock_t blockNum = SuperBlock().FirstFatBlock()
437 		+ _cluster / clusterPerBlock;
438 	cluster_t *cluster = (cluster_t *)block.SetTo(blockNum);
439 	cluster += _cluster % clusterPerBlock;
440 	TRACE("Volume::NextCluster() cluster %" B_PRIu32 " next %" B_PRIu32 "\n",
441 		_cluster, *cluster);
442 	return *cluster;
443 }
444 
445 
446 Inode*
447 Volume::FindInode(ino_t id)
448 {
449 	return fInodesInoTree->Lookup(id);
450 }
451 
452 
453 Inode*
454 Volume::FindInode(cluster_t cluster)
455 {
456 	return fInodesClusterTree->Lookup(cluster);
457 }
458 
459 
460 ino_t
461 Volume::GetIno(cluster_t cluster, uint32 offset, ino_t parent)
462 {
463 	struct node_key key;
464 	key.cluster = cluster;
465 	key.offset = offset;
466 
467 	MutexLocker locker(fLock);
468 	struct node* node = fNodeTree.Lookup(key);
469 	if (node != NULL) {
470 		TRACE("Volume::GetIno() cached cluster %" B_PRIu32 " offset %" B_PRIu32
471 			" ino %" B_PRIdINO "\n", cluster, offset, node->ino);
472 		return node->ino;
473 	}
474 	node = new struct node();
475 	node->key = key;
476 	node->ino = _NextID();
477 	node->parent = parent;
478 	fNodeTree.Insert(node);
479 	fInoTree.Insert(node);
480 	TRACE("Volume::GetIno() new cluster %" B_PRIu32 " offset %" B_PRIu32
481 		" ino %" B_PRIdINO "\n", cluster, offset, node->ino);
482 	return node->ino;
483 }
484 
485 
486 struct node_key*
487 Volume::GetNode(ino_t ino, ino_t &parent)
488 {
489 	MutexLocker locker(fLock);
490 	struct node* node = fInoTree.Lookup(ino);
491 	if (node != NULL) {
492 		parent = node->parent;
493 		return &node->key;
494 	}
495 	return NULL;
496 }
497 
498 
499 //	#pragma mark - Disk scanning and initialization
500 
501 
502 /*static*/ status_t
503 Volume::Identify(int fd, exfat_super_block* superBlock)
504 {
505 	if (read_pos(fd, EXFAT_SUPER_BLOCK_OFFSET, superBlock,
506 			sizeof(exfat_super_block)) != sizeof(exfat_super_block))
507 		return B_IO_ERROR;
508 
509 	if (!superBlock->IsValid()) {
510 		ERROR("invalid superblock!\n");
511 		return B_BAD_VALUE;
512 	}
513 
514 	return B_OK;
515 }
516