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