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