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