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