xref: /haiku/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp (revision 58481f0f6ef1a61ba07283f012cafbc2ed874ead)
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 <fs_cache.h>
12 #include <fs_info.h>
13 
14 #include "DirectoryIterator.h"
15 #include "ext2.h"
16 #include "Inode.h"
17 
18 
19 #define EXT2_IO_SIZE	65536
20 
21 
22 struct identify_cookie {
23 	ext2_super_block super_block;
24 };
25 
26 
27 /*!	Converts the open mode, the open flags given to bfs_open(), into
28 	access modes, e.g. since O_RDONLY requires read access to the
29 	file, it will be converted to R_OK.
30 */
31 int
32 open_mode_to_access(int openMode)
33 {
34 	openMode &= O_RWMASK;
35 	if (openMode == O_RDONLY)
36 		return R_OK;
37 	else if (openMode == O_WRONLY)
38 		return W_OK;
39 
40 	return R_OK | W_OK;
41 }
42 
43 
44 //	#pragma mark - Scanning
45 
46 
47 static float
48 ext2_identify_partition(int fd, partition_data *partition, void **_cookie)
49 {
50 	ext2_super_block superBlock;
51 	status_t status = Volume::Identify(fd, &superBlock);
52 	if (status != B_OK)
53 		return status;
54 
55 	identify_cookie *cookie = new identify_cookie;
56 	memcpy(&cookie->super_block, &superBlock, sizeof(ext2_super_block));
57 
58 	*_cookie = cookie;
59 	return 0.8f;
60 }
61 
62 
63 static status_t
64 ext2_scan_partition(int fd, partition_data *partition, void *_cookie)
65 {
66 	identify_cookie *cookie = (identify_cookie *)_cookie;
67 
68 	partition->status = B_PARTITION_VALID;
69 	partition->flags |= B_PARTITION_FILE_SYSTEM;
70 	partition->content_size = cookie->super_block.NumBlocks()
71 		<< cookie->super_block.BlockShift();
72 	partition->block_size = 1UL << cookie->super_block.BlockShift();
73 	partition->content_name = strdup(cookie->super_block.name);
74 	if (partition->content_name == NULL)
75 		return B_NO_MEMORY;
76 
77 	return B_OK;
78 }
79 
80 
81 static void
82 ext2_free_identify_partition_cookie(partition_data* partition, void* _cookie)
83 {
84 	delete (identify_cookie*)_cookie;
85 }
86 
87 
88 //	#pragma mark -
89 
90 
91 static status_t
92 ext2_mount(fs_volume* _volume, const char* device, uint32 flags,
93 	const char* args, ino_t* _rootID)
94 {
95 	Volume* volume = new Volume(_volume);
96 	if (volume == NULL)
97 		return B_NO_MEMORY;
98 
99 	// TODO: this is a bit hacky: we can't use publish_vnode() to publish
100 	// the root node, or else its file cache cannot be created (we could
101 	// create it later, though). Therefore we're using get_vnode() in Mount(),
102 	// but that requires us to export our volume data before calling it.
103 	_volume->private_volume = volume;
104 	_volume->ops = &gExt2VolumeOps;
105 
106 	status_t status = volume->Mount(device, flags);
107 	if (status != B_OK) {
108 		delete volume;
109 		return status;
110 	}
111 
112 	*_rootID = volume->RootNode()->ID();
113 	return B_OK;
114 }
115 
116 
117 static status_t
118 ext2_unmount(fs_volume *_volume)
119 {
120 	Volume* volume = (Volume *)_volume->private_volume;
121 
122 	status_t status = volume->Unmount();
123 	delete volume;
124 
125 	return status;
126 }
127 
128 
129 static status_t
130 ext2_read_fs_info(fs_volume* _volume, struct fs_info* info)
131 {
132 	Volume* volume = (Volume*)_volume->private_volume;
133 
134 	// File system flags
135 	info->flags = B_FS_IS_PERSISTENT
136 		| (volume->IsReadOnly() ? B_FS_IS_READONLY : 0);
137 	info->io_size = EXT2_IO_SIZE;
138 	info->block_size = volume->BlockSize();
139 	info->total_blocks = volume->NumBlocks();
140 	info->free_blocks = volume->FreeBlocks();
141 
142 	// Volume name
143 	strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
144 
145 	// File system name
146 	strlcpy(info->fsh_name, "ext2", sizeof(info->fsh_name));
147 
148 	return B_OK;
149 }
150 
151 
152 //	#pragma mark -
153 
154 
155 static status_t
156 ext2_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type,
157 	uint32* _flags, bool reenter)
158 {
159 	Volume* volume = (Volume*)_volume->private_volume;
160 
161 	if (id < 2 || id > volume->NumBlocks()) {
162 		dprintf("ext2: inode at %Ld requested!\n", id);
163 		return B_ERROR;
164 	}
165 
166 	Inode* inode = new Inode(volume, id);
167 	if (inode == NULL)
168 		return B_NO_MEMORY;
169 
170 	status_t status = inode->InitCheck();
171 	if (status < B_OK)
172 		delete inode;
173 
174 	if (status == B_OK) {
175 		_node->private_node = inode;
176 		_node->ops = &gExt2VnodeOps;
177 		*_type = inode->Mode();
178 		*_flags = 0;
179 	}
180 
181 	return status;
182 }
183 
184 
185 static status_t
186 ext2_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
187 {
188 	delete (Inode*)_node->private_node;
189 	return B_OK;
190 }
191 
192 
193 static bool
194 ext2_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie)
195 {
196 	return true;
197 }
198 
199 
200 static status_t
201 ext2_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie,
202 	off_t pos, const iovec* vecs, size_t count, size_t* _numBytes)
203 {
204 	Volume* volume = (Volume*)_volume->private_volume;
205 	Inode* inode = (Inode*)_node->private_node;
206 
207 	if (inode->FileCache() == NULL)
208 		return B_BAD_VALUE;
209 
210 	rw_lock_read_lock(inode->Lock());
211 
212 	uint32 vecIndex = 0;
213 	size_t vecOffset = 0;
214 	size_t bytesLeft = *_numBytes;
215 	status_t status;
216 
217 	while (true) {
218 		file_io_vec fileVecs[8];
219 		uint32 fileVecCount = 8;
220 
221 		status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs,
222 			&fileVecCount, 0);
223 		if (status != B_OK && status != B_BUFFER_OVERFLOW)
224 			break;
225 
226 		bool bufferOverflow = status == B_BUFFER_OVERFLOW;
227 
228 		size_t bytes = bytesLeft;
229 		status = read_file_io_vec_pages(volume->Device(), fileVecs,
230 			fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes);
231 		if (status != B_OK || !bufferOverflow)
232 			break;
233 
234 		pos += bytes;
235 		bytesLeft -= bytes;
236 	}
237 
238 	rw_lock_read_unlock(inode->Lock());
239 
240 	return status;
241 }
242 
243 
244 static status_t
245 ext2_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset,
246 	size_t size, struct file_io_vec* vecs, size_t* _count)
247 {
248 	Volume* volume = (Volume*)_volume->private_volume;
249 	Inode* inode = (Inode*)_node->private_node;
250 	size_t index = 0, max = *_count;
251 
252 	while (true) {
253 		uint32 block;
254 		status_t status = inode->FindBlock(offset, block);
255 		if (status != B_OK)
256 			return status;
257 
258 		off_t blockOffset = (off_t)block << volume->BlockShift();
259 		uint32 blockLength = volume->BlockSize();
260 
261 		if (index > 0 && (vecs[index - 1].offset == blockOffset - blockLength
262 				|| (vecs[index - 1].offset == -1 && block == 0))) {
263 			vecs[index - 1].length += blockLength;
264 		} else {
265 			if (index >= max) {
266 				// we're out of file_io_vecs; let's bail out
267 				*_count = index;
268 				return B_BUFFER_OVERFLOW;
269 			}
270 
271 			// 'block' is 0 for sparse blocks
272 			if (block != 0)
273 				vecs[index].offset = blockOffset;
274 			else
275 				vecs[index].offset = -1;
276 
277 			vecs[index].length = blockLength;
278 			index++;
279 		}
280 
281 		offset += blockLength;
282 
283 		if (size <= vecs[index - 1].length || offset >= inode->Size()) {
284 			// We're done!
285 			*_count = index;
286 			return B_OK;
287 		}
288 	}
289 
290 	// can never get here
291 	return B_ERROR;
292 }
293 
294 
295 //	#pragma mark -
296 
297 
298 static status_t
299 ext2_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name,
300 	ino_t* _vnodeID)
301 {
302 	Volume* volume = (Volume*)_volume->private_volume;
303 	Inode* directory = (Inode*)_directory->private_node;
304 
305 	// check access permissions
306 	status_t status = directory->CheckPermissions(X_OK);
307 	if (status < B_OK)
308 		return status;
309 
310 	DirectoryIterator iterator(directory);
311 	while (true) {
312 		char buffer[B_FILE_NAME_LENGTH];
313 		size_t length = sizeof(buffer);
314 		status = iterator.GetNext(buffer, &length, _vnodeID);
315 		if (status != B_OK)
316 			return status;
317 
318 		if (!strcmp(buffer, name))
319 			break;
320 	}
321 
322 	return get_vnode(volume->FSVolume(), *_vnodeID, NULL);
323 }
324 
325 
326 static status_t
327 ext2_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
328 {
329 	Inode* inode = (Inode*)_node->private_node;
330 	const ext2_inode& node = inode->Node();
331 
332 	stat->st_dev = inode->GetVolume()->ID();
333 	stat->st_ino = inode->ID();
334 	stat->st_nlink = node.NumLinks();
335 	stat->st_blksize = EXT2_IO_SIZE;
336 
337 	stat->st_uid = node.UserID();
338 	stat->st_gid = node.GroupID();
339 	stat->st_mode = node.Mode();
340 	stat->st_type = 0;
341 
342 	stat->st_atime = node.AccessTime();
343 	stat->st_mtime = stat->st_ctime = node.ModificationTime();
344 	stat->st_crtime = node.CreationTime();
345 
346 	stat->st_size = inode->Size();
347 	stat->st_blocks = (inode->Size() + 511) / 512;
348 
349 	return B_OK;
350 }
351 
352 
353 static status_t
354 ext2_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
355 {
356 	Inode* inode = (Inode*)_node->private_node;
357 
358 	// opening a directory read-only is allowed, although you can't read
359 	// any data from it.
360 	if (inode->IsDirectory() && (openMode & O_RWMASK) != 0)
361 		return B_IS_A_DIRECTORY;
362 
363 	if ((openMode & O_TRUNC) != 0)
364 		return B_READ_ONLY_DEVICE;
365 
366 	return inode->CheckPermissions(open_mode_to_access(openMode)
367 		| (openMode & O_TRUNC ? W_OK : 0));
368 }
369 
370 
371 static status_t
372 ext2_read(fs_volume *_volume, fs_vnode *_node, void *_cookie, off_t pos,
373 	void *buffer, size_t *_length)
374 {
375 	Inode *inode = (Inode *)_node->private_node;
376 
377 	if (!inode->IsFile()) {
378 		*_length = 0;
379 		return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE;
380 	}
381 
382 	return inode->ReadAt(pos, (uint8 *)buffer, _length);
383 }
384 
385 
386 static status_t
387 ext2_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
388 {
389 	return B_OK;
390 }
391 
392 
393 static status_t
394 ext2_free_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie)
395 {
396 	return B_OK;
397 }
398 
399 
400 static status_t
401 ext2_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
402 {
403 	Inode* inode = (Inode*)_node->private_node;
404 	return inode->CheckPermissions(accessMode);
405 }
406 
407 
408 static status_t
409 ext2_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer,
410 	size_t *_bufferSize)
411 {
412 	Inode* inode = (Inode*)_node->private_node;
413 
414 	if (!inode->IsSymLink())
415 		return B_BAD_VALUE;
416 
417 	if (inode->Size() < *_bufferSize)
418 		*_bufferSize = inode->Size();
419 
420 	if (inode->Size() > EXT2_SHORT_SYMLINK_LENGTH)
421 		return inode->ReadAt(0, (uint8 *)buffer, _bufferSize);
422 
423 	memcpy(buffer, inode->Node().symlink, *_bufferSize);
424 	return B_OK;
425 }
426 
427 
428 //	#pragma mark - Directory functions
429 
430 
431 static status_t
432 ext2_open_dir(fs_volume *_volume, fs_vnode *_node, void **_cookie)
433 {
434 	Inode *inode = (Inode *)_node->private_node;
435 	status_t status = inode->CheckPermissions(R_OK);
436 	if (status < B_OK)
437 		return status;
438 
439 	if (!inode->IsDirectory())
440 		return B_BAD_VALUE;
441 
442 	DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode);
443 	if (iterator == NULL)
444 		return B_NO_MEMORY;
445 
446 	*_cookie = iterator;
447 	return B_OK;
448 }
449 
450 
451 static status_t
452 ext2_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie,
453 	struct dirent *dirent, size_t bufferSize, uint32 *_num)
454 {
455 	DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
456 
457 	size_t length = bufferSize;
458 	ino_t id;
459 	status_t status = iterator->GetNext(dirent->d_name, &length, &id);
460 	if (status == B_ENTRY_NOT_FOUND) {
461 		*_num = 0;
462 		return B_OK;
463 	} else if (status != B_OK)
464 		return status;
465 
466 	Volume* volume = (Volume*)_volume->private_volume;
467 
468 	dirent->d_dev = volume->ID();
469 	dirent->d_ino = id;
470 	dirent->d_reclen = sizeof(struct dirent) + length;
471 
472 	*_num = 1;
473 	return B_OK;
474 }
475 
476 
477 static status_t
478 ext2_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie)
479 {
480 	DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
481 	return iterator->Rewind();
482 }
483 
484 
485 static status_t
486 ext2_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/)
487 {
488 	return B_OK;
489 }
490 
491 
492 static status_t
493 ext2_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie)
494 {
495 	delete (DirectoryIterator *)_cookie;
496 	return B_OK;
497 }
498 
499 
500 fs_volume_ops gExt2VolumeOps = {
501 	&ext2_unmount,
502 	&ext2_read_fs_info,
503 	NULL,	// write_fs_info()
504 	NULL,	// sync()
505 	&ext2_get_vnode,
506 };
507 
508 fs_vnode_ops gExt2VnodeOps = {
509 	/* vnode operations */
510 	&ext2_lookup,
511 	NULL,
512 	&ext2_put_vnode,
513 	NULL,
514 
515 	/* VM file access */
516 	&ext2_can_page,
517 	&ext2_read_pages,
518 	NULL,
519 
520 	NULL,	// io()
521 	NULL,	// cancel_io()
522 
523 	&ext2_get_file_map,
524 
525 	NULL,
526 	NULL,
527 	NULL,	// fs_select
528 	NULL,	// fs_deselect
529 	NULL,
530 
531 	&ext2_read_link,
532 	NULL,
533 
534 	NULL,
535 	NULL,
536 	NULL,
537 
538 	&ext2_access,
539 	&ext2_read_stat,
540 	NULL,
541 
542 	/* file operations */
543 	NULL,
544 	&ext2_open,
545 	&ext2_close,
546 	&ext2_free_cookie,
547 	&ext2_read,
548 	NULL,
549 
550 	/* directory operations */
551 	NULL,
552 	NULL,
553 	&ext2_open_dir,
554 	&ext2_close_dir,
555 	&ext2_free_dir_cookie,
556 	&ext2_read_dir,
557 	&ext2_rewind_dir,
558 
559 	NULL,
560 };
561 
562 static file_system_module_info sExt2FileSystem = {
563 	{
564 		"file_systems/ext2" B_CURRENT_FS_API_VERSION,
565 		0,
566 		NULL,
567 	},
568 
569 	"ext2",						// short_name
570 	"Ext2 File System",			// pretty_name
571 	0,							// DDM flags
572 
573 	// scanning
574 	ext2_identify_partition,
575 	ext2_scan_partition,
576 	ext2_free_identify_partition_cookie,
577 	NULL,	// free_partition_content_cookie()
578 
579 	&ext2_mount,
580 
581 	NULL,
582 };
583 
584 module_info *modules[] = {
585 	(module_info *)&sExt2FileSystem,
586 	NULL,
587 };
588