/*
* Copyright (c) 2011 Jiri Svoboda
* Copyright (c) 2011 Martin Sucha
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdbool.h>
#include <dirent.h>
#include <errno.h>
#include <macros.h>
#include <stdlib.h>
#include <vfs/vfs.h>
#include <str.h>
#include <adt/odict.h>
#include "scli.h"
#include "cmds/cmds.h"
#include "compl.h"
#include "exec.h"
#include "tok.h"
#include "util.h"
static errno_t compl_init(char32_t *text, size_t pos, size_t *cstart, void **state);
static errno_t compl_get_next(void *state, char **compl);
static void compl_fini(void *state);
/** Bdsh implementation of completion ops. */
tinput_compl_ops_t compl_ops = {
.init = compl_init,
.get_next = compl_get_next,
.fini = compl_fini
};
/** Completion state object.
*
* The state object contains 'iterators' for modules, builtins and
* executables in directories.
*/
typedef struct {
/** String prefix which we are trying to complete. */
char *prefix;
/** Length of string prefix (number of characters) */
size_t prefix_len;
/* Pointer to the current alias */
odlink_t *alias_link;
/** Pointer inside list of modules */
module_t *module;
/** Pointer inside list of builtins */
builtin_t *builtin;
/** Pointer inside list of directories */
const char *const *path;
/** If not @c NULL, should be freed in the end. */
char **path_list;
/** Current open directory */
DIR *dir;
char *last_compl;
/**
* @c true if we are completing a command, @c false if we are
* completing an argument
*/
bool is_command;
} compl_t;
/** Init completion.
*
* Set up iterators in completion object, based on current token.
*/
static errno_t compl_init(char32_t *text, size_t pos, size_t *cstart, void **state)
{
compl_t *cs = NULL;
char *stext = NULL;
char *prefix = NULL;
char *dirname = NULL;
errno_t retval;
token_t *tokens = calloc(WORD_MAX, sizeof(token_t));
if (tokens == NULL) {
retval = ENOMEM;
goto error;
}
size_t pref_size;
char *rpath_sep;
static const char *dirlist_arg[] = { ".", NULL };
tokenizer_t tok;
ssize_t current_token;
size_t tokens_length;
cs = calloc(1, sizeof(compl_t));
if (!cs) {
retval = ENOMEM;
goto error;
}
/* Convert text buffer to string */
stext = wstr_to_astr(text);
if (stext == NULL) {
retval = ENOMEM;
goto error;
}
/* Tokenize the input string */
retval = tok_init(&tok, stext, tokens, WORD_MAX);
if (retval != EOK) {
goto error;
}
retval = tok_tokenize(&tok, &tokens_length);
if (retval != EOK) {
goto error;
}
/* Find the current token */
for (current_token = 0; current_token < (ssize_t) tokens_length;
current_token++) {
token_t *t = &tokens[current_token];
size_t end = t->char_start + t->char_length;
/*
* Check if the caret lies inside the token or immediately
* after it
*/
if (t->char_start <= pos && pos <= end) {
break;
}
}
if (tokens_length == 0)
current_token = -1;
if ((current_token >= 0) && (tokens[current_token].type != TOKTYPE_SPACE))
*cstart = tokens[current_token].char_start;
else
*cstart = pos;
/*
* Extract the prefix being completed
* XXX: handle strings, etc.
*/
pref_size = str_lsize(stext, pos - *cstart);
prefix = malloc(pref_size + 1);
if (prefix == NULL) {
retval = ENOMEM;
goto error;
}
prefix[pref_size] = 0;
if (current_token >= 0) {
str_ncpy(prefix, pref_size + 1, stext +
tokens[current_token].byte_start, pref_size);
}
/*
* Determine if the token being completed is a command or argument.
* We look at the previous token. If there is none or it is a pipe
* ('|'), it is a command, otherwise it is an argument.
*/
/* Skip any whitespace before current token */
ssize_t prev_token = current_token - 1;
if ((prev_token >= 0) && (tokens[prev_token].type == TOKTYPE_SPACE))
prev_token--;
/*
* It is a command if it is the first token or if it immediately
* follows a pipe token.
*/
if ((prev_token < 0) || (tokens[prev_token].type == TOKTYPE_SPACE))
cs->is_command = true;
else
cs->is_command = false;
rpath_sep = str_rchr(prefix, '/');
if (rpath_sep != NULL) {
/* Extract path. For path beginning with '/' keep the '/'. */
dirname = str_ndup(prefix, max(1, rpath_sep - prefix));
if (dirname == NULL) {
retval = ENOMEM;
goto error;
}
/* Extract name prefix */
cs->prefix = str_dup(rpath_sep + 1);
if (cs->prefix == NULL) {
retval = ENOMEM;
goto error;
}
*cstart += rpath_sep + 1 - prefix;
cs->path_list = malloc(sizeof(char *) * 2);
if (cs->path_list == NULL) {
retval = ENOMEM;
goto error;
}
if (!is_path(prefix) && cs->is_command) {
cs->path_list[0] = malloc(sizeof(char) * PATH_MAX);
if (cs->path_list[0] == NULL) {
retval = ENOMEM;
goto error;
}
int ret = snprintf(cs->path_list[0], PATH_MAX, "%s/%s", search_dir[0], dirname);
if (ret < 0 || ret >= PATH_MAX) {
retval = ENOMEM;
goto error;
}
} else {
cs->path_list[0] = dirname;
}
free(prefix);
cs->path_list[1] = NULL;
/*
* The second const ensures that we can't assign a const
* string to the non-const array.
*/
cs->path = (const char *const *) cs->path_list;
} else if (cs->is_command) {
/* Command without path */
cs->alias_link = odict_first(&alias_dict);
cs->module = modules;
cs->builtin = builtins;
cs->prefix = prefix;
cs->path = &search_dir[0];
} else {
/* Argument without path */
cs->prefix = prefix;
cs->path = &dirlist_arg[0];
}
cs->prefix_len = str_length(cs->prefix);
tok_fini(&tok);
*state = cs;
return EOK;
error:
/* Error cleanup */
tok_fini(&tok);
if (cs != NULL && cs->path_list != NULL) {
size_t i = 0;
while (cs->path_list[i] != NULL) {
free(cs->path_list[i]);
++i;
}
free(cs->path_list);
}
if ((cs != NULL) && (cs->prefix != NULL))
free(cs->prefix);
if (dirname != NULL)
free(dirname);
if (prefix != NULL)
free(prefix);
if (stext != NULL)
free(stext);
if (cs != NULL)
free(cs);
if (tokens != NULL)
free(tokens);
return retval;
}
/** Determine if completion matches the required prefix.
*
* Required prefix is stored in @a cs->prefix.
*
* @param cs Completion state object
* @param compl Completion string
* @return @c true when @a compl matches, @c false otherwise
*/
static bool compl_match_prefix(compl_t *cs, const char *compl)
{
return str_lcmp(compl, cs->prefix, cs->prefix_len) == 0;
}
/** Get next match. */
static errno_t compl_get_next(void *state, char **compl)
{
compl_t *cs = (compl_t *) state;
struct dirent *dent;
*compl = NULL;
if (cs->last_compl != NULL) {
free(cs->last_compl);
cs->last_compl = NULL;
}
/* Alias */
if (cs->alias_link != NULL) {
while (*compl == NULL && cs->alias_link != NULL) {
alias_t *data = odict_get_instance(cs->alias_link, alias_t, odict);
if (compl_match_prefix(cs, data->name)) {
asprintf(compl, "%s ", data->name);
cs->last_compl = *compl;
if (*compl == NULL)
return ENOMEM;
}
cs->alias_link = odict_next(cs->alias_link, &alias_dict);
}
}
/* Modules */
if (cs->module != NULL) {
while (*compl == NULL && cs->module->name != NULL) {
/* prevents multiple listing of an overriden cmd */
if (compl_match_prefix(cs, cs->module->name)) {
odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)cs->module->name, NULL);
if (alias_link == NULL) {
asprintf(compl, "%s ", cs->module->name);
cs->last_compl = *compl;
if (*compl == NULL)
return ENOMEM;
}
}
cs->module++;
}
}
/* Builtins */
if (cs->builtin != NULL) {
while (*compl == NULL && cs->builtin->name != NULL) {
if (compl_match_prefix(cs, cs->builtin->name)) {
/* prevents multiple listing of an overriden cmd */
odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)cs->module->name, NULL);
if (alias_link == NULL) {
asprintf(compl, "%s ", cs->builtin->name);
cs->last_compl = *compl;
if (*compl == NULL)
return ENOMEM;
}
}
cs->builtin++;
}
}
/* Files and directories. We scan entries from a set of directories. */
if (cs->path != NULL) {
while (*compl == NULL) {
/* Open next directory */
while (cs->dir == NULL) {
if (*cs->path == NULL)
break;
cs->dir = opendir(*cs->path);
/* Skip directories that we fail to open. */
if (cs->dir == NULL)
cs->path++;
}
/* If it was the last one, we are done */
if (cs->dir == NULL)
break;
/* Read next directory entry */
dent = readdir(cs->dir);
if (dent == NULL) {
/* Error. Close directory, go to next one */
closedir(cs->dir);
cs->dir = NULL;
cs->path++;
continue;
}
if (compl_match_prefix(cs, dent->d_name)) {
/* Construct pathname */
char *ent_path;
asprintf(&ent_path, "%s/%s", *cs->path, dent->d_name);
vfs_stat_t ent_stat;
if (vfs_stat_path(ent_path, &ent_stat) != EOK) {
/* Error */
free(ent_path);
continue;
}
free(ent_path);
/* prevents multiple listing of an overriden cmd */
if (cs->is_command && !ent_stat.is_directory) {
odlink_t *alias_link = odict_find_eq(&alias_dict, (void *)dent->d_name, NULL);
if (alias_link != NULL) {
continue;
}
}
asprintf(compl, "%s%c", dent->d_name,
ent_stat.is_directory ? '/' : ' ');
cs->last_compl = *compl;
if (*compl == NULL)
return ENOMEM;
}
}
}
if (*compl == NULL)
return ENOENT;
return EOK;
}
/** Finish completion operation. */
static void compl_fini(void *state)
{
compl_t *cs = (compl_t *) state;
if (cs->path_list != NULL) {
size_t i = 0;
while (cs->path_list[i] != NULL) {
free(cs->path_list[i]);
++i;
}
free(cs->path_list);
}
if (cs->last_compl != NULL)
free(cs->last_compl);
if (cs->dir != NULL)
closedir(cs->dir);
free(cs->prefix);
free(cs);
}