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