xref: /haiku/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp (revision 60c26cd332a044bb9003091b9196cc404ebe5482)
1 /*
2  * Copyright 2008, Axel Dörfler, axeld@pinc-software.de.
3  * This file may be used under the terms of the MIT License.
4  */
5 
6 
7 #include <dirent.h>
8 #include <util/kernel_cpp.h>
9 #include <string.h>
10 
11 #include <AutoDeleter.h>
12 #include <fs_cache.h>
13 #include <fs_info.h>
14 
15 #include "AttributeIterator.h"
16 #include "DirectoryIterator.h"
17 #include "ext2.h"
18 #include "HTree.h"
19 #include "Inode.h"
20 
21 
22 //#define TRACE_EXT2
23 #ifdef TRACE_EXT2
24 #	define TRACE(x...) dprintf("\33[34mext2:\33[0m " x)
25 #else
26 #	define TRACE(x...) ;
27 #endif
28 
29 
30 #define EXT2_IO_SIZE	65536
31 
32 
33 struct identify_cookie {
34 	ext2_super_block super_block;
35 };
36 
37 
38 /*!	Converts the open mode, the open flags given to bfs_open(), into
39 	access modes, e.g. since O_RDONLY requires read access to the
40 	file, it will be converted to R_OK.
41 */
42 int
43 open_mode_to_access(int openMode)
44 {
45 	openMode &= O_RWMASK;
46 	if (openMode == O_RDONLY)
47 		return R_OK;
48 	else if (openMode == O_WRONLY)
49 		return W_OK;
50 
51 	return R_OK | W_OK;
52 }
53 
54 
55 //	#pragma mark - Scanning
56 
57 
58 static float
59 ext2_identify_partition(int fd, partition_data *partition, void **_cookie)
60 {
61 	ext2_super_block superBlock;
62 	status_t status = Volume::Identify(fd, &superBlock);
63 	if (status != B_OK)
64 		return -1;
65 
66 	identify_cookie *cookie = new identify_cookie;
67 	memcpy(&cookie->super_block, &superBlock, sizeof(ext2_super_block));
68 
69 	*_cookie = cookie;
70 	return 0.8f;
71 }
72 
73 
74 static status_t
75 ext2_scan_partition(int fd, partition_data *partition, void *_cookie)
76 {
77 	identify_cookie *cookie = (identify_cookie *)_cookie;
78 
79 	partition->status = B_PARTITION_VALID;
80 	partition->flags |= B_PARTITION_FILE_SYSTEM;
81 	partition->content_size = cookie->super_block.NumBlocks()
82 		<< cookie->super_block.BlockShift();
83 	partition->block_size = 1UL << cookie->super_block.BlockShift();
84 	partition->content_name = strdup(cookie->super_block.name);
85 	if (partition->content_name == NULL)
86 		return B_NO_MEMORY;
87 
88 	return B_OK;
89 }
90 
91 
92 static void
93 ext2_free_identify_partition_cookie(partition_data* partition, void* _cookie)
94 {
95 	delete (identify_cookie*)_cookie;
96 }
97 
98 
99 //	#pragma mark -
100 
101 
102 static status_t
103 ext2_mount(fs_volume* _volume, const char* device, uint32 flags,
104 	const char* args, ino_t* _rootID)
105 {
106 	Volume* volume = new Volume(_volume);
107 	if (volume == NULL)
108 		return B_NO_MEMORY;
109 
110 	// TODO: this is a bit hacky: we can't use publish_vnode() to publish
111 	// the root node, or else its file cache cannot be created (we could
112 	// create it later, though). Therefore we're using get_vnode() in Mount(),
113 	// but that requires us to export our volume data before calling it.
114 	_volume->private_volume = volume;
115 	_volume->ops = &gExt2VolumeOps;
116 
117 	status_t status = volume->Mount(device, flags);
118 	if (status != B_OK) {
119 		delete volume;
120 		return status;
121 	}
122 
123 	*_rootID = volume->RootNode()->ID();
124 	return B_OK;
125 }
126 
127 
128 static status_t
129 ext2_unmount(fs_volume *_volume)
130 {
131 	Volume* volume = (Volume *)_volume->private_volume;
132 
133 	status_t status = volume->Unmount();
134 	delete volume;
135 
136 	return status;
137 }
138 
139 
140 static status_t
141 ext2_read_fs_info(fs_volume* _volume, struct fs_info* info)
142 {
143 	Volume* volume = (Volume*)_volume->private_volume;
144 
145 	// File system flags
146 	info->flags = B_FS_IS_PERSISTENT
147 		| (volume->IsReadOnly() ? B_FS_IS_READONLY : 0);
148 	info->io_size = EXT2_IO_SIZE;
149 	info->block_size = volume->BlockSize();
150 	info->total_blocks = volume->NumBlocks();
151 	info->free_blocks = volume->FreeBlocks();
152 
153 	// Volume name
154 	strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
155 
156 	// File system name
157 	strlcpy(info->fsh_name, "ext2", sizeof(info->fsh_name));
158 
159 	return B_OK;
160 }
161 
162 
163 //	#pragma mark -
164 
165 
166 static status_t
167 ext2_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type,
168 	uint32* _flags, bool reenter)
169 {
170 	Volume* volume = (Volume*)_volume->private_volume;
171 
172 	if (id < 2 || id > volume->NumInodes()) {
173 		dprintf("ext2: inode at %Ld requested!\n", id);
174 		return B_ERROR;
175 	}
176 
177 	Inode* inode = new Inode(volume, id);
178 	if (inode == NULL)
179 		return B_NO_MEMORY;
180 
181 	status_t status = inode->InitCheck();
182 	if (status < B_OK)
183 		delete inode;
184 
185 	if (status == B_OK) {
186 		_node->private_node = inode;
187 		_node->ops = &gExt2VnodeOps;
188 		*_type = inode->Mode();
189 		*_flags = 0;
190 	}
191 
192 	return status;
193 }
194 
195 
196 static status_t
197 ext2_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
198 {
199 	delete (Inode*)_node->private_node;
200 	return B_OK;
201 }
202 
203 
204 static bool
205 ext2_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie)
206 {
207 	return true;
208 }
209 
210 
211 static status_t
212 ext2_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie,
213 	off_t pos, const iovec* vecs, size_t count, size_t* _numBytes)
214 {
215 	Volume* volume = (Volume*)_volume->private_volume;
216 	Inode* inode = (Inode*)_node->private_node;
217 
218 	if (inode->FileCache() == NULL)
219 		return B_BAD_VALUE;
220 
221 	rw_lock_read_lock(inode->Lock());
222 
223 	uint32 vecIndex = 0;
224 	size_t vecOffset = 0;
225 	size_t bytesLeft = *_numBytes;
226 	status_t status;
227 
228 	while (true) {
229 		file_io_vec fileVecs[8];
230 		uint32 fileVecCount = 8;
231 
232 		status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs,
233 			&fileVecCount, 0);
234 		if (status != B_OK && status != B_BUFFER_OVERFLOW)
235 			break;
236 
237 		bool bufferOverflow = status == B_BUFFER_OVERFLOW;
238 
239 		size_t bytes = bytesLeft;
240 		status = read_file_io_vec_pages(volume->Device(), fileVecs,
241 			fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes);
242 		if (status != B_OK || !bufferOverflow)
243 			break;
244 
245 		pos += bytes;
246 		bytesLeft -= bytes;
247 	}
248 
249 	rw_lock_read_unlock(inode->Lock());
250 
251 	return status;
252 }
253 
254 
255 static status_t
256 ext2_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset,
257 	size_t size, struct file_io_vec* vecs, size_t* _count)
258 {
259 	Volume* volume = (Volume*)_volume->private_volume;
260 	Inode* inode = (Inode*)_node->private_node;
261 	size_t index = 0, max = *_count;
262 
263 	while (true) {
264 		uint32 block;
265 		status_t status = inode->FindBlock(offset, block);
266 		if (status != B_OK)
267 			return status;
268 
269 		off_t blockOffset = (off_t)block << volume->BlockShift();
270 		uint32 blockLength = volume->BlockSize();
271 
272 		if (index > 0 && (vecs[index - 1].offset == blockOffset - blockLength
273 				|| (vecs[index - 1].offset == -1 && block == 0))) {
274 			vecs[index - 1].length += blockLength;
275 		} else {
276 			if (index >= max) {
277 				// we're out of file_io_vecs; let's bail out
278 				*_count = index;
279 				return B_BUFFER_OVERFLOW;
280 			}
281 
282 			// 'block' is 0 for sparse blocks
283 			if (block != 0)
284 				vecs[index].offset = blockOffset;
285 			else
286 				vecs[index].offset = -1;
287 
288 			vecs[index].length = blockLength;
289 			index++;
290 		}
291 
292 		offset += blockLength;
293 
294 		if (size <= vecs[index - 1].length || offset >= inode->Size()) {
295 			// We're done!
296 			*_count = index;
297 			TRACE("ext2_get_file_map for inode %ld\n", inode->ID());
298 			return B_OK;
299 		}
300 	}
301 
302 	// can never get here
303 	return B_ERROR;
304 }
305 
306 
307 //	#pragma mark -
308 
309 
310 static status_t
311 ext2_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name,
312 	ino_t* _vnodeID)
313 {
314 	Volume* volume = (Volume*)_volume->private_volume;
315 	Inode* directory = (Inode*)_directory->private_node;
316 
317 	// check access permissions
318 	status_t status = directory->CheckPermissions(X_OK);
319 	if (status < B_OK)
320 		return status;
321 
322 	HTree htree(volume, directory);
323 	DirectoryIterator* iterator;
324 
325 	status = htree.Lookup(name, &iterator);
326 	if (status != B_OK)
327 		return status;
328 
329 	ObjectDeleter<DirectoryIterator> iteratorDeleter(iterator);
330 
331 	while (true) {
332 		char buffer[B_FILE_NAME_LENGTH];
333 		size_t length = sizeof(buffer);
334 		status = iterator->GetNext(buffer, &length, _vnodeID);
335 		if (status != B_OK)
336 			return status;
337 		TRACE("ext2_lookup() %s\n", buffer);
338 
339 		if (!strcmp(buffer, name))
340 			break;
341 	}
342 
343 	return get_vnode(volume->FSVolume(), *_vnodeID, NULL);
344 }
345 
346 
347 static status_t
348 ext2_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
349 {
350 	Inode* inode = (Inode*)_node->private_node;
351 	const ext2_inode& node = inode->Node();
352 
353 	stat->st_dev = inode->GetVolume()->ID();
354 	stat->st_ino = inode->ID();
355 	stat->st_nlink = node.NumLinks();
356 	stat->st_blksize = EXT2_IO_SIZE;
357 
358 	stat->st_uid = node.UserID();
359 	stat->st_gid = node.GroupID();
360 	stat->st_mode = node.Mode();
361 	stat->st_type = 0;
362 
363 	stat->st_atime = node.AccessTime();
364 	stat->st_mtime = stat->st_ctime = node.ModificationTime();
365 	stat->st_crtime = node.CreationTime();
366 
367 	stat->st_size = inode->Size();
368 	stat->st_blocks = (inode->Size() + 511) / 512;
369 
370 	return B_OK;
371 }
372 
373 
374 static status_t
375 ext2_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
376 {
377 	Inode* inode = (Inode*)_node->private_node;
378 
379 	// opening a directory read-only is allowed, although you can't read
380 	// any data from it.
381 	if (inode->IsDirectory() && (openMode & O_RWMASK) != 0)
382 		return B_IS_A_DIRECTORY;
383 
384 	if ((openMode & O_TRUNC) != 0)
385 		return B_READ_ONLY_DEVICE;
386 
387 	return inode->CheckPermissions(open_mode_to_access(openMode)
388 		| (openMode & O_TRUNC ? W_OK : 0));
389 }
390 
391 
392 static status_t
393 ext2_read(fs_volume *_volume, fs_vnode *_node, void *_cookie, off_t pos,
394 	void *buffer, size_t *_length)
395 {
396 	Inode *inode = (Inode *)_node->private_node;
397 
398 	if (!inode->IsFile()) {
399 		*_length = 0;
400 		return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE;
401 	}
402 
403 	return inode->ReadAt(pos, (uint8 *)buffer, _length);
404 }
405 
406 
407 static status_t
408 ext2_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
409 {
410 	return B_OK;
411 }
412 
413 
414 static status_t
415 ext2_free_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie)
416 {
417 	return B_OK;
418 }
419 
420 
421 static status_t
422 ext2_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
423 {
424 	Inode* inode = (Inode*)_node->private_node;
425 	return inode->CheckPermissions(accessMode);
426 }
427 
428 
429 static status_t
430 ext2_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer,
431 	size_t *_bufferSize)
432 {
433 	Inode* inode = (Inode*)_node->private_node;
434 
435 	if (!inode->IsSymLink())
436 		return B_BAD_VALUE;
437 
438 	if (inode->Size() < *_bufferSize)
439 		*_bufferSize = inode->Size();
440 
441 	if (inode->Size() > EXT2_SHORT_SYMLINK_LENGTH)
442 		return inode->ReadAt(0, (uint8 *)buffer, _bufferSize);
443 
444 	memcpy(buffer, inode->Node().symlink, *_bufferSize);
445 	return B_OK;
446 }
447 
448 
449 //	#pragma mark - Directory functions
450 
451 
452 static status_t
453 ext2_open_dir(fs_volume *_volume, fs_vnode *_node, void **_cookie)
454 {
455 	Inode *inode = (Inode *)_node->private_node;
456 	status_t status = inode->CheckPermissions(R_OK);
457 	if (status < B_OK)
458 		return status;
459 
460 	if (!inode->IsDirectory())
461 		return B_NOT_A_DIRECTORY;
462 
463 	DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode);
464 	if (iterator == NULL)
465 		return B_NO_MEMORY;
466 
467 	*_cookie = iterator;
468 	return B_OK;
469 }
470 
471 
472 static status_t
473 ext2_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie,
474 	struct dirent *dirent, size_t bufferSize, uint32 *_num)
475 {
476 	DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
477 
478 	size_t length = bufferSize;
479 	ino_t id;
480 	status_t status = iterator->GetNext(dirent->d_name, &length, &id);
481 	if (status == B_ENTRY_NOT_FOUND) {
482 		*_num = 0;
483 		return B_OK;
484 	} else if (status != B_OK)
485 		return status;
486 
487 	Volume* volume = (Volume*)_volume->private_volume;
488 
489 	dirent->d_dev = volume->ID();
490 	dirent->d_ino = id;
491 	dirent->d_reclen = sizeof(struct dirent) + length;
492 
493 	*_num = 1;
494 	return B_OK;
495 }
496 
497 
498 static status_t
499 ext2_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie)
500 {
501 	DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
502 	return iterator->Rewind();
503 }
504 
505 
506 static status_t
507 ext2_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/)
508 {
509 	return B_OK;
510 }
511 
512 
513 static status_t
514 ext2_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie)
515 {
516 	delete (DirectoryIterator *)_cookie;
517 	return B_OK;
518 }
519 
520 
521 static status_t
522 ext2_open_attr_dir(fs_volume *_volume, fs_vnode *_node, void **_cookie)
523 {
524 	Inode* inode = (Inode*)_node->private_node;
525 	Volume* volume = (Volume*)_volume->private_volume;
526 	TRACE("%s()\n", __FUNCTION__);
527 
528 	if (!(volume->SuperBlock().CompatibleFeatures() & EXT2_FEATURE_EXT_ATTR))
529 		return ENOSYS;
530 
531 	// on directories too ?
532 	if (!inode->IsFile())
533 		return EINVAL;
534 
535 	AttributeIterator* iterator = new(std::nothrow) AttributeIterator(inode);
536 	if (iterator == NULL)
537 		return B_NO_MEMORY;
538 
539 	*_cookie = iterator;
540 	return B_OK;
541 }
542 
543 static status_t
544 ext2_close_attr_dir(fs_volume* _volume, fs_vnode* _node, void* cookie)
545 {
546 	TRACE("%s()\n", __FUNCTION__);
547 	return B_OK;
548 }
549 
550 
551 static status_t
552 ext2_free_attr_dir_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
553 {
554 	TRACE("%s()\n", __FUNCTION__);
555 	delete (AttributeIterator *)_cookie;
556 	return B_OK;
557 }
558 
559 
560 static status_t
561 ext2_read_attr_dir(fs_volume* _volume, fs_vnode* _node,
562 				void* _cookie, struct dirent* dirent, size_t bufferSize,
563 				uint32* _num)
564 {
565 	Inode* inode = (Inode*)_node->private_node;
566 	AttributeIterator *iterator = (AttributeIterator *)_cookie;
567 	TRACE("%s()\n", __FUNCTION__);
568 
569 	size_t length = bufferSize;
570 	status_t status = iterator->GetNext(dirent->d_name, &length);
571 	if (status == B_ENTRY_NOT_FOUND) {
572 		*_num = 0;
573 		return B_OK;
574 	} else if (status != B_OK)
575 		return status;
576 
577 	Volume* volume = (Volume*)_volume->private_volume;
578 
579 	dirent->d_dev = volume->ID();
580 	dirent->d_ino = inode->ID();
581 	dirent->d_reclen = sizeof(struct dirent) + length;
582 
583 	*_num = 1;
584 	return B_OK;
585 }
586 
587 
588 static status_t
589 ext2_rewind_attr_dir(fs_volume* _volume, fs_vnode* _node, void* _cookie)
590 {
591 	AttributeIterator *iterator = (AttributeIterator *)_cookie;
592 	TRACE("%s()\n", __FUNCTION__);
593 	return iterator->Rewind();
594 }
595 
596 
597 	/* attribute operations */
598 static status_t
599 ext2_create_attr(fs_volume* _volume, fs_vnode* _node,
600 	const char* name, uint32 type, int openMode, void** _cookie)
601 {
602 	return EROFS;
603 }
604 
605 
606 static status_t
607 ext2_open_attr(fs_volume* _volume, fs_vnode* _node, const char* name,
608 	int openMode, void** _cookie)
609 {
610 	TRACE("%s()\n", __FUNCTION__);
611 	if ((openMode & O_RWMASK) != O_RDONLY)
612 		return EROFS;
613 
614 	Inode* inode = (Inode*)_node->private_node;
615 	Volume* volume = (Volume*)_volume->private_volume;
616 
617 	if (!(volume->SuperBlock().CompatibleFeatures() & EXT2_FEATURE_EXT_ATTR))
618 		return ENOSYS;
619 
620 	// on directories too ?
621 	if (!inode->IsFile())
622 		return EINVAL;
623 
624 	ext2_xattr_entry *entry = new ext2_xattr_entry;
625 
626 	AttributeIterator i(inode);
627 	status_t status = i.Find(name, entry);
628 	if (status == B_OK) {
629 		//entry->Dump();
630 		*_cookie = entry;
631 		return B_OK;
632 	}
633 
634 	delete entry;
635 	return status;
636 }
637 
638 
639 static status_t
640 ext2_close_attr(fs_volume* _volume, fs_vnode* _node,
641 	void* cookie)
642 {
643 	return B_OK;
644 }
645 
646 
647 static status_t
648 ext2_free_attr_cookie(fs_volume* _volume, fs_vnode* _node,
649 	void* cookie)
650 {
651 	ext2_xattr_entry *entry = (ext2_xattr_entry *)cookie;
652 
653 	delete entry;
654 	return B_OK;
655 }
656 
657 
658 static status_t
659 ext2_read_attr(fs_volume* _volume, fs_vnode* _node, void* cookie,
660 	off_t pos, void* buffer, size_t* length)
661 {
662 	TRACE("%s()\n", __FUNCTION__);
663 
664 	Inode* inode = (Inode*)_node->private_node;
665 	Volume* volume = (Volume*)_volume->private_volume;
666 	ext2_xattr_entry *entry = (ext2_xattr_entry *)cookie;
667 
668 	if (!entry->IsValid())
669 		return EINVAL;
670 
671 	if (pos < 0 || (pos + *length) > entry->ValueSize())
672 		return ERANGE;
673 
674 	return inode->AttributeBlockReadAt(entry->ValueOffset() + pos,
675 		(uint8 *)buffer, length);
676 }
677 
678 
679 static status_t
680 ext2_write_attr(fs_volume* _volume, fs_vnode* _node, void* cookie,
681 	off_t pos, const void* buffer, size_t* length)
682 {
683 	return EROFS;
684 }
685 
686 
687 
688 static status_t
689 ext2_read_attr_stat(fs_volume* _volume, fs_vnode* _node,
690 	void* cookie, struct stat* stat)
691 {
692 	ext2_xattr_entry *entry = (ext2_xattr_entry *)cookie;
693 
694 	stat->st_type = B_RAW_TYPE;
695 	stat->st_size = entry->ValueSize();
696 	TRACE("%s: st_size %d\n", __FUNCTION__, stat->st_size);
697 
698 	return B_OK;
699 }
700 
701 
702 static status_t
703 ext2_write_attr_stat(fs_volume* _volume, fs_vnode* _node,
704 	void* cookie, const struct stat* stat, int statMask)
705 {
706 	return EROFS;
707 }
708 
709 
710 static status_t
711 ext2_rename_attr(fs_volume* _volume, fs_vnode* fromVnode,
712 	const char* fromName, fs_vnode* toVnode, const char* toName)
713 {
714 	return ENOSYS;
715 }
716 
717 
718 static status_t
719 ext2_remove_attr(fs_volume* _volume, fs_vnode* vnode,
720 	const char* name)
721 {
722 	return ENOSYS;
723 }
724 
725 
726 fs_volume_ops gExt2VolumeOps = {
727 	&ext2_unmount,
728 	&ext2_read_fs_info,
729 	NULL,	// write_fs_info()
730 	NULL,	// sync()
731 	&ext2_get_vnode,
732 };
733 
734 fs_vnode_ops gExt2VnodeOps = {
735 	/* vnode operations */
736 	&ext2_lookup,
737 	NULL,
738 	&ext2_put_vnode,
739 	NULL,
740 
741 	/* VM file access */
742 	&ext2_can_page,
743 	&ext2_read_pages,
744 	NULL,
745 
746 	NULL,	// io()
747 	NULL,	// cancel_io()
748 
749 	&ext2_get_file_map,
750 
751 	NULL,
752 	NULL,
753 	NULL,	// fs_select
754 	NULL,	// fs_deselect
755 	NULL,
756 
757 	&ext2_read_link,
758 	NULL,
759 
760 	NULL,
761 	NULL,
762 	NULL,
763 
764 	&ext2_access,
765 	&ext2_read_stat,
766 	NULL,
767 
768 	/* file operations */
769 	NULL,
770 	&ext2_open,
771 	&ext2_close,
772 	&ext2_free_cookie,
773 	&ext2_read,
774 	NULL,
775 
776 	/* directory operations */
777 	NULL,
778 	NULL,
779 	&ext2_open_dir,
780 	&ext2_close_dir,
781 	&ext2_free_dir_cookie,
782 	&ext2_read_dir,
783 	&ext2_rewind_dir,
784 
785 	/* attribute directory operations */
786 	&ext2_open_attr_dir,
787 	&ext2_close_attr_dir,
788 	&ext2_free_attr_dir_cookie,
789 	&ext2_read_attr_dir,
790 	&ext2_rewind_attr_dir,
791 
792 	/* attribute operations */
793 	NULL, //&ext2_create_attr,
794 	&ext2_open_attr,
795 	&ext2_close_attr,
796 	&ext2_free_attr_cookie,
797 	&ext2_read_attr,
798 	NULL, //&ext2_write_attr,
799 	&ext2_read_attr_stat,
800 	NULL, //&ext2_write_attr_stat,
801 	NULL, //&ext2_rename_attr,
802 	NULL, //&ext2_remove_attr,
803 
804 };
805 
806 static file_system_module_info sExt2FileSystem = {
807 	{
808 		"file_systems/ext2" B_CURRENT_FS_API_VERSION,
809 		0,
810 		NULL,
811 	},
812 
813 	"ext2",						// short_name
814 	"Ext2 File System",			// pretty_name
815 	0,							// DDM flags
816 
817 	// scanning
818 	ext2_identify_partition,
819 	ext2_scan_partition,
820 	ext2_free_identify_partition_cookie,
821 	NULL,	// free_partition_content_cookie()
822 
823 	&ext2_mount,
824 
825 	NULL,
826 };
827 
828 module_info *modules[] = {
829 	(module_info *)&sExt2FileSystem,
830 	NULL,
831 };
832