xref: /haiku/src/add-ons/kernel/file_systems/exfat/Volume.cpp (revision e5d65858f2361fe0552495b61620c84dcee6bc00)
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 "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 superblock
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 superblock!\n");
506  		return B_BAD_VALUE;
507  	}
508  
509  	return B_OK;
510  }
511  
512