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