xref: /haiku/src/add-ons/kernel/file_systems/exfat/kernel_interface.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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 
37 struct identify_cookie {
38 	exfat_super_block super_block;
39 };
40 
41 
42 //!	exfat_io() callback hook
43 static status_t
44 iterative_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
55 static status_t
56 iterative_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 
68 static float
69 exfat_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 
84 static status_t
85 exfat_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 
103 static void
104 exfat_free_identify_partition_cookie(partition_data* partition, void* _cookie)
105 {
106 	delete (identify_cookie*)_cookie;
107 }
108 
109 
110 //	#pragma mark -
111 
112 
113 static status_t
114 exfat_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 
140 static status_t
141 exfat_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 
152 static status_t
153 exfat_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 
178 static status_t
179 exfat_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 
205 static status_t
206 exfat_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
207 {
208 	delete (Inode*)_node->private_node;
209 	return B_OK;
210 }
211 
212 
213 static bool
214 exfat_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie)
215 {
216 	return true;
217 }
218 
219 
220 static status_t
221 exfat_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 
264 static status_t
265 exfat_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 
292 static status_t
293 exfat_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 
341 static status_t
342 exfat_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 
366 static status_t
367 exfat_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 
377 static status_t
378 exfat_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 
404 static status_t
405 exfat_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 
444 static status_t
445 exfat_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 
459 static status_t
460 exfat_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
461 {
462 	return B_OK;
463 }
464 
465 
466 static status_t
467 exfat_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 
481 static status_t
482 exfat_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 
489 static status_t
490 exfat_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 
501 static status_t
502 exfat_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 
523 static status_t
524 exfat_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 
551 static status_t
552 exfat_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie)
553 {
554 	DirectoryIterator* iterator = (DirectoryIterator*)_cookie;
555 
556 	return iterator->Rewind();
557 }
558 
559 
560 static status_t
561 exfat_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/)
562 {
563 	return B_OK;
564 }
565 
566 
567 static status_t
568 exfat_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie)
569 {
570 	delete (DirectoryIterator*)_cookie;
571 	return B_OK;
572 }
573 
574 
575 fs_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 
584 fs_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 
657 static 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 
680 module_info *modules[] = {
681 	(module_info *)&sExfatFileSystem,
682 	NULL,
683 };
684