1/*
2 * Copyright 2011, J��r��me Duval, korli@users.berlios.de.
3 * Copyright 2008, Axel D��rfler, axeld@pinc-software.de.
4 * This file may be used under the terms of the MIT License.
5 */
6
7
8#include <dirent.h>
9#include <util/kernel_cpp.h>
10#include <string.h>
11
12#include <AutoDeleter.h>
13#include <fs_cache.h>
14#include <fs_info.h>
15#include <io_requests.h>
16#include <NodeMonitor.h>
17#include <util/AutoLock.h>
18
19#include "DirectoryIterator.h"
20#include "exfat.h"
21#include "Inode.h"
22#include "Utility.h"
23
24
25//#define TRACE_EXFAT
26#ifdef TRACE_EXFAT
27#	define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x)
28#else
29#	define TRACE(x...) ;
30#endif
31#define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x)
32
33
34#define EXFAT_IO_SIZE	65536
35
36
37struct identify_cookie {
38	exfat_super_block super_block;
39};
40
41
42//!	exfat_io() callback hook
43static status_t
44iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset,
45	size_t size, struct file_io_vec* vecs, size_t* _count)
46{
47	Inode* inode = (Inode*)cookie;
48
49	return file_map_translate(inode->Map(), offset, size, vecs, _count,
50		inode->GetVolume()->BlockSize());
51}
52
53
54//!	exfat_io() callback hook
55static status_t
56iterative_io_finished_hook(void* cookie, io_request* request, status_t status,
57	bool partialTransfer, size_t bytesTransferred)
58{
59	Inode* inode = (Inode*)cookie;
60	rw_lock_read_unlock(inode->Lock());
61	return B_OK;
62}
63
64
65//	#pragma mark - Scanning
66
67
68static float
69exfat_identify_partition(int fd, partition_data *partition, void **_cookie)
70{
71	exfat_super_block superBlock;
72	status_t status = Volume::Identify(fd, &superBlock);
73	if (status != B_OK)
74		return -1;
75
76	identify_cookie *cookie = new identify_cookie;
77	memcpy(&cookie->super_block, &superBlock, sizeof(exfat_super_block));
78
79	*_cookie = cookie;
80	return 0.8f;
81}
82
83
84static status_t
85exfat_scan_partition(int fd, partition_data *partition, void *_cookie)
86{
87	identify_cookie *cookie = (identify_cookie *)_cookie;
88
89	partition->status = B_PARTITION_VALID;
90	partition->flags |= B_PARTITION_FILE_SYSTEM;
91	partition->content_size = cookie->super_block.NumBlocks()
92		<< cookie->super_block.BlockShift();
93	partition->block_size = 1 << cookie->super_block.BlockShift();
94	// TODO volume name isn't in the superblock
95	partition->content_name = strdup(cookie->super_block.filesystem);
96	if (partition->content_name == NULL)
97		return B_NO_MEMORY;
98
99	return B_OK;
100}
101
102
103static void
104exfat_free_identify_partition_cookie(partition_data* partition, void* _cookie)
105{
106	delete (identify_cookie*)_cookie;
107}
108
109
110//	#pragma mark -
111
112
113static status_t
114exfat_mount(fs_volume* _volume, const char* device, uint32 flags,
115	const char* args, ino_t* _rootID)
116{
117	Volume* volume = new(std::nothrow) Volume(_volume);
118	if (volume == NULL)
119		return B_NO_MEMORY;
120
121	// TODO: this is a bit hacky: we can't use publish_vnode() to publish
122	// the root node, or else its file cache cannot be created (we could
123	// create it later, though). Therefore we're using get_vnode() in Mount(),
124	// but that requires us to export our volume data before calling it.
125	_volume->private_volume = volume;
126	_volume->ops = &gExfatVolumeOps;
127
128	status_t status = volume->Mount(device, flags);
129	if (status != B_OK) {
130		ERROR("Failed mounting the volume. Error: %s\n", strerror(status));
131		delete volume;
132		return status;
133	}
134
135	*_rootID = volume->RootNode()->ID();
136	return B_OK;
137}
138
139
140static status_t
141exfat_unmount(fs_volume *_volume)
142{
143	Volume* volume = (Volume *)_volume->private_volume;
144
145	status_t status = volume->Unmount();
146	delete volume;
147
148	return status;
149}
150
151
152static status_t
153exfat_read_fs_info(fs_volume* _volume, struct fs_info* info)
154{
155	Volume* volume = (Volume*)_volume->private_volume;
156
157	// File system flags
158	info->flags = B_FS_IS_PERSISTENT
159		| (volume->IsReadOnly() ? B_FS_IS_READONLY : 0);
160	info->io_size = EXFAT_IO_SIZE;
161	info->block_size = volume->BlockSize();
162	info->total_blocks = volume->SuperBlock().NumBlocks();
163	info->free_blocks = 0; //volume->NumFreeBlocks();
164
165	// Volume name
166	strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
167
168	// File system name
169	strlcpy(info->fsh_name, "exfat", sizeof(info->fsh_name));
170
171	return B_OK;
172}
173
174
175//	#pragma mark -
176
177
178static status_t
179exfat_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type,
180	uint32* _flags, bool reenter)
181{
182	TRACE("get_vnode %lu\n", id);
183	Volume* volume = (Volume*)_volume->private_volume;
184
185	Inode* inode = new(std::nothrow) Inode(volume, id);
186	if (inode == NULL)
187		return B_NO_MEMORY;
188
189	status_t status = inode->InitCheck();
190	if (status != B_OK)
191		delete inode;
192
193	if (status == B_OK) {
194		_node->private_node = inode;
195		_node->ops = &gExfatVnodeOps;
196		*_type = inode->Mode();
197		*_flags = 0;
198	} else
199		ERROR("get_vnode: InitCheck() failed. Error: %s\n", strerror(status));
200
201	return status;
202}
203
204
205static status_t
206exfat_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
207{
208	delete (Inode*)_node->private_node;
209	return B_OK;
210}
211
212
213static bool
214exfat_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie)
215{
216	return true;
217}
218
219
220static status_t
221exfat_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie,
222	off_t pos, const iovec* vecs, size_t count, size_t* _numBytes)
223{
224	Volume* volume = (Volume*)_volume->private_volume;
225	Inode* inode = (Inode*)_node->private_node;
226
227	if (inode->FileCache() == NULL)
228		return B_BAD_VALUE;
229
230	rw_lock_read_lock(inode->Lock());
231
232	uint32 vecIndex = 0;
233	size_t vecOffset = 0;
234	size_t bytesLeft = *_numBytes;
235	status_t status;
236
237	while (true) {
238		file_io_vec fileVecs[8];
239		uint32 fileVecCount = 8;
240
241		status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs,
242			&fileVecCount, 0);
243		if (status != B_OK && status != B_BUFFER_OVERFLOW)
244			break;
245
246		bool bufferOverflow = status == B_BUFFER_OVERFLOW;
247
248		size_t bytes = bytesLeft;
249		status = read_file_io_vec_pages(volume->Device(), fileVecs,
250			fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes);
251		if (status != B_OK || !bufferOverflow)
252			break;
253
254		pos += bytes;
255		bytesLeft -= bytes;
256	}
257
258	rw_lock_read_unlock(inode->Lock());
259
260	return status;
261}
262
263
264static status_t
265exfat_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request)
266{
267	Volume* volume = (Volume*)_volume->private_volume;
268	Inode* inode = (Inode*)_node->private_node;
269
270#ifndef EXFAT_SHELL
271	if (io_request_is_write(request) && volume->IsReadOnly()) {
272		notify_io_request(request, B_READ_ONLY_DEVICE);
273		return B_READ_ONLY_DEVICE;
274	}
275#endif
276
277	if (inode->FileCache() == NULL) {
278#ifndef EXFAT_SHELL
279		notify_io_request(request, B_BAD_VALUE);
280#endif
281		return B_BAD_VALUE;
282	}
283
284	// We lock the node here and will unlock it in the "finished" hook.
285	rw_lock_read_lock(inode->Lock());
286
287	return do_iterative_fd_io(volume->Device(), request,
288		iterative_io_get_vecs_hook, iterative_io_finished_hook, inode);
289}
290
291
292static status_t
293exfat_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset,
294	size_t size, struct file_io_vec* vecs, size_t* _count)
295{
296	TRACE("exfat_get_file_map()\n");
297	Inode* inode = (Inode*)_node->private_node;
298	size_t index = 0, max = *_count;
299
300	while (true) {
301		off_t blockOffset;
302		off_t blockLength;
303		status_t status = inode->FindBlock(offset, blockOffset, &blockLength);
304		if (status != B_OK)
305			return status;
306
307		if (index > 0 && (vecs[index - 1].offset
308				== blockOffset - vecs[index - 1].length)) {
309			vecs[index - 1].length += blockLength;
310		} else {
311			if (index >= max) {
312				// we're out of file_io_vecs; let's bail out
313				*_count = index;
314				return B_BUFFER_OVERFLOW;
315			}
316
317			vecs[index].offset = blockOffset;
318			vecs[index].length = blockLength;
319			index++;
320		}
321
322		offset += blockLength;
323		size -= blockLength;
324
325		if (size <= vecs[index - 1].length || offset >= inode->Size()) {
326			// We're done!
327			*_count = index;
328			TRACE("exfat_get_file_map for inode %lld\n", inode->ID());
329			return B_OK;
330		}
331	}
332
333	// can never get here
334	return B_ERROR;
335}
336
337
338//	#pragma mark -
339
340
341static status_t
342exfat_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name,
343	ino_t* _vnodeID)
344{
345	TRACE("exfat_lookup: name address: %p (%s)\n", name, name);
346	Volume* volume = (Volume*)_volume->private_volume;
347	Inode* directory = (Inode*)_directory->private_node;
348
349	// check access permissions
350	status_t status = directory->CheckPermissions(X_OK);
351	if (status < B_OK)
352		return status;
353
354	status = DirectoryIterator(directory).Lookup(name, strlen(name), _vnodeID);
355	if (status != B_OK) {
356		ERROR("exfat_lookup: name %s (%s)\n", name, strerror(status));
357		return status;
358	}
359
360	TRACE("exfat_lookup: ID %d\n", *_vnodeID);
361
362	return get_vnode(volume->FSVolume(), *_vnodeID, NULL);
363}
364
365
366static status_t
367exfat_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd,
368	void* buffer, size_t bufferLength)
369{
370	TRACE("ioctl: %lu\n", cmd);
371
372	/*Volume* volume = (Volume*)_volume->private_volume;*/
373	return B_DEV_INVALID_IOCTL;
374}
375
376
377static status_t
378exfat_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
379{
380	Inode* inode = (Inode*)_node->private_node;
381
382	stat->st_dev = inode->GetVolume()->ID();
383	stat->st_ino = inode->ID();
384	stat->st_nlink = 1;
385	stat->st_blksize = EXFAT_IO_SIZE;
386
387	stat->st_uid = inode->UserID();
388	stat->st_gid = inode->GroupID();
389	stat->st_mode = inode->Mode();
390	stat->st_type = 0;
391
392	inode->GetAccessTime(stat->st_atim);
393	inode->GetModificationTime(stat->st_mtim);
394	inode->GetChangeTime(stat->st_ctim);
395	inode->GetCreationTime(stat->st_crtim);
396
397	stat->st_size = inode->Size();
398	stat->st_blocks = (inode->Size() + 511) / 512;
399
400	return B_OK;
401}
402
403
404static status_t
405exfat_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode,
406	void** _cookie)
407{
408	Inode* inode = (Inode*)_node->private_node;
409
410	// opening a directory read-only is allowed, although you can't read
411	// any data from it.
412	if (inode->IsDirectory() && (openMode & O_RWMASK) != 0)
413		return B_IS_A_DIRECTORY;
414
415	status_t status =  inode->CheckPermissions(open_mode_to_access(openMode)
416		| (openMode & O_TRUNC ? W_OK : 0));
417	if (status != B_OK)
418		return status;
419
420	// Prepare the cookie
421	file_cookie* cookie = new(std::nothrow) file_cookie;
422	if (cookie == NULL)
423		return B_NO_MEMORY;
424	ObjectDeleter<file_cookie> cookieDeleter(cookie);
425
426	cookie->open_mode = openMode & EXFAT_OPEN_MODE_USER_MASK;
427	cookie->last_size = inode->Size();
428	cookie->last_notification = system_time();
429
430	if ((openMode & O_NOCACHE) != 0 && inode->FileCache() != NULL) {
431		// Disable the file cache, if requested?
432		status = file_cache_disable(inode->FileCache());
433		if (status != B_OK)
434			return status;
435	}
436
437	cookieDeleter.Detach();
438	*_cookie = cookie;
439
440	return B_OK;
441}
442
443
444static status_t
445exfat_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos,
446	void* buffer, size_t* _length)
447{
448	Inode* inode = (Inode*)_node->private_node;
449
450	if (!inode->IsFile()) {
451		*_length = 0;
452		return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE;
453	}
454
455	return inode->ReadAt(pos, (uint8*)buffer, _length);
456}
457
458
459static status_t
460exfat_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
461{
462	return B_OK;
463}
464
465
466static status_t
467exfat_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
468{
469	file_cookie* cookie = (file_cookie*)_cookie;
470	Volume* volume = (Volume*)_volume->private_volume;
471	Inode* inode = (Inode*)_node->private_node;
472
473	if (inode->Size() != cookie->last_size)
474		notify_stat_changed(volume->ID(), inode->ID(), B_STAT_SIZE);
475
476	delete cookie;
477	return B_OK;
478}
479
480
481static status_t
482exfat_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
483{
484	Inode* inode = (Inode*)_node->private_node;
485	return inode->CheckPermissions(accessMode);
486}
487
488
489static status_t
490exfat_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer,
491	size_t *_bufferSize)
492{
493	Inode* inode = (Inode*)_node->private_node;
494	return inode->ReadAt(0, (uint8*)buffer, _bufferSize);
495}
496
497
498//	#pragma mark - Directory functions
499
500
501static status_t
502exfat_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie)
503{
504	Inode* inode = (Inode*)_node->private_node;
505	status_t status = inode->CheckPermissions(R_OK);
506	if (status < B_OK)
507		return status;
508
509	if (!inode->IsDirectory())
510		return B_NOT_A_DIRECTORY;
511
512	DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode);
513	if (iterator == NULL || iterator->InitCheck() != B_OK) {
514		delete iterator;
515		return B_NO_MEMORY;
516	}
517
518	*_cookie = iterator;
519	return B_OK;
520}
521
522
523static status_t
524exfat_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie,
525	struct dirent *dirent, size_t bufferSize, uint32 *_num)
526{
527	TRACE("exfat_read_dir\n");
528	DirectoryIterator* iterator = (DirectoryIterator*)_cookie;
529
530	size_t length = bufferSize;
531	ino_t id;
532	status_t status = iterator->GetNext(dirent->d_name, &length, &id);
533	if (status == B_ENTRY_NOT_FOUND) {
534		*_num = 0;
535		return B_OK;
536	} else if (status != B_OK)
537		return status;
538
539	Volume* volume = (Volume*)_volume->private_volume;
540	dirent->d_dev = volume->ID();
541	dirent->d_ino = id;
542	dirent->d_reclen = sizeof(struct dirent) + length;
543	*_num = 1;
544
545	TRACE("exfat_read_dir end\n");
546
547	return B_OK;
548}
549
550
551static status_t
552exfat_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie)
553{
554	DirectoryIterator* iterator = (DirectoryIterator*)_cookie;
555
556	return iterator->Rewind();
557}
558
559
560static status_t
561exfat_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/)
562{
563	return B_OK;
564}
565
566
567static status_t
568exfat_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie)
569{
570	delete (DirectoryIterator*)_cookie;
571	return B_OK;
572}
573
574
575fs_volume_ops gExfatVolumeOps = {
576	&exfat_unmount,
577	&exfat_read_fs_info,
578	NULL,	// write_fs_info()
579	NULL,	// fs_sync,
580	&exfat_get_vnode,
581};
582
583
584fs_vnode_ops gExfatVnodeOps = {
585	/* vnode operations */
586	&exfat_lookup,
587	NULL,
588	&exfat_put_vnode,
589	NULL,	// exfat_remove_vnode,
590
591	/* VM file access */
592	&exfat_can_page,
593	&exfat_read_pages,
594	NULL,	// exfat_write_pages,
595
596	NULL,	// io()
597	NULL,	// cancel_io()
598
599	&exfat_get_file_map,
600
601	&exfat_ioctl,
602	NULL,
603	NULL,	// fs_select
604	NULL,	// fs_deselect
605	NULL,	// fs_fsync,
606
607	&exfat_read_link,
608	NULL,	// fs_create_symlink,
609
610	NULL,	// fs_link,
611	NULL,	// fs_unlink,
612	NULL,	// fs_rename,
613
614	&exfat_access,
615	&exfat_read_stat,
616	NULL,	// fs_write_stat,
617	NULL,	// fs_preallocate
618
619	/* file operations */
620	NULL,	// fs_create,
621	&exfat_open,
622	&exfat_close,
623	&exfat_free_cookie,
624	&exfat_read,
625	NULL,	//	fs_write,
626
627	/* directory operations */
628	NULL, 	// fs_create_dir,
629	NULL, 	// fs_remove_dir,
630	&exfat_open_dir,
631	&exfat_close_dir,
632	&exfat_free_dir_cookie,
633	&exfat_read_dir,
634	&exfat_rewind_dir,
635
636	/* attribute directory operations */
637	NULL, 	// fs_open_attr_dir,
638	NULL,	// fs_close_attr_dir,
639	NULL,	// fs_free_attr_dir_cookie,
640	NULL,	// fs_read_attr_dir,
641	NULL,	// fs_rewind_attr_dir,
642
643	/* attribute operations */
644	NULL,	// fs_create_attr,
645	NULL,	// fs_open_attr,
646	NULL,	// fs_close_attr,
647	NULL,	// fs_free_attr_cookie,
648	NULL,	// fs_read_attr,
649	NULL,	// fs_write_attr,
650	NULL,	// fs_read_attr_stat,
651	NULL,	// fs_write_attr_stat,
652	NULL,	// fs_rename_attr,
653	NULL,	// fs_remove_attr,
654};
655
656
657static file_system_module_info sExfatFileSystem = {
658	{
659		"file_systems/exfat" B_CURRENT_FS_API_VERSION,
660		0,
661		NULL,
662	},
663
664	"exfat",						// short_name
665	"ExFAT File System",			// pretty_name
666	0,								// DDM flags
667
668	// scanning
669	exfat_identify_partition,
670	exfat_scan_partition,
671	exfat_free_identify_partition_cookie,
672	NULL,	// free_partition_content_cookie()
673
674	&exfat_mount,
675
676	NULL,
677};
678
679
680module_info *modules[] = {
681	(module_info *)&sExfatFileSystem,
682	NULL,
683};
684