xref: /haiku/src/tools/fs_shell/fssh.cpp (revision cfc3fa87da824bdf593eb8b817a83b6376e77935)
1 /*
2  * Copyright 2007-2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include "compatibility.h"
7 
8 #include "fssh.h"
9 
10 #include <stdarg.h>
11 #include <stdio.h>
12 #include <string.h>
13 #include <time.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16 
17 #include <vector>
18 
19 #include "command_cp.h"
20 #include "driver_settings.h"
21 #include "external_commands.h"
22 #include "fd.h"
23 #include "fssh_dirent.h"
24 #include "fssh_errno.h"
25 #include "fssh_errors.h"
26 #include "fssh_module.h"
27 #include "fssh_node_monitor.h"
28 #include "fssh_stat.h"
29 #include "fssh_string.h"
30 #include "fssh_type_constants.h"
31 #include "module.h"
32 #include "partition_support.h"
33 #include "path_util.h"
34 #include "syscalls.h"
35 #include "vfs.h"
36 
37 
38 extern fssh_module_info *modules[];
39 
40 
41 extern fssh_file_system_module_info gRootFileSystem;
42 
43 namespace FSShell {
44 
45 const char* kMountPoint = "/myfs";
46 
47 // command line args
48 static	int					sArgc;
49 static	const char* const*	sArgv;
50 
51 static mode_t sUmask = 0022;
52 
53 
54 static fssh_status_t
55 init_kernel()
56 {
57 	fssh_status_t error;
58 
59 	// init module subsystem
60 	error = module_init(NULL);
61 	if (error != FSSH_B_OK) {
62 		fprintf(stderr, "module_init() failed: %s\n", fssh_strerror(error));
63 		return error;
64 	}
65 
66 	// init driver settings
67 	error = driver_settings_init();
68 	if (error != FSSH_B_OK) {
69 		fprintf(stderr, "initializing driver settings failed: %s\n",
70 			fssh_strerror(error));
71 		return error;
72 	}
73 
74 	// register built-in modules, i.e. the rootfs and the client FS
75 	register_builtin_module(&gRootFileSystem.info);
76 	for (int i = 0; modules[i]; i++)
77 		register_builtin_module(modules[i]);
78 
79 	// init VFS
80 	error = vfs_init(NULL);
81 	if (error != FSSH_B_OK) {
82 		fprintf(stderr, "initializing VFS failed: %s\n", fssh_strerror(error));
83 		return error;
84 	}
85 
86 	// init kernel IO context
87 	gKernelIOContext = (io_context*)vfs_new_io_context(NULL);
88 	if (!gKernelIOContext) {
89 		fprintf(stderr, "creating IO context failed!\n");
90 		return FSSH_B_NO_MEMORY;
91 	}
92 
93 	// mount root FS
94 	fssh_dev_t rootDev = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
95 	if (rootDev < 0) {
96 		fprintf(stderr, "mounting rootfs failed: %s\n", fssh_strerror(rootDev));
97 		return rootDev;
98 	}
99 
100 	// set cwd to "/"
101 	error = _kern_setcwd(-1, "/");
102 	if (error != FSSH_B_OK) {
103 		fprintf(stderr, "setting cwd failed: %s\n", fssh_strerror(error));
104 		return error;
105 	}
106 
107 	// create mount point for the client FS
108 	error = _kern_create_dir(-1, kMountPoint, 0775);
109 	if (error != FSSH_B_OK) {
110 		fprintf(stderr, "creating mount point failed: %s\n",
111 			fssh_strerror(error));
112 		return error;
113 	}
114 
115 	return FSSH_B_OK;
116 }
117 
118 
119 // #pragma mark - Command
120 
121 Command::Command(const char* name, const char* description)
122 	: fName(name),
123 	  fDescription(description)
124 {
125 }
126 
127 
128 Command::Command(command_function* function, const char* name,
129 	const char* description)
130 	: fName(name),
131 	  fDescription(description),
132 	  fFunction(function)
133 {
134 }
135 
136 
137 Command::~Command()
138 {
139 }
140 
141 
142 const char*
143 Command::Name() const
144 {
145 	return fName.c_str();
146 }
147 
148 
149 const char*
150 Command::Description() const
151 {
152 	return fDescription.c_str();
153 }
154 
155 
156 fssh_status_t
157 Command::Do(int argc, const char* const* argv)
158 {
159 	if (!fFunction) {
160 		fprintf(stderr, "No function given for command \"%s\"\n", Name());
161 		return FSSH_B_BAD_VALUE;
162 	}
163 
164 	return (*fFunction)(argc, argv);
165 }
166 
167 
168 // #pragma mark - CommandManager
169 
170 CommandManager::CommandManager()
171 {
172 }
173 
174 
175 CommandManager*
176 CommandManager::Default()
177 {
178 	if (!sManager)
179 		sManager = new CommandManager;
180 	return sManager;
181 }
182 
183 
184 void
185 CommandManager::AddCommand(Command* command)
186 {
187 	// The command name may consist of several aliases. Split them and
188 	// register the command for each of them.
189 	char _names[1024];
190 	char* names = _names;
191 	strcpy(names, command->Name());
192 
193 	char* cookie;
194 	while (char* name = strtok_r(names, " /", &cookie)) {
195 		fCommands[name] = command;
196 		names = NULL;
197 	}
198 }
199 
200 
201 void
202 CommandManager::AddCommand(command_function* function, const char* name,
203 	const char* description)
204 {
205 	AddCommand(new Command(function, name, description));
206 }
207 
208 
209 void
210 CommandManager::AddCommands(command_function* function, const char* name,
211 	const char* description, ...)
212 {
213 	va_list args;
214 	va_start(args, description);
215 
216 	while (function) {
217 		AddCommand(function, name, description);
218 
219 		function = va_arg(args, command_function*);
220 		if (function) {
221 			name = va_arg(args, const char*);
222 			description = va_arg(args, const char*);
223 		}
224 	}
225 
226 	va_end(args);
227 }
228 
229 
230 Command*
231 CommandManager::FindCommand(const char* name) const
232 {
233 	CommandMap::const_iterator it = fCommands.find(name);
234 	if (it == fCommands.end())
235 		return NULL;
236 
237 	return it->second;
238 }
239 
240 
241 void
242 CommandManager::ListCommands() const
243 {
244 	for (CommandMap::const_iterator it = fCommands.begin();
245 			it != fCommands.end(); ++it) {
246 		const char* name = it->first.c_str();
247 		Command* command = it->second;
248 		printf("%-16s - %s\n", name, command->Description());
249 	}
250 }
251 
252 
253 CommandManager*	CommandManager::sManager = NULL;
254 
255 
256 // #pragma mark - Commands
257 
258 
259 static fssh_status_t
260 command_cd(int argc, const char* const* argv)
261 {
262 	if (argc != 2) {
263 		fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
264 		return FSSH_B_BAD_VALUE;
265 	}
266 	const char* directory = argv[1];
267 
268 	fssh_status_t error = FSSH_B_OK;
269 	if (directory[0] == ':') {
270 		if (chdir(directory + 1) < 0)
271 			error = fssh_get_errno();
272 	} else
273 		error = _kern_setcwd(-1, directory);
274 
275 	if (error != FSSH_B_OK) {
276 		fprintf(stderr, "Error: cd %s: %s\n", directory, fssh_strerror(error));
277 		return error;
278 	}
279 
280 	return FSSH_B_OK;
281 }
282 
283 
284 static bool
285 get_permissions(const char* modeString, fssh_mode_t& _permissions)
286 {
287 	// currently only octal mode is supported
288 	if (strlen(modeString) != 3)
289 		return false;
290 
291 	fssh_mode_t permissions = 0;
292 	for (int i = 0; i < 3; i++) {
293 		char c = modeString[i];
294 		if (c < '0' || c > '7')
295 			return false;
296 		permissions = (permissions << 3) | (c - '0');
297 	}
298 
299 	_permissions = permissions;
300 	return true;
301 }
302 
303 
304 static fssh_status_t
305 command_chmod(int argc, const char* const* argv)
306 {
307 	bool recursive = false;
308 
309 	// parse parameters
310 	int argi = 1;
311 	for (argi = 1; argi < argc; argi++) {
312 		const char *arg = argv[argi];
313 		if (arg[0] != '-')
314 			break;
315 
316 		if (arg[1] == '\0') {
317 			fprintf(stderr, "Error: Invalid option \"-\"\n");
318 			return FSSH_B_BAD_VALUE;
319 		}
320 
321 		for (int i = 1; arg[i]; i++) {
322 			switch (arg[i]) {
323 				case 'R':
324 					recursive = true;
325 					fprintf(stderr, "Sorry, recursive mode not supported "
326 						"yet.\n");
327 					return FSSH_B_BAD_VALUE;
328 				default:
329 					fprintf(stderr, "Error: Unknown option \"-%c\"\n", arg[i]);
330 					return FSSH_B_BAD_VALUE;
331 			}
332 		}
333 	}
334 
335 	// get mode
336 	fssh_mode_t permissions;
337 	if (argi + 1 >= argc || !get_permissions(argv[argi++], permissions)) {
338 		printf("Usage: %s [ -R ] <octal mode> <file>...\n", argv[0]);
339 		return FSSH_B_BAD_VALUE;
340 	}
341 
342 	fssh_struct_stat st;
343 	st.fssh_st_mode = permissions;
344 
345 	// chmod loop
346 	for (; argi < argc; argi++) {
347 		const char *file = argv[argi];
348 		if (strlen(file) == 0) {
349 			fprintf(stderr, "Error: An empty path is not a valid argument!\n");
350 			return FSSH_B_BAD_VALUE;
351 		}
352 
353 		fssh_status_t error = _kern_write_stat(-1, file, false, &st, sizeof(st),
354 			FSSH_B_STAT_MODE);
355 		if (error != FSSH_B_OK) {
356 			fprintf(stderr, "Error: Failed to change mode of \"%s\"!\n", file);
357 			return error;
358 		}
359 	}
360 
361 	return FSSH_B_OK;
362 }
363 
364 
365 static fssh_status_t
366 command_help(int argc, const char* const* argv)
367 {
368 	printf("supported commands:\n");
369 	CommandManager::Default()->ListCommands();
370 	return FSSH_B_OK;
371 }
372 
373 
374 static fssh_status_t
375 command_ln(int argc, const char* const* argv)
376 {
377 	bool force = false;
378 	bool symbolic = false;
379 	bool dereference = true;
380 
381 	// parse parameters
382 	int argi = 1;
383 	for (argi = 1; argi < argc; argi++) {
384 		const char *arg = argv[argi];
385 		if (arg[0] != '-')
386 			break;
387 
388 		if (arg[1] == '\0') {
389 			fprintf(stderr, "Error: Invalid option \"-\"\n");
390 			return FSSH_B_BAD_VALUE;
391 		}
392 
393 		for (int i = 1; arg[i]; i++) {
394 			switch (arg[i]) {
395 				case 'f':
396 					force = true;
397 					break;
398 				case 's':
399 					symbolic = true;
400 					break;
401 				case 'n':
402 					dereference = false;
403 					break;
404 				default:
405 					fprintf(stderr, "Error: Unknown option \"-%c\"\n", arg[i]);
406 					return FSSH_B_BAD_VALUE;
407 			}
408 		}
409 	}
410 
411 	if (argc - argi != 2) {
412 		fprintf(stderr, "Usage: %s [Options] <source> <target>\n", argv[0]);
413 		return FSSH_B_BAD_VALUE;
414 	}
415 
416 	const char *source = argv[argi];
417 	const char *target = argv[argi + 1];
418 
419 	// check, if the the target is an existing directory
420 	struct fssh_stat st;
421 	char targetBuffer[FSSH_B_PATH_NAME_LENGTH];
422 	fssh_status_t error = _kern_read_stat(-1, target, dereference, &st,
423 		sizeof(st));
424 	if (error == FSSH_B_OK) {
425 		if (FSSH_S_ISDIR(st.fssh_st_mode)) {
426 			// get source leaf
427 			char leaf[FSSH_B_FILE_NAME_LENGTH];
428 			error = get_last_path_component(source, leaf, sizeof(leaf));
429 			if (error != FSSH_B_OK) {
430 				fprintf(stderr, "Error: Failed to get leaf name of source "
431 					"path: %s\n", fssh_strerror(error));
432 				return error;
433 			}
434 
435 			// compose a new path
436 			int len = strlen(target) + 1 + strlen(leaf);
437 			if (len > (int)sizeof(targetBuffer)) {
438 				fprintf(stderr, "Error: Resulting target path is too long.\n");
439 				return FSSH_B_BAD_VALUE;
440 			}
441 
442 			strcpy(targetBuffer, target);
443 			strcat(targetBuffer, "/");
444 			strcat(targetBuffer, leaf);
445 			target = targetBuffer;
446 		}
447 	}
448 
449 	// check, if the target exists
450 	error = _kern_read_stat(-1, target, false, &st, sizeof(st));
451 	if (error == FSSH_B_OK) {
452 		if (!force) {
453 			fprintf(stderr, "Error: Can't create link. \"%s\" is in the way.\n",
454 				target);
455 			return FSSH_B_FILE_EXISTS;
456 		}
457 
458 		// unlink the entry
459 		error = _kern_unlink(-1, target);
460 		if (error != FSSH_B_OK) {
461 			fprintf(stderr, "Error: Failed to remove \"%s\" to make way for "
462 				"link: %s\n", target, fssh_strerror(error));
463 			return error;
464 		}
465 	}
466 
467 	// finally create the link
468 	if (symbolic) {
469 		error = _kern_create_symlink(-1, target, source,
470 			FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO);
471 	} else
472 		error = _kern_create_link(target, source);
473 
474 	if (error != FSSH_B_OK) {
475 		fprintf(stderr, "Error: Failed to create link: %s\n",
476 			fssh_strerror(error));
477 	}
478 
479 	return error;
480 }
481 
482 
483 static void
484 list_entry(const char* file, const char* name = NULL)
485 {
486 	// construct path, if a leaf name is given
487 	std::string path;
488 	if (name) {
489 		path = file;
490 		path += '/';
491 		path += name;
492 		file = path.c_str();
493 	} else
494 		name = file;
495 
496 	// stat the file
497 	struct fssh_stat st;
498 	fssh_status_t error = _kern_read_stat(-1, file, false, &st, sizeof(st));
499 	if (error != FSSH_B_OK) {
500 		fprintf(stderr, "Error: Failed to stat() \"%s\": %s\n", file,
501 			fssh_strerror(error));
502 		return;
503 	}
504 
505 	// get time
506 	struct tm time;
507 	time_t fileTime = st.fssh_st_mtime;
508 	localtime_r(&fileTime, &time);
509 
510 	// get permissions
511 	std::string permissions;
512 	fssh_mode_t mode = st.fssh_st_mode;
513 	// user
514 	permissions += ((mode & FSSH_S_IRUSR) ? 'r' : '-');
515 	permissions += ((mode & FSSH_S_IWUSR) ? 'w' : '-');
516 	if (mode & FSSH_S_ISUID)
517 		permissions += 's';
518 	else
519 		permissions += ((mode & FSSH_S_IXUSR) ? 'x' : '-');
520 	// group
521 	permissions += ((mode & FSSH_S_IRGRP) ? 'r' : '-');
522 	permissions += ((mode & FSSH_S_IWGRP) ? 'w' : '-');
523 	if (mode & FSSH_S_ISGID)
524 		permissions += 's';
525 	else
526 		permissions += ((mode & FSSH_S_IXGRP) ? 'x' : '-');
527 	// others
528 	permissions += ((mode & FSSH_S_IROTH) ? 'r' : '-');
529 	permissions += ((mode & FSSH_S_IWOTH) ? 'w' : '-');
530 	permissions += ((mode & FSSH_S_IXOTH) ? 'x' : '-');
531 
532 	// get file type
533 	char fileType = '?';
534 	if (FSSH_S_ISREG(mode)) {
535 		fileType = '-';
536 	} else if (FSSH_S_ISLNK(mode)) {
537 		fileType = 'l';
538 	} else if (FSSH_S_ISBLK(mode)) {
539 		fileType = 'b';
540 	} else if (FSSH_S_ISDIR(mode)) {
541 		fileType = 'd';
542 	} else if (FSSH_S_ISCHR(mode)) {
543 		fileType = 'c';
544 	} else if (FSSH_S_ISFIFO(mode)) {
545 		fileType = 'f';
546 	} else if (FSSH_S_ISINDEX(mode)) {
547 		fileType = 'i';
548 	}
549 
550 	// get link target
551 	std::string nameSuffix;
552 	if (FSSH_S_ISLNK(mode)) {
553 		char buffer[FSSH_B_PATH_NAME_LENGTH];
554 		fssh_size_t size = sizeof(buffer) - 1;
555 		error = _kern_read_link(-1, file, buffer, &size);
556 		if (error != FSSH_B_OK)
557 			snprintf(buffer, sizeof(buffer), "(%s)", fssh_strerror(error));
558 
559 		buffer[size] = '\0';
560 		nameSuffix += " -> ";
561 		nameSuffix += buffer;
562 	}
563 
564 	printf("%c%s %2d %2d %10lld %d-%02d-%02d %02d:%02d:%02d %s%s\n",
565 		fileType, permissions.c_str(), (int)st.fssh_st_uid, (int)st.fssh_st_gid,
566 		st.fssh_st_size,
567 		1900 + time.tm_year, 1 + time.tm_mon, time.tm_mday,
568 		time.tm_hour, time.tm_min, time.tm_sec,
569 		name, nameSuffix.c_str());
570 }
571 
572 
573 static fssh_status_t
574 command_ls(int argc, const char* const* argv)
575 {
576 	const char* const currentDirFiles[] = { ".", NULL };
577 	const char* const* files;
578 	if (argc >= 2)
579 		files = argv + 1;
580 	else
581 		files = currentDirFiles;
582 
583 	for (; *files; files++) {
584 		const char* file = *files;
585 		// stat file
586 		struct fssh_stat st;
587 		fssh_status_t error = _kern_read_stat(-1, file, false, &st, sizeof(st));
588 		if (error != FSSH_B_OK) {
589 			fprintf(stderr, "Error: Failed to stat() \"%s\": %s\n", file,
590 				fssh_strerror(error));
591 			continue;
592 		}
593 
594 		// if it is a directory, print its entries
595 		if (FSSH_S_ISDIR(st.fssh_st_mode)) {
596 			printf("%s:\n", file);
597 
598 			// open dir
599 			int fd = _kern_open_dir(-1, file);
600 			if (fd < 0) {
601 				fprintf(stderr, "Error: Failed to open dir \"%s\": %s\n",
602 					file, fssh_strerror(fd));
603 				continue;
604 			}
605 
606 			// iterate through the entries
607 			char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
608 			fssh_dirent* entry = (fssh_dirent*)buffer;
609 			fssh_ssize_t entriesRead = 0;
610 			while ((entriesRead = _kern_read_dir(fd, entry, sizeof(buffer), 1))
611 					== 1) {
612 				list_entry(file, entry->d_name);
613 			}
614 
615 			if (entriesRead < 0) {
616 				fprintf(stderr, "Error: reading dir \"%s\" failed: %s\n",
617 					file, fssh_strerror(entriesRead));
618 			}
619 
620 			// close dir
621 			error = _kern_close(fd);
622 			if (error != FSSH_B_OK) {
623 				fprintf(stderr, "Error: Closing dir \"%s\" (fd: %d) failed: "
624 					"%s\n", file, fd, fssh_strerror(error));
625 				continue;
626 			}
627 		} else
628 			list_entry(file);
629 	}
630 
631 	return FSSH_B_OK;
632 }
633 
634 
635 static fssh_status_t
636 create_dir(const char *path, bool createParents)
637 {
638 	// stat the entry
639 	struct fssh_stat st;
640 	fssh_status_t error = _kern_read_stat(-1, path, false, &st, sizeof(st));
641 	if (error == FSSH_B_OK) {
642 		if (createParents && FSSH_S_ISDIR(st.fssh_st_mode))
643 			return FSSH_B_OK;
644 
645 		fprintf(stderr, "Error: Cannot make dir, entry \"%s\" is in the way.\n",
646 			path);
647 		return FSSH_B_FILE_EXISTS;
648 	}
649 
650 	// the dir doesn't exist yet
651 	// if we shall create all parents, do that first
652 	if (createParents) {
653 		// create the parent dir path
654 		// eat the trailing '/'s
655 		int len = strlen(path);
656 		while (len > 0 && path[len - 1] == '/')
657 			len--;
658 
659 		// eat the last path component
660 		while (len > 0 && path[len - 1] != '/')
661 			len--;
662 
663 		// eat the trailing '/'s
664 		while (len > 0 && path[len - 1] == '/')
665 			len--;
666 
667 		// Now either nothing remains, which means we had a single component,
668 		// a root subdir -- in those cases we can just fall through (we should
669 		// actually never be here in case of the root dir, but anyway) -- or
670 		// there is something left, which we can call a parent directory and
671 		// try to create it.
672 		if (len > 0) {
673 			char *parentPath = (char*)malloc(len + 1);
674 			if (!parentPath) {
675 				fprintf(stderr, "Error: Failed to allocate memory for parent "
676 					"path.\n");
677 				return FSSH_B_NO_MEMORY;
678 			}
679 			memcpy(parentPath, path, len);
680 			parentPath[len] = '\0';
681 
682 			error = create_dir(parentPath, createParents);
683 
684 			free(parentPath);
685 
686 			if (error != FSSH_B_OK)
687 				return error;
688 		}
689 	}
690 
691 	// make the directory
692 	error = _kern_create_dir(-1,
693 		path, (FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO) & ~sUmask);
694 	if (error != FSSH_B_OK) {
695 		fprintf(stderr, "Error: Failed to make directory \"%s\": %s\n", path,
696 			fssh_strerror(error));
697 		return error;
698 	}
699 
700 	return FSSH_B_OK;
701 }
702 
703 
704 static fssh_status_t
705 command_mkdir(int argc, const char* const* argv)
706 {
707 	bool createParents = false;
708 
709 	// parse parameters
710 	int argi = 1;
711 	for (argi = 1; argi < argc; argi++) {
712 		const char *arg = argv[argi];
713 		if (arg[0] != '-')
714 			break;
715 
716 		if (arg[1] == '\0') {
717 			fprintf(stderr, "Error: Invalid option \"-\"\n");
718 			return FSSH_B_BAD_VALUE;
719 		}
720 
721 		for (int i = 1; arg[i]; i++) {
722 			switch (arg[i]) {
723 				case 'p':
724 					createParents = true;
725 					break;
726 				default:
727 					fprintf(stderr, "Error: Unknown option \"-%c\"\n", arg[i]);
728 					return FSSH_B_BAD_VALUE;
729 			}
730 		}
731 	}
732 
733 	if (argi >= argc) {
734 		printf("Usage: %s [ -p ] <dir>...\n", argv[0]);
735 		return FSSH_B_BAD_VALUE;
736 	}
737 
738 	// create loop
739 	for (; argi < argc; argi++) {
740 		const char *dir = argv[argi];
741 		if (strlen(dir) == 0) {
742 			fprintf(stderr, "Error: An empty path is not a valid argument!\n");
743 			return FSSH_B_BAD_VALUE;
744 		}
745 
746 		fssh_status_t error = create_dir(dir, createParents);
747 		if (error != FSSH_B_OK)
748 			return error;
749 	}
750 
751 	return FSSH_B_OK;
752 }
753 
754 
755 static fssh_dev_t
756 get_volume_id()
757 {
758 	struct fssh_stat st;
759 	fssh_status_t error = _kern_read_stat(-1, kMountPoint, false, &st,
760 		sizeof(st));
761 	if (error != FSSH_B_OK) {
762 		fprintf(stderr, "Error: Failed to stat() mount point: %s\n",
763 			fssh_strerror(error));
764 		return error;
765 	}
766 
767 	return st.fssh_st_dev;
768 }
769 
770 
771 static fssh_status_t
772 command_mkindex(int argc, const char* const* argv)
773 {
774 	if (argc != 2) {
775 		fprintf(stderr, "Usage: %s <index name>\n", argv[0]);
776 		return FSSH_B_BAD_VALUE;
777 	}
778 
779 	const char* indexName = argv[1];
780 
781 	// get the volume ID
782 	fssh_dev_t volumeID = get_volume_id();
783 	if (volumeID < 0)
784 		return volumeID;
785 
786 	// create the index
787 	fssh_status_t error =_kern_create_index(volumeID, indexName,
788 		FSSH_B_STRING_TYPE, 0);
789 	if (error != FSSH_B_OK) {
790 		fprintf(stderr, "Error: Failed to create index \"%s\": %s\n",
791 			indexName, fssh_strerror(error));
792 		return error;
793 	}
794 
795 	return FSSH_B_OK;
796 }
797 
798 
799 static fssh_status_t
800 command_query(int argc, const char* const* argv)
801 {
802 	if (argc != 2) {
803 		fprintf(stderr, "Usage: %s <query string>\n", argv[0]);
804 		return FSSH_B_BAD_VALUE;
805 	}
806 
807 	const char* query = argv[1];
808 
809 	// get the volume ID
810 	fssh_dev_t volumeID = get_volume_id();
811 	if (volumeID < 0)
812 		return volumeID;
813 
814 	// open query
815 	int fd = _kern_open_query(volumeID, query, strlen(query), 0, -1, -1);
816 	if (fd < 0) {
817 		fprintf(stderr, "Error: Failed to open query: %s\n", fssh_strerror(fd));
818 		return fd;
819 	}
820 
821 	// iterate through the entries
822 	fssh_status_t error = FSSH_B_OK;
823 	char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
824 	fssh_dirent* entry = (fssh_dirent*)buffer;
825 	fssh_ssize_t entriesRead = 0;
826 	while ((entriesRead = _kern_read_dir(fd, entry, sizeof(buffer), 1)) == 1) {
827 		char path[FSSH_B_PATH_NAME_LENGTH];
828 		error = _kern_entry_ref_to_path(volumeID, entry->d_pino, entry->d_name,
829 			path, sizeof(path));
830 		if (error == FSSH_B_OK) {
831 			printf("  %s\n", path);
832 		} else {
833 			fprintf(stderr, "  failed to resolve entry (%8lld, \"%s\")\n",
834 				entry->d_pino, entry->d_name);
835 		}
836 	}
837 
838 	if (entriesRead < 0) {
839 		fprintf(stderr, "Error: reading query failed: %s\n",
840 			fssh_strerror(entriesRead));
841 	}
842 
843 	// close query
844 	error = _kern_close(fd);
845 	if (error != FSSH_B_OK) {
846 		fprintf(stderr, "Error: Closing query (fd: %d) failed: %s\n",
847 			fd, fssh_strerror(error));
848 	}
849 
850 	return error;
851 }
852 
853 
854 static fssh_status_t
855 command_quit(int argc, const char* const* argv)
856 {
857 	return COMMAND_RESULT_EXIT;
858 }
859 
860 
861 static fssh_status_t remove_entry(int dir, const char *entry, bool recursive,
862 	bool force);
863 
864 
865 static fssh_status_t
866 remove_dir_contents(int parentDir, const char *name, bool force)
867 {
868 	// open the dir
869 	int dir = _kern_open_dir(parentDir, name);
870 	if (dir < 0) {
871 		fprintf(stderr, "Error: Failed to open dir \"%s\": %s\n", name,
872 			fssh_strerror(dir));
873 		return dir;
874 	}
875 
876 	fssh_status_t error = FSSH_B_OK;
877 
878 	// iterate through the entries
879 	fssh_ssize_t numRead;
880 	char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
881 	fssh_dirent *entry = (fssh_dirent*)buffer;
882 	while ((numRead = _kern_read_dir(dir, entry, sizeof(buffer), 1)) > 0) {
883 		// skip "." and ".."
884 		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
885 			continue;
886 
887 		error = remove_entry(dir, entry->d_name, true, force);
888 		if (error != FSSH_B_OK)
889 			break;
890 	}
891 
892 	if (numRead < 0) {
893 		fprintf(stderr, "Error: Failed to read directory \"%s\": %s\n", name,
894 			fssh_strerror(numRead));
895 		error = numRead;
896 	}
897 
898 	// close
899 	_kern_close(dir);
900 
901 	return error;
902 }
903 
904 
905 static fssh_status_t
906 remove_entry(int dir, const char *entry, bool recursive, bool force)
907 {
908 	// stat the file
909 	struct fssh_stat st;
910 	fssh_status_t error = _kern_read_stat(dir, entry, false, &st, sizeof(st));
911 	if (error != FSSH_B_OK) {
912 		if (force && error == FSSH_B_ENTRY_NOT_FOUND)
913 			return FSSH_B_OK;
914 
915 		fprintf(stderr, "Error: Failed to remove \"%s\": %s\n", entry,
916 			fssh_strerror(error));
917 		return error;
918 	}
919 
920 	if (FSSH_S_ISDIR(st.fssh_st_mode)) {
921 		if (!recursive) {
922 			fprintf(stderr, "Error: \"%s\" is a directory.\n", entry);
923 				// TODO: get the full path
924 			return FSSH_EISDIR;
925 		}
926 
927 		// remove the contents
928 		error = remove_dir_contents(dir, entry, force);
929 		if (error != FSSH_B_OK)
930 			return error;
931 
932 		// remove the directory
933 		error = _kern_remove_dir(dir, entry);
934 		if (error != FSSH_B_OK) {
935 			fprintf(stderr, "Error: Failed to remove directory \"%s\": %s\n",
936 				entry, fssh_strerror(error));
937 			return error;
938 		}
939 	} else {
940 		// remove the entry
941 		error = _kern_unlink(dir, entry);
942 		if (error != FSSH_B_OK) {
943 			fprintf(stderr, "Error: Failed to remove entry \"%s\": %s\n", entry,
944 				fssh_strerror(error));
945 			return error;
946 		}
947 	}
948 
949 	return FSSH_B_OK;
950 }
951 
952 
953 static fssh_status_t
954 command_rm(int argc, char **argv)
955 {
956 	bool recursive = false;
957 	bool force = false;
958 
959 	// parse parameters
960 	int argi = 1;
961 	for (argi = 1; argi < argc; argi++) {
962 		const char *arg = argv[argi];
963 		if (arg[0] != '-')
964 			break;
965 
966 		if (arg[1] == '\0') {
967 			fprintf(stderr, "Error: Invalid option \"-\"\n");
968 			return FSSH_B_BAD_VALUE;
969 		}
970 
971 		for (int i = 1; arg[i]; i++) {
972 			switch (arg[i]) {
973 				case 'f':
974 					force = true;
975 					break;
976 				case 'r':
977 					recursive = true;
978 					break;
979 				default:
980 					fprintf(stderr, "Error: Unknown option \"-%c\"\n", arg[i]);
981 					return FSSH_B_BAD_VALUE;
982 			}
983 		}
984 	}
985 
986 	// check params
987 	if (argi >= argc) {
988 		fprintf(stderr, "Usage: %s [ -r ] <file>...\n", argv[0]);
989 		return FSSH_B_BAD_VALUE;
990 	}
991 
992 	// remove loop
993 	for (; argi < argc; argi++) {
994 		fssh_status_t error = remove_entry(-1, argv[argi], recursive, force);
995 		if (error != FSSH_B_OK)
996 			return error;
997 	}
998 
999 	return FSSH_B_OK;
1000 }
1001 
1002 
1003 static fssh_status_t
1004 command_sync(int argc, const char* const* argv)
1005 {
1006 	fssh_status_t error = _kern_sync();
1007 	if (error != FSSH_B_OK) {
1008 		fprintf(stderr, "Error: syncing: %s\n", fssh_strerror(error));
1009 		return error;
1010 	}
1011 
1012 	return FSSH_B_OK;
1013 }
1014 
1015 
1016 static void
1017 register_commands()
1018 {
1019 	CommandManager::Default()->AddCommands(
1020 		command_cd,			"cd",			"change current directory",
1021 		command_chmod,		"chmod",		"change file permissions",
1022 		command_cp,			"cp",			"copy files and directories",
1023 		command_help,		"help",			"list supported commands",
1024 		command_ln,			"ln",			"create a hard or symbolic link",
1025 		command_ls,			"ls",			"list files or directories",
1026 		command_mkdir,		"mkdir",		"create directories",
1027 		command_mkindex,	"mkindex",		"create an index",
1028 		command_query,		"query",		"query for files",
1029 		command_quit,		"quit/exit",	"quit the shell",
1030 		command_rm,			"rm",			"remove files and directories",
1031 		command_sync,		"sync",			"syncs the file system",
1032 		NULL
1033 	);
1034 }
1035 
1036 
1037 // #pragma mark - ArgVector
1038 
1039 
1040 class ArgVector {
1041 public:
1042 	ArgVector()
1043 		: fArgc(0),
1044 		  fArgv(NULL)
1045 	{
1046 	}
1047 
1048 	~ArgVector()
1049 	{
1050 		_Cleanup();
1051 	}
1052 
1053 	int Argc() const
1054 	{
1055 		return fArgc;
1056 	}
1057 
1058 	const char* const* Argv() const
1059 	{
1060 		return fArgv;
1061 	}
1062 
1063 	bool Parse(const char* commandLine)
1064 	{
1065 		_Cleanup();
1066 
1067 		// init temporary arg/argv storage
1068 		std::string currentArg;
1069 		std::vector<std::string> argVector;
1070 
1071 		fCurrentArg = &currentArg;
1072 		fCurrentArgStarted = false;
1073 		fArgVector = &argVector;
1074 
1075 		for (; *commandLine; commandLine++) {
1076 			char c = *commandLine;
1077 
1078 			// whitespace delimits args and is otherwise ignored
1079 			if (isspace(c)) {
1080 				_PushCurrentArg();
1081 				continue;
1082 			}
1083 
1084 			switch (c) {
1085 				case '\'':
1086 					// quoted string -- no quoting
1087 					while (*++commandLine != '\'') {
1088 						c = *commandLine;
1089 						if (c == '\0') {
1090 							fprintf(stderr, "Error: Unterminated quoted "
1091 								"string.\n");
1092 							return false;
1093 						}
1094 						_PushCharacter(c);
1095 					}
1096 					break;
1097 
1098 				case '"':
1099 					// quoted string -- some quoting
1100 					while (*++commandLine != '"') {
1101 						c = *commandLine;
1102 						if (c == '\0') {
1103 							fprintf(stderr, "Error: Unterminated quoted "
1104 								"string.\n");
1105 							return false;
1106 						}
1107 
1108 						if (c == '\\') {
1109 							c = *++commandLine;
1110 							if (c == '\0') {
1111 								fprintf(stderr, "Error: Unterminated quoted "
1112 									"string.\n");
1113 								return false;
1114 							}
1115 
1116 							// only '\' and '"' can be quoted, otherwise the
1117 							// the '\' is treated as a normal char
1118 							if (c != '\\' && c != '"')
1119 								_PushCharacter('\\');
1120 						}
1121 
1122 						_PushCharacter(c);
1123 					}
1124 					break;
1125 
1126 				case '\\':
1127 					// quoted char
1128 					c = *++commandLine;
1129 					if (c == '\0') {
1130 						fprintf(stderr, "Error: Command line ends with "
1131 							"'\\'.\n");
1132 						return false;
1133 					}
1134 					_PushCharacter(c);
1135 					break;
1136 
1137 				default:
1138 					// normal char
1139 					_PushCharacter(c);
1140 					break;
1141 			}
1142 		}
1143 
1144 		// commit last arg
1145 		_PushCurrentArg();
1146 
1147 		// build arg vector
1148 		fArgc = argVector.size();
1149 		fArgv = new char*[fArgc + 1];
1150 		for (int i = 0; i < fArgc; i++) {
1151 			int len = argVector[i].length();
1152 			fArgv[i] = new char[len + 1];
1153 			memcpy(fArgv[i], argVector[i].c_str(), len + 1);
1154 		}
1155 		fArgv[fArgc] = NULL;
1156 
1157 		return true;
1158 	}
1159 
1160 private:
1161 	void _Cleanup()
1162 	{
1163 		if (fArgv) {
1164 			for (int i = 0; i < fArgc; i++)
1165 				delete[] fArgv[i];
1166 			delete[] fArgv;
1167 		}
1168 	}
1169 
1170 	void _PushCurrentArg()
1171 	{
1172 		if (fCurrentArgStarted) {
1173 			fArgVector->push_back(*fCurrentArg);
1174 			fCurrentArgStarted = false;
1175 		}
1176 	}
1177 
1178 	void _PushCharacter(char c)
1179 	{
1180 		if (!fCurrentArgStarted) {
1181 			*fCurrentArg = "";
1182 			fCurrentArgStarted = true;
1183 		}
1184 
1185 		*fCurrentArg += c;
1186 	}
1187 
1188 private:
1189 	// temporaries
1190 	std::string*				fCurrentArg;
1191 	bool						fCurrentArgStarted;
1192 	std::vector<std::string>*	fArgVector;
1193 
1194 	int							fArgc;
1195 	char**						fArgv;
1196 };
1197 
1198 
1199 // #pragma mark - input loop
1200 
1201 
1202 static char*
1203 read_command_line(char* buffer, int bufferSize)
1204 {
1205 	// print prompt (including cwd, if available)
1206 	char directory[FSSH_B_PATH_NAME_LENGTH];
1207 	if (_kern_getcwd(directory, sizeof(directory)) == FSSH_B_OK)
1208 		printf("fssh:%s> ", directory);
1209 	else
1210 		printf("fssh> ");
1211 	fflush(stdout);
1212 
1213 	// read input line
1214 	return fgets(buffer, bufferSize, stdin);
1215 }
1216 
1217 
1218 static void
1219 input_loop(bool interactive)
1220 {
1221 	static const int kInputBufferSize = 100 * 1024;
1222 	char* inputBuffer = new char[kInputBufferSize];
1223 
1224 	for (;;) {
1225 		// read command line
1226 		if (interactive) {
1227 			if (!read_command_line(inputBuffer, kInputBufferSize))
1228 				break;
1229 		} else {
1230 			if (!get_external_command(inputBuffer, kInputBufferSize))
1231 				break;
1232 		}
1233 
1234 		// construct argv vector
1235 		int result = FSSH_B_BAD_VALUE;
1236 		ArgVector argVector;
1237 		if (argVector.Parse(inputBuffer) && argVector.Argc() > 0) {
1238 			int argc = argVector.Argc();
1239 			const char* const* argv = argVector.Argv();
1240 
1241 			// find command
1242 			Command* command = CommandManager::Default()->FindCommand(argv[0]);
1243 			if (command) {
1244 				// execute it
1245 				result = command->Do(argc, argv);
1246 				if (result == COMMAND_RESULT_EXIT) {
1247 					if (!interactive)
1248 						reply_to_external_command(0);
1249 					break;
1250 				}
1251 			} else {
1252 				fprintf(stderr, "Error: Invalid command \"%s\". Type \"help\" "
1253 					"for a list of supported commands\n", argv[0]);
1254 			}
1255 		}
1256 
1257 		if (!interactive)
1258 			reply_to_external_command(fssh_to_host_error(result));
1259 	}
1260 
1261 	if (!interactive)
1262 		external_command_cleanup();
1263 
1264 	delete[] inputBuffer;
1265 }
1266 
1267 
1268 static int
1269 standard_session(const char* device, const char* fsName, bool interactive)
1270 {
1271 	// mount FS
1272 	fssh_dev_t fsDev = _kern_mount(kMountPoint, device, fsName, 0, NULL, 0);
1273 	if (fsDev < 0) {
1274 		fprintf(stderr, "Error: Mounting FS failed: %s\n",
1275 			fssh_strerror(fsDev));
1276 		return 1;
1277 	}
1278 
1279 	// register commands
1280 	register_commands();
1281 
1282 	// process commands
1283 	input_loop(interactive);
1284 
1285 	// unmount FS
1286 	_kern_setcwd(-1, "/");	// avoid a "busy" vnode
1287 	fssh_status_t error = _kern_unmount(kMountPoint, 0);
1288 	if (error != FSSH_B_OK) {
1289 		fprintf(stderr, "Error: Unmounting FS failed: %s\n",
1290 			fssh_strerror(error));
1291 		return 1;
1292 	}
1293 
1294 	return 0;
1295 }
1296 
1297 
1298 static int
1299 initialization_session(const char* device, const char* fsName,
1300 	const char* volumeName, const char* initParameters)
1301 {
1302 	fssh_status_t error = _kern_initialize_volume(fsName, device,
1303 		volumeName, initParameters);
1304 	if (error != FSSH_B_OK) {
1305 		fprintf(stderr, "Error: Initializing volume failed: %s\n",
1306 			fssh_strerror(error));
1307 		return 1;
1308 	}
1309 
1310 	return 0;
1311 }
1312 
1313 
1314 static void
1315 print_usage(bool error)
1316 {
1317 	fprintf((error ? stderr : stdout),
1318 		"Usage: %s [ --start-offset <startOffset>]\n"
1319 		"          [ --end-offset <endOffset>] [-n] <device>\n"
1320 		"       %s [ --start-offset <startOffset>]\n"
1321 		"          [ --end-offset <endOffset>]\n"
1322 		"          --initialize [-n] <device> <volume name> "
1323 			"[ <init parameters> ]\n",
1324 		sArgv[0], sArgv[0]
1325 	);
1326 }
1327 
1328 
1329 static void
1330 print_usage_and_exit(bool error)
1331 {
1332 	print_usage(error);
1333 	exit(error ? 1 : 0);
1334 }
1335 
1336 
1337 }	// namespace FSShell
1338 
1339 
1340 using namespace FSShell;
1341 
1342 
1343 int
1344 main(int argc, const char* const* argv)
1345 {
1346 	sArgc = argc;
1347 	sArgv = argv;
1348 
1349 	// process arguments
1350 	bool interactive = true;
1351 	bool initialize = false;
1352 	const char* device = NULL;
1353 	const char* volumeName = NULL;
1354 	const char* initParameters = NULL;
1355 	fssh_off_t startOffset = 0;
1356 	fssh_off_t endOffset = -1;
1357 
1358 	// eat options
1359 	int argi = 1;
1360 	while (argi < argc && argv[argi][0] == '-') {
1361 		const char* arg = argv[argi++];
1362 		if (strcmp(arg, "--help") == 0) {
1363 			print_usage_and_exit(false);
1364 		} else if (strcmp(arg, "--initialize") == 0) {
1365 			initialize = true;
1366 		} else if (strcmp(arg, "-n") == 0) {
1367 			interactive = false;
1368 		} else if (strcmp(arg, "--start-offset") == 0) {
1369 			if (argi >= argc)
1370 				print_usage_and_exit(true);
1371 			startOffset = atoll(argv[argi++]);
1372 		} else if (strcmp(arg, "--end-offset") == 0) {
1373 			if (argi >= argc)
1374 				print_usage_and_exit(true);
1375 			endOffset = atoll(argv[argi++]);
1376 		} else {
1377 			print_usage_and_exit(true);
1378 		}
1379 	}
1380 
1381 	// get device
1382 	if (argi >= argc)
1383 		print_usage_and_exit(true);
1384 	device = argv[argi++];
1385 
1386 	// get volume name and init parameters
1387 	if (initialize) {
1388 		// volume name
1389 		if (argi >= argc)
1390 			print_usage_and_exit(true);
1391 		volumeName = argv[argi++];
1392 
1393 		// (optional) init paramaters
1394 		if (argi < argc)
1395 			initParameters = argv[argi++];
1396 	}
1397 
1398 	// more parameters are excess
1399 	if (argi < argc)
1400 		print_usage_and_exit(true);
1401 
1402 	// get FS module
1403 	if (!modules[0]) {
1404 		fprintf(stderr, "Error: Couldn't find FS module!\n");
1405 		return 1;
1406 	}
1407 	const char* fsName = modules[0]->name;
1408 
1409 	fssh_status_t error;
1410 
1411 	// init kernel
1412 	error = init_kernel();
1413 	if (error != FSSH_B_OK) {
1414 		fprintf(stderr, "Error: Initializing kernel failed: %s\n",
1415 			fssh_strerror(error));
1416 		return error;
1417 	}
1418 
1419 	// restrict access if requested
1420 	if (startOffset != 0 || endOffset != -1)
1421 		add_file_restriction(device, startOffset, endOffset);
1422 
1423 
1424 	// start the action
1425 	int result;
1426 	if (initialize) {
1427 		result = initialization_session(device, fsName, volumeName,
1428 			initParameters);
1429 	} else
1430 		result = standard_session(device, fsName, interactive);
1431 
1432 	return result;
1433 }
1434