xref: /haiku/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp (revision 746cac055adc6ac3308c7bc2d29040fb95689cc9)
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].length += blockLength;
263 		} else {
264 			if (index >= max) {
265 				// we're out of file_io_vecs; let's bail out
266 				*_count = index;
267 				return B_BUFFER_OVERFLOW;
268 			}
269 
270 			vecs[index].offset = blockOffset;
271 			vecs[index].length = blockLength;
272 			index++;
273 		}
274 
275 		offset += blockLength;
276 
277 		if (size <= vecs[index - 1].length || offset >= inode->Size()) {
278 			// We're done!
279 			*_count = index;
280 			return B_OK;
281 		}
282 	}
283 
284 	// can never get here
285 	return B_ERROR;
286 }
287 
288 
289 //	#pragma mark -
290 
291 
292 static status_t
293 ext2_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name,
294 	ino_t* _vnodeID)
295 {
296 	Volume* volume = (Volume*)_volume->private_volume;
297 	Inode* directory = (Inode*)_directory->private_node;
298 
299 	// check access permissions
300 	status_t status = directory->CheckPermissions(X_OK);
301 	if (status < B_OK)
302 		return status;
303 
304 	DirectoryIterator iterator(directory);
305 	while (true) {
306 		char buffer[B_FILE_NAME_LENGTH];
307 		size_t length = sizeof(buffer);
308 		status = iterator.GetNext(buffer, &length, _vnodeID);
309 		if (status != B_OK)
310 			return status;
311 
312 		if (!strcmp(buffer, name))
313 			break;
314 	}
315 
316 	return get_vnode(volume->FSVolume(), *_vnodeID, NULL);
317 }
318 
319 
320 static status_t
321 ext2_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
322 {
323 	Inode* inode = (Inode*)_node->private_node;
324 	const ext2_inode& node = inode->Node();
325 
326 	stat->st_dev = inode->GetVolume()->ID();
327 	stat->st_ino = inode->ID();
328 	stat->st_nlink = node.NumLinks();
329 	stat->st_blksize = EXT2_IO_SIZE;
330 
331 	stat->st_uid = node.UserID();
332 	stat->st_gid = node.GroupID();
333 	stat->st_mode = node.Mode();
334 	stat->st_type = 0;
335 
336 	stat->st_atime = node.AccessTime();
337 	stat->st_mtime = stat->st_ctime = node.ModificationTime();
338 	stat->st_crtime = node.CreationTime();
339 
340 	stat->st_size = inode->Size();
341 	return B_OK;
342 }
343 
344 
345 static status_t
346 ext2_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
347 {
348 	Inode* inode = (Inode*)_node->private_node;
349 
350 	// opening a directory read-only is allowed, although you can't read
351 	// any data from it.
352 	if (inode->IsDirectory() && (openMode & O_RWMASK) != 0)
353 		return B_IS_A_DIRECTORY;
354 
355 	if ((openMode & O_TRUNC) != 0)
356 		return B_READ_ONLY_DEVICE;
357 
358 	return inode->CheckPermissions(open_mode_to_access(openMode)
359 		| (openMode & O_TRUNC ? W_OK : 0));
360 }
361 
362 
363 static status_t
364 ext2_read(fs_volume *_volume, fs_vnode *_node, void *_cookie, off_t pos,
365 	void *buffer, size_t *_length)
366 {
367 	Inode *inode = (Inode *)_node->private_node;
368 
369 	if (!inode->IsFile()) {
370 		*_length = 0;
371 		return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE;
372 	}
373 
374 	return inode->ReadAt(pos, (uint8 *)buffer, _length);
375 }
376 
377 
378 static status_t
379 ext2_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
380 {
381 	return B_OK;
382 }
383 
384 
385 static status_t
386 ext2_free_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie)
387 {
388 	return B_OK;
389 }
390 
391 
392 static status_t
393 ext2_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
394 {
395 	Inode* inode = (Inode*)_node->private_node;
396 	return inode->CheckPermissions(accessMode);
397 }
398 
399 
400 static status_t
401 ext2_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer,
402 	size_t *_bufferSize)
403 {
404 	Inode* inode = (Inode*)_node->private_node;
405 
406 	if (!inode->IsSymLink())
407 		return B_BAD_VALUE;
408 
409 	if (inode->Size() < *_bufferSize)
410 		*_bufferSize = inode->Size();
411 
412 	if (inode->Size() > EXT2_SHORT_SYMLINK_LENGTH)
413 		return inode->ReadAt(0, (uint8 *)buffer, _bufferSize);
414 
415 	memcpy(buffer, inode->Node().symlink, *_bufferSize);
416 	return B_OK;
417 }
418 
419 
420 //	#pragma mark - Directory functions
421 
422 
423 static status_t
424 ext2_open_dir(fs_volume *_volume, fs_vnode *_node, void **_cookie)
425 {
426 	Inode *inode = (Inode *)_node->private_node;
427 	status_t status = inode->CheckPermissions(R_OK);
428 	if (status < B_OK)
429 		return status;
430 
431 	if (!inode->IsDirectory())
432 		return B_BAD_VALUE;
433 
434 	DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode);
435 	if (iterator == NULL)
436 		return B_NO_MEMORY;
437 
438 	*_cookie = iterator;
439 	return B_OK;
440 }
441 
442 
443 static status_t
444 ext2_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie,
445 	struct dirent *dirent, size_t bufferSize, uint32 *_num)
446 {
447 	DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
448 
449 	size_t length = bufferSize;
450 	ino_t id;
451 	status_t status = iterator->GetNext(dirent->d_name, &length, &id);
452 	if (status == B_ENTRY_NOT_FOUND) {
453 		*_num = 0;
454 		return B_OK;
455 	} else if (status != B_OK)
456 		return status;
457 
458 	Volume* volume = (Volume*)_volume->private_volume;
459 
460 	dirent->d_dev = volume->ID();
461 	dirent->d_ino = id;
462 	dirent->d_reclen = sizeof(struct dirent) + length;
463 
464 	*_num = 1;
465 	return B_OK;
466 }
467 
468 
469 static status_t
470 ext2_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie)
471 {
472 	DirectoryIterator *iterator = (DirectoryIterator *)_cookie;
473 	return iterator->Rewind();
474 }
475 
476 
477 static status_t
478 ext2_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/)
479 {
480 	return B_OK;
481 }
482 
483 
484 static status_t
485 ext2_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie)
486 {
487 	delete (DirectoryIterator *)_cookie;
488 	return B_OK;
489 }
490 
491 
492 fs_volume_ops gExt2VolumeOps = {
493 	&ext2_unmount,
494 	&ext2_read_fs_info,
495 	NULL,	// write_fs_info()
496 	NULL,	// sync()
497 	&ext2_get_vnode,
498 };
499 
500 fs_vnode_ops gExt2VnodeOps = {
501 	/* vnode operations */
502 	&ext2_lookup,
503 	NULL,
504 	&ext2_put_vnode,
505 	NULL,
506 
507 	/* VM file access */
508 	&ext2_can_page,
509 	&ext2_read_pages,
510 	NULL,
511 
512 	NULL,	// io()
513 	NULL,	// cancel_io()
514 
515 	&ext2_get_file_map,
516 
517 	NULL,
518 	NULL,
519 	NULL,	// fs_select
520 	NULL,	// fs_deselect
521 	NULL,
522 
523 	&ext2_read_link,
524 	NULL,
525 
526 	NULL,
527 	NULL,
528 	NULL,
529 
530 	&ext2_access,
531 	&ext2_read_stat,
532 	NULL,
533 
534 	/* file operations */
535 	NULL,
536 	&ext2_open,
537 	&ext2_close,
538 	&ext2_free_cookie,
539 	&ext2_read,
540 	NULL,
541 
542 	/* directory operations */
543 	NULL,
544 	NULL,
545 	&ext2_open_dir,
546 	&ext2_close_dir,
547 	&ext2_free_dir_cookie,
548 	&ext2_read_dir,
549 	&ext2_rewind_dir,
550 
551 	NULL,
552 };
553 
554 static file_system_module_info sExt2FileSystem = {
555 	{
556 		"file_systems/ext2" B_CURRENT_FS_API_VERSION,
557 		0,
558 		NULL,
559 	},
560 
561 	"ext2",						// short_name
562 	"Ext2 File System",			// pretty_name
563 	0,							// DDM flags
564 
565 	// scanning
566 	ext2_identify_partition,
567 	ext2_scan_partition,
568 	ext2_free_identify_partition_cookie,
569 	NULL,	// free_partition_content_cookie()
570 
571 	&ext2_mount,
572 
573 	NULL,
574 };
575 
576 module_info *modules[] = {
577 	(module_info *)&sExt2FileSystem,
578 	NULL,
579 };
580