1 /* radare2 - LGPL - Copyright 2011-2019 - pancake */
2 
3 #include <r_fs.h>
4 #include "config.h"
5 #include "types.h"
6 #include <errno.h>
7 #include "../../shlr/grub/include/grub/msdos_partition.h"
8 
9 #if WITH_GPL
10 # ifndef USE_GRUB
11 #  define USE_GRUB 1
12 # endif
13 #endif
14 
15 R_LIB_VERSION (r_fs);
16 
17 static RFSPlugin* fs_static_plugins[] = {
18 	R_FS_STATIC_PLUGINS
19 };
20 
r_fs_new(void)21 R_API RFS* r_fs_new(void) {
22 	int i;
23 	RFSPlugin* static_plugin;
24 	RFS* fs = R_NEW0 (RFS);
25 	if (fs) {
26 		fs->view = R_FS_VIEW_NORMAL;
27 		fs->roots = r_list_new ();
28 		if (!fs->roots) {
29 			r_fs_free (fs);
30 			return NULL;
31 		}
32 		fs->roots->free = (RListFree) r_fs_root_free;
33 		fs->plugins = r_list_new ();
34 		if (!fs->plugins) {
35 			r_fs_free (fs);
36 			return NULL;
37 		}
38 		fs->plugins->free = free;
39 		// XXX fs->roots->free = r_fs_plugin_free;
40 		for (i = 0; fs_static_plugins[i]; i++) {
41 			static_plugin = R_NEW (RFSPlugin);
42 			if (!static_plugin) {
43 				continue;
44 			}
45 			memcpy (static_plugin, fs_static_plugins[i], sizeof (RFSPlugin));
46 			r_fs_add (fs, static_plugin);
47 			free (static_plugin);
48 		}
49 	}
50 	return fs;
51 }
52 
r_fs_plugin_get(RFS * fs,const char * name)53 R_API RFSPlugin* r_fs_plugin_get(RFS* fs, const char* name) {
54 	RListIter* iter;
55 	RFSPlugin* p;
56 	if (!fs || !name) {
57 		return NULL;
58 	}
59 	r_list_foreach (fs->plugins, iter, p) {
60 		if (!strcmp (p->name, name)) {
61 			return p;
62 		}
63 	}
64 	return NULL;
65 }
66 
r_fs_free(RFS * fs)67 R_API void r_fs_free(RFS* fs) {
68 	if (!fs) {
69 		return;
70 	}
71 	//r_io_free (fs->iob.io);
72 	//root makes use of plugin so revert to avoid UaF
73 	r_list_free (fs->roots);
74 	r_list_free (fs->plugins);
75 	free (fs);
76 }
77 
78 /* plugins */
r_fs_add(RFS * fs,RFSPlugin * p)79 R_API void r_fs_add(RFS* fs, RFSPlugin* p) {
80 	// TODO: find coliding plugin name
81 	if (p && p->init) {
82 		p->init ();
83 	}
84 	RFSPlugin* sp = R_NEW0 (RFSPlugin);
85 	if (sp) {
86 		if (p) {
87 			memcpy (sp, p, sizeof (RFSPlugin));
88 		}
89 		r_list_append (fs->plugins, sp);
90 	}
91 }
92 
r_fs_del(RFS * fs,RFSPlugin * p)93 R_API void r_fs_del(RFS* fs, RFSPlugin* p) {
94 	// TODO: implement r_fs_del
95 }
96 
97 /* mountpoint */
r_fs_mount(RFS * fs,const char * fstype,const char * path,ut64 delta)98 R_API RFSRoot* r_fs_mount(RFS* fs, const char* fstype, const char* path, ut64 delta) {
99 	RFSPlugin* p;
100 	RFSRoot* root;
101 	RListIter* iter;
102 	char* str;
103 	int len, lenstr;
104 	char *heapFsType = NULL;
105 
106 	if (path[0] != '/') {
107 		eprintf ("r_fs_mount: invalid mountpoint %s\n", path);
108 		return NULL;
109 	}
110 	if (!fstype || !*fstype) {
111 		heapFsType = r_fs_name (fs, delta);
112 		fstype = (const char *)heapFsType;
113 	}
114 	if (!(p = r_fs_plugin_get (fs, fstype))) {
115 		// eprintf ("r_fs_mount: Invalid filesystem type\n");
116 		free (heapFsType);
117 		return NULL;
118 	}
119 	str = strdup (path);
120 	if (!str) {
121 		free (heapFsType);
122 		return NULL;
123 	}
124 	r_str_trim_path (str);
125 	if (*str && strchr (str + 1, '/')) {
126 		eprintf ("r_fs_mount: mountpoint must have no subdirectories\n");
127 		free (heapFsType);
128 		return NULL;
129 	}
130 	/* Check if path exists */
131 	r_list_foreach (fs->roots, iter, root) {
132 		len = strlen (root->path);
133 		lenstr = strlen (str);
134 		if (!strncmp (str, root->path, len)) {
135 			if (len < lenstr && str[len] != '/') {
136 				continue;
137 			}
138 			if (len > lenstr && root->path[lenstr] == '/') {
139 				continue;
140 			}
141 			eprintf ("r_fs_mount: Invalid mount point\n");
142 			free (str);
143 			free (heapFsType);
144 			return NULL;
145 		}
146 	}
147 	RFSFile* file = r_fs_open (fs, str, false);
148 	if (file) {
149 		r_fs_close (fs, file);
150 		eprintf ("r_fs_mount: Invalid mount point\n");
151 		free (heapFsType);
152 		free (str);
153 		return NULL;
154 	}
155 	RList *list = r_fs_dir (fs, str);
156 	if (!r_list_empty (list)) {
157 		//XXX: list need free ??
158 		eprintf ("r_fs_mount: Invalid mount point\n");
159 		free (str);
160 		free (heapFsType);
161 		return NULL;
162 	}
163 	// TODO: we should just construct the root with the rfs instance
164 	root = r_fs_root_new (str, delta);
165 	root->p = p;
166 	root->iob = fs->iob;
167 	root->cob = fs->cob;
168 	if (!p->mount (root)) {
169 		free (str);
170 		free (heapFsType);
171 		r_fs_root_free (root);
172 		return NULL;
173 	}
174 	r_list_append (fs->roots, root);
175 	eprintf ("Mounted %s on %s at 0x%" PFMT64x "\n", fstype, str, delta);
176 	free (str);
177 	free (heapFsType);
178 	return root;
179 }
180 
r_fs_match(const char * root,const char * path,int len)181 static inline bool r_fs_match(const char* root, const char* path, int len) {
182 	return (!strncmp (path, root, len));
183 }
184 
r_fs_umount(RFS * fs,const char * path)185 R_API bool r_fs_umount(RFS* fs, const char* path) {
186 	int len;
187 	RFSRoot* root;
188 	RListIter* iter, * riter = NULL;
189 
190 	if (!path) {
191 		return false;
192 	}
193 
194 	r_list_foreach (fs->roots, iter, root) {
195 		len = strlen (root->path);
196 		if (r_fs_match (path, root->path, len)) {
197 			riter = iter;
198 		}
199 	}
200 	if (riter) {
201 		r_list_delete (fs->roots, riter);
202 		return true;
203 	}
204 	return false;
205 }
206 
r_fs_root(RFS * fs,const char * p)207 R_API RList* r_fs_root(RFS* fs, const char* p) {
208 	RList* roots;
209 	RFSRoot* root;
210 	RListIter* iter;
211 	int len, olen;
212 	char* path = strdup (p);
213 	if (!path) {
214 		return NULL;
215 	}
216 	roots = r_list_new ();
217 	r_str_trim_path (path);
218 	r_list_foreach (fs->roots, iter, root) {
219 		len = strlen (root->path);
220 		if (r_fs_match (path, root->path, len)) {
221 			olen = strlen (path);
222 			if (len == 1 || olen == len) {
223 				r_list_append (roots, root);
224 			} else if (olen > len && path[len] == '/') {
225 				r_list_append (roots, root);
226 			}
227 		}
228 	}
229 	free (path);
230 	return roots;
231 }
232 
233 /* filez */
r_fs_open(RFS * fs,const char * p,bool create)234 R_API RFSFile* r_fs_open(RFS* fs, const char* p, bool create) {
235 	RFSRoot* root;
236 	RListIter* iter;
237 	RFSFile* f = NULL;
238 	const char* dir;
239 	char* path = r_str_trim_dup (p);
240 	RList *roots = r_fs_root (fs, path);
241 	if (!r_list_empty (roots)) {
242 		r_list_foreach (roots, iter, root) {
243 			if (create) {
244 				if (root && root->p && root->p->write) {
245 					f = r_fs_file_new (root, path + strlen (root->path));
246 					break;
247 				}
248 				continue;
249 			}
250 			if (root && root->p && root->p->open) {
251 				if (strlen (root->path) == 1) {
252 					dir = path;
253 				} else {
254 					dir = path + strlen (root->path);
255 				}
256 				f = root->p->open (root, dir, false);
257 				if (f) {
258 					break;
259 				}
260 			}
261 		}
262 	}
263 	free (roots);
264 	free (path);
265 	return f;
266 }
267 
268 // NOTE: close doesnt free
r_fs_close(RFS * fs,RFSFile * file)269 R_API void r_fs_close(RFS* fs, RFSFile* file) {
270 	if (fs && file) {
271 		R_FREE (file->data);
272 		if (file->p && file->p->close) {
273 			file->p->close (file);
274 		}
275 	}
276 }
277 
r_fs_write(RFS * fs,RFSFile * file,ut64 addr,const ut8 * data,int len)278 R_API int r_fs_write(RFS* fs, RFSFile* file, ut64 addr, const ut8 *data, int len) {
279 	if (len < 1) {
280 		return -1;
281 	}
282 	if (fs && file) {
283 		// TODO: fill file->data ? looks like dupe of rbuffer
284 		if (file->p && file->p->write) {
285 			return file->p->write (file, addr, data, len);;
286 		}
287 		eprintf ("r_fs_write: file->p->write is null\n");
288 	}
289 	return -1;
290 }
291 
r_fs_read(RFS * fs,RFSFile * file,ut64 addr,int len)292 R_API int r_fs_read(RFS* fs, RFSFile* file, ut64 addr, int len) {
293 	if (len < 1) {
294 		eprintf ("r_fs_read: too short read\n");
295 		return -1;
296 	}
297 	if (fs && file) {
298 		if (file->p && file->p->read) {
299 			if (!file->data) {
300 				free (file->data);
301 				file->data = calloc (1, len + 1);
302 			}
303 			return file->p->read (file, addr, len);
304 		} else {
305 			eprintf ("r_fs_read: file->p->read is null\n");
306 		}
307 	}
308 	return -1;
309 }
310 
r_fs_dir(RFS * fs,const char * p)311 R_API RList* r_fs_dir(RFS* fs, const char* p) {
312 	RList *ret = NULL;
313 	RFSRoot* root;
314 	RListIter* iter;
315 	const char* dir;
316 	char* path = strdup (p);
317 	r_str_trim_path (path);
318 	RList *roots = r_fs_root (fs, path);
319 	r_list_foreach (roots, iter, root) {
320 		if (root) {
321 			if (strlen (root->path) == 1) {
322 				dir = path;
323 			} else {
324 				dir = path + strlen (root->path);
325 			}
326 			if (!*dir) {
327 				dir = "/";
328 			}
329 			ret = root->p->dir (root, dir, fs->view);
330 			if (ret) {
331 				break;
332 			}
333 		}
334 	}
335 	free (roots);
336 	free (path);
337 	return ret;
338 }
339 
r_fs_dir_dump(RFS * fs,const char * path,const char * name)340 R_API int r_fs_dir_dump(RFS* fs, const char* path, const char* name) {
341 	RList* list;
342 	RListIter* iter;
343 	RFSFile* file, * item;
344 	char* str, * npath;
345 
346 	list = r_fs_dir (fs, path);
347 	if (!list) {
348 		return false;
349 	}
350 	if (!r_sys_mkdir (name)) {
351 		if (r_sys_mkdir_failed ()) {
352 			eprintf ("Cannot create \"%s\"\n", name);
353 			return false;
354 		}
355 	}
356 	r_list_foreach (list, iter, file) {
357 		if (!strcmp (file->name, ".") || !strcmp (file->name, "..")) {
358 			continue;
359 		}
360 		str = (char*) malloc (strlen (name) + strlen (file->name) + 2);
361 		if (!str) {
362 			return false;
363 		}
364 		strcpy (str, name);
365 		strcat (str, "/");
366 		strcat (str, file->name);
367 		npath = malloc (strlen (path) + strlen (file->name) + 2);
368 		if (!npath) {
369 			free (str);
370 			return false;
371 		}
372 		strcpy (npath, path);
373 		strcat (npath, "/");
374 		strcat (npath, file->name);
375 		switch (file->type) {
376 		// DON'T FOLLOW MOUNTPOINTS
377 		case R_FS_FILE_TYPE_DIRECTORY:
378 			if (!r_fs_dir_dump (fs, npath, str)) {
379 				free (npath);
380 				free (str);
381 				return false;
382 			}
383 			break;
384 		case R_FS_FILE_TYPE_REGULAR:
385 			item = r_fs_open (fs, npath, false);
386 			if (item) {
387 				r_fs_read (fs, item, 0, item->size);
388 				if (!r_file_dump (str, item->data, item->size, 0)) {
389 					free (npath);
390 					free (str);
391 					return false;
392 				}
393 				r_fs_close (fs, item);
394 			}
395 			break;
396 		}
397 		free (npath);
398 		free (str);
399 	}
400 	return true;
401 }
402 
r_fs_find_off_aux(RFS * fs,const char * name,ut64 offset,RList * list)403 static void r_fs_find_off_aux(RFS* fs, const char* name, ut64 offset, RList* list) {
404 	RList* dirs;
405 	RListIter* iter;
406 	RFSFile* item, * file;
407 	char* found = NULL;
408 
409 	dirs = r_fs_dir (fs, name);
410 	r_list_foreach (dirs, iter, item) {
411 		if (!strcmp (item->name, ".") || !strcmp (item->name, "..")) {
412 			continue;
413 		}
414 
415 		found = (char*) malloc (strlen (name) + strlen (item->name) + 2);
416 		if (!found) {
417 			break;
418 		}
419 		strcpy (found, name);
420 		strcat (found, "/");
421 		strcat (found, item->name);
422 
423 		if (item->type == R_FS_FILE_TYPE_DIRECTORY) {
424 			r_fs_find_off_aux (fs, found, offset, list);
425 		} else {
426 			file = r_fs_open (fs, found, false);
427 			if (file) {
428 				r_fs_read (fs, file, 0, file->size);
429 				if (file->off == offset) {
430 					r_list_append (list, found);
431 				}
432 				r_fs_close (fs, file);
433 			}
434 		}
435 		free (found);
436 	}
437 }
438 
r_fs_find_off(RFS * fs,const char * name,ut64 off)439 R_API RList* r_fs_find_off(RFS* fs, const char* name, ut64 off) {
440 	RList* list = r_list_new ();
441 	if (!list) {
442 		return NULL;
443 	}
444 	list->free = free;
445 	r_fs_find_off_aux (fs, name, off, list);
446 	return list;
447 }
448 
r_fs_find_name_aux(RFS * fs,const char * name,const char * glob,RList * list)449 static void r_fs_find_name_aux(RFS* fs, const char* name, const char* glob, RList* list) {
450 	RList* dirs;
451 	RListIter* iter;
452 	RFSFile* item;
453 	char* found;
454 
455 	dirs = r_fs_dir (fs, name);
456 	r_list_foreach (dirs, iter, item) {
457 		if (r_str_glob (item->name, glob)) {
458 			found = (char*) malloc (strlen (name) + strlen (item->name) + 2);
459 			if (!found) {
460 				break;
461 			}
462 			strcpy (found, name);
463 			strcat (found, "/");
464 			strcat (found, item->name);
465 			r_list_append (list, found);
466 		}
467 		if (!strcmp (item->name, ".") || !strcmp (item->name, "..")) {
468 			continue;
469 		}
470 		if (item->type == R_FS_FILE_TYPE_DIRECTORY) {
471 			found = (char*) malloc (strlen (name) + strlen (item->name) + 2);
472 			if (!found) {
473 				break;
474 			}
475 			strcpy (found, name);
476 			strcat (found, "/");
477 			strcat (found, item->name);
478 			r_fs_find_name_aux (fs, found, glob, list);
479 			free (found);
480 		}
481 	}
482 }
483 
r_fs_find_name(RFS * fs,const char * name,const char * glob)484 R_API RList* r_fs_find_name(RFS* fs, const char* name, const char* glob) {
485 	RList* list = r_list_newf (free);
486 	if (list) {
487 		r_fs_find_name_aux (fs, name, glob, list);
488 	}
489 	return list;
490 }
491 
r_fs_slurp(RFS * fs,const char * path)492 R_API RFSFile* r_fs_slurp(RFS* fs, const char* path) {
493 	RFSFile* file = NULL;
494 	RFSRoot* root;
495 	RList* roots = r_fs_root (fs, path);
496 	RListIter* iter;
497 	r_list_foreach (roots, iter, root) {
498 		if (!root || !root->p) {
499 			continue;
500 		}
501 		if (root->p->open && root->p->read && root->p->close) {
502 			file = root->p->open (root, path, false);
503 			if (file) {
504 				root->p->read (file, 0, file->size); //file->data
505 			}else {
506 				eprintf ("r_fs_slurp: cannot open file\n");
507 			}
508 		} else {
509 			if (root->p->slurp) {
510 				free (roots);
511 				return root->p->slurp (root, path);
512 			}
513 			eprintf ("r_fs_slurp: null root->p->slurp\n");
514 		}
515 	}
516 	free (roots);
517 	return file;
518 }
519 
520 // TODO: move into grubfs
521 #include "../../shlr/grub/include/grubfs.h"
522 
523 #if USE_GRUB
grub_parhook(void * disk,void * ptr,void * closure)524 static int grub_parhook(void* disk, void* ptr, void* closure) {
525 	struct grub_partition* par = ptr;
526 	RList* list = (RList*) closure;
527 	RFSPartition* p = r_fs_partition_new (
528 		r_list_length (list),
529 		par->start * 512, 512 * par->len);
530 	p->type = par->msdostype;
531 	r_list_append (list, p);
532 	return 0;
533 }
534 #endif
535 
fs_parhook(void * disk,void * ptr,void * closure)536 static int fs_parhook(void* disk, void* ptr, void* closure) {
537 	RFSPartition* par = ptr;
538 	RList* list = (RList*) closure;
539 	r_list_append (list, par);
540 	return 0;
541 }
542 
543 #include "p/part_dos.c"
544 
545 static RFSPartitionType partitions[] = {
546 	/* LGPL code */
547 	{"dos", &fs_part_dos, fs_parhook},
548 #if USE_GRUB
549 	/* WARNING GPL code */
550 #if !__EMSCRIPTEN__
551 // wtf for some reason is not available on emscripten
552 	{"msdos", &grub_msdos_partition_map, grub_parhook},
553 #endif
554 	{"apple", &grub_apple_partition_map, grub_parhook},
555 	{"sun", &grub_sun_partition_map, grub_parhook},
556 	{"sunpc", &grub_sun_pc_partition_map, grub_parhook},
557 	{"amiga", &grub_amiga_partition_map, grub_parhook},
558 	{"bsdlabel", &grub_bsdlabel_partition_map, grub_parhook},
559 	{"gpt", &grub_gpt_partition_map, grub_parhook},
560 #endif
561 	// XXX: In BURG all bsd partition map are in bsdlabel
562 	//{ "openbsdlabel", &grub_openbsd_partition_map },
563 	//{ "netbsdlabel", &grub_netbsd_partition_map },
564 	//{ "acorn", &grub_acorn_partition_map },
565 	{ NULL }
566 };
567 
r_fs_partition_type_get(int n)568 R_API const char* r_fs_partition_type_get(int n) {
569 	if (n < 0 || n >= R_FS_PARTITIONS_LENGTH) {
570 		return NULL;
571 	}
572 	return partitions[n].name;
573 }
574 
r_fs_partition_get_size(void)575 R_API int r_fs_partition_get_size(void) {
576 	return R_FS_PARTITIONS_LENGTH;
577 }
578 
r_fs_partitions(RFS * fs,const char * ptype,ut64 delta)579 R_API RList* r_fs_partitions(RFS* fs, const char* ptype, ut64 delta) {
580 	int i, cur = -1;
581 	for (i = 0; partitions[i].name; i++) {
582 		if (!strcmp (ptype, partitions[i].name)) {
583 			cur = i;
584 			break;
585 		}
586 	}
587 	if (cur != -1) {
588 		RList* list = r_list_newf ((RListFree) r_fs_partition_free);
589 #if USE_GRUB
590 		void* disk = NULL;
591 		if (partitions[i].iterate == grub_parhook) {
592 			struct grub_partition_map* gpt = partitions[i].ptr;
593 			grubfs_bind_io (NULL, 0);
594 			disk = (void*) grubfs_disk (&fs->iob);
595 			if (gpt) {
596 				gpt->iterate (disk,
597 					(void*) partitions[i].iterate, list);
598 			}
599 			grubfs_free (disk);
600 		} else {
601 #else
602 		{
603 #endif
604 			RFSPartitionIterator iterate = partitions[i].ptr;
605 			iterate (fs, partitions[i].iterate, list); //grub_parhook, list);
606 		}
607 		return list;
608 	}
609 	if (ptype && *ptype) {
610 		eprintf ("Unknown partition type '%s'.\n", ptype);
611 	}
612 	eprintf ("Supported types:\n");
613 	for (i = 0; partitions[i].name; i++) {
614 		eprintf (" %s", partitions[i].name);
615 	}
616 	eprintf ("\n");
617 	return NULL;
618 }
619 
620 R_API int r_fs_partition_type_str(const char* type) {
621 	// TODO: implement
622 	return 0;
623 }
624 
625 R_API const char* r_fs_partition_type(const char* part, int type) {
626 	// XXX: part is ignored O_o
627 	switch (type) {
628 	case GRUB_PC_PARTITION_TYPE_FAT12:
629 	case GRUB_PC_PARTITION_TYPE_FAT16_GT32M:
630 	case GRUB_PC_PARTITION_TYPE_FAT16_LT32M:
631 	case GRUB_PC_PARTITION_TYPE_FAT32:
632 	case GRUB_PC_PARTITION_TYPE_FAT32_LBA:
633 	case GRUB_PC_PARTITION_TYPE_FAT16_LBA:
634 		return strdup ("fat");
635 
636 	case GRUB_PC_PARTITION_TYPE_EXT2FS:
637 		return strdup ("ext2");
638 
639 	case GRUB_PC_PARTITION_TYPE_MINIX:
640 	case GRUB_PC_PARTITION_TYPE_LINUX_MINIX:
641 		return strdup ("minix");
642 
643 	case GRUB_PC_PARTITION_TYPE_NTFS:
644 		return strdup ("ntfs");
645 
646 	case GRUB_PC_PARTITION_TYPE_EXTENDED:
647 	case GRUB_PC_PARTITION_TYPE_LINUX_EXTENDED:
648 		return strdup ("ext3");
649 
650 	case GRUB_PC_PARTITION_TYPE_HFS:
651 		return strdup ("hfs");
652 
653 	case GRUB_PC_PARTITION_TYPE_WIN95_EXTENDED: // fat?
654 	case GRUB_PC_PARTITION_TYPE_EZD:
655 	case GRUB_PC_PARTITION_TYPE_VSTAFS:
656 	case GRUB_PC_PARTITION_TYPE_FREEBSD: // ufs
657 	case GRUB_PC_PARTITION_TYPE_OPENBSD: // ufs
658 	case GRUB_PC_PARTITION_TYPE_NETBSD:  // ufs
659 	case GRUB_PC_PARTITION_TYPE_GPT_DISK:
660 	case GRUB_PC_PARTITION_TYPE_LINUX_RAID:
661 	case GRUB_PC_PARTITION_TYPE_NONE:
662 	default:
663 		return NULL;
664 	}
665 }
666 
667 R_API char* r_fs_name(RFS* fs, ut64 offset) {
668 	ut8 buf[1024];
669 	int i, j, len, ret = false;
670 
671 	for (i = 0; fstypes[i].name; i++) {
672 		RFSType* f = &fstypes[i];
673 		len = R_MIN (f->buflen, sizeof (buf) - 1);
674 		fs->iob.read_at (fs->iob.io, offset + f->bufoff, buf, len);
675 		if (f->buflen > 0 && !memcmp (buf, f->buf, f->buflen)) {
676 			ret = true;
677 			len = R_MIN (f->bytelen, sizeof (buf));
678 			fs->iob.read_at (fs->iob.io, offset + f->byteoff, buf, len);
679 			// for (j = 0; j < f->bytelen; j++) {
680 			for (j = 0; j < len; j++) {
681 				if (buf[j] != f->byte) {
682 					ret = false;
683 					break;
684 				}
685 			}
686 			if (ret) {
687 				return strdup (f->name);
688 			}
689 		}
690 	}
691 	return NULL;
692 }
693 
694 R_API void r_fs_view(RFS* fs, int view) {
695 	fs->view = view;
696 }
697 
698 R_API bool r_fs_check(RFS *fs, const char *p) {
699 	RFSRoot *root;
700 	RListIter *iter;
701 	char* path = strdup (p);
702 	if (!path) {
703 		return false;
704 	}
705 	r_str_trim_path (path);
706 	r_list_foreach (fs->roots, iter, root) {
707 		if (r_fs_match (path, root->path, strlen (root->path))) {
708 			free (path);
709 			return true;
710 		}
711 	}
712 	free (path);
713 	return false;
714 }
715