blob: 2a6c75a9ef2d7432f2743a7b547bb1dc59e9d101 [file] [edit]
/*
*
* Copyright (c) 2016 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Description:
* This file implements the main program entry point for the
* WPAN control utility, `wpanctl`.
*
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#undef ASSERT_MACROS_USE_SYSLOG
#define ASSERT_MACROS_USE_SYSLOG 0
#include <getopt.h>
#include "assert-macros.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <libgen.h>
#include <time.h>
#if HAVE_PWD_H
#include <pwd.h>
#endif
#include <dbus/dbus.h>
#if HAVE_LIBREADLINE
#include <readline/readline.h>
#include <readline/history.h>
#endif // HAVE_LIBREADLINE
#include <poll.h>
#include "args.h"
#include "config-file.h"
#include "wpanctl-cmds.h"
#include "version.h"
#include "string-utils.h"
#include "wpanctl-utils.h"
#include "wpan-dbus-v0.h"
static bool istty = true;
int gDebugMode = 0;
static arg_list_item_t option_list[] = {
{ 'h', "help", NULL,
"Print Help" },
{ 'v', "version", NULL,
"Print Version Information" },
{ 'f', NULL, "filename",
"Read commands from file" },
{ 'I', "interface", "iface",
"Set interface to use" },
{ 0, "ignore-mismatch", NULL, "Ignore driver version mismatch" },
{ 0 }
};
void print_commands();
int exec_command(
int argc, char * argv[]);
static int
tool_cmd_help(
int argc, char* argv[]
)
{
if ((2 == argc) && (0 == strcmp(argv[1], "--help"))) {
printf("Help not yet implemented for this command.\n");
return ERRORCODE_HELP;
}
if ((argc == 2) && argv[1][0] != '-') {
const char *argv2[2] = {
argv[1],
"--help"
};
return exec_command(2, (char**)argv2);
} else {
print_commands();
}
return ERRORCODE_HELP;
}
static int tool_cmd_clear(int argc, char *argv[])
{
if (system("clear") == -1) {
printf("\n\n\n\n\n\n\n\n");
}
return 0;
}
struct command_info_s commandList[] = {
WPANCTL_CLI_COMMANDS,
{"quit", "Terminate command line mode.", NULL},
{"help", "Display this help.", &tool_cmd_help},
{"clear", "Clear shell.", &tool_cmd_clear},
{"?", NULL, &tool_cmd_help, 1},
{NULL}
};
void
print_commands()
{
int i;
printf("Commands:\n");
for (i = 0; commandList[i].name; ++i) {
if (commandList[i].isHidden)
continue;
printf(
" %s %s%s\n",
commandList[i].name,
&" "[strlen(commandList[i].name)],
commandList[i].desc
);
}
}
struct command_info_s * find_cmd(char *cmd_name)
{
int idx;
for (idx = 0; commandList[idx].name; idx++) {
if (strcmp(cmd_name, commandList[idx].name) == 0) {
return commandList + idx;
}
}
return NULL;
}
int exec_command(int argc, char * argv[])
{
int ret = 0;
struct command_info_s *cmd_entry;
require(argc, bail);
if ((strcmp(argv[0], "quit") == 0) || (strcmp(argv[0], "exit") == 0) ||
(strcmp(argv[0], "q") == 0)) {
ret = ERRORCODE_QUIT;
goto bail;
}
if ((cmd_entry = find_cmd(argv[0])) == NULL) {
fprintf(stderr, "The command \"%s\" is not recognised.\n",
argv[0]);
ret = ERRORCODE_BADCOMMAND;
goto bail;
}
if (cmd_entry->entrypoint == NULL) {
fprintf(stderr,
"The command \"%s\" is not yet implemented.\n",
cmd_entry->name);
ret = ERRORCODE_NOCOMMAND;
goto bail;
}
ret = cmd_entry->entrypoint(argc, argv);
bail:
return ret;
}
#if HAVE_LIBREADLINE
static bool history_disabled;
char* wpanctl_generator(const char* text, int state)
{
static int idx, len;
const char *name;
if (!state) {
idx = 0;
len = strlen(text);
}
while ((name = commandList[idx].name)) {
idx++;
if (commandList[idx - 1].isHidden)
continue;
if (strncmp(name, text, len) == 0)
return strdup(name);
}
return NULL;
}
static char** wpanctl_completion(const char *text, int start, int end)
{
if (start != 0)
return NULL;
return rl_completion_matches(text, wpanctl_generator);
}
#endif // HAVE_LIBREADLINE
void
process_input_line(char *l)
{
char *inputstring;
char *argv2[100];
char **ap = argv2;
int argc2 = 0;
if (!l[0]) {
l = NULL;
goto bail;
}
l = strdup(l);
#if HAVE_LIBREADLINE
if (!history_disabled) {
add_history(l);
}
#endif // HAVE_LIBREADLINE
inputstring = l;
while ((*ap = get_next_arg(inputstring, &inputstring))) {
if (**ap != '\0') {
ap++;
argc2++;
}
}
if (argc2 > 0) {
gRet = exec_command(argc2, argv2);
if (gRet == ERRORCODE_QUIT)
goto bail;
else if (gRet == ERRORCODE_ERRNO)
fprintf(stderr, "errno=%d %s\n", errno, strerror(errno));
else if (gRet < 0 && (gRet != ERRORCODE_HELP))
fprintf(stderr, "Error %d %s\n", gRet, strerror(-gRet));
else if (gRet && (gRet != ERRORCODE_HELP))
fprintf(stderr, "Error %d (0x%02X)\n", gRet, gRet);
#if HAVE_LIBREADLINE
if (!history_disabled)
write_history(getenv("WPANCTL_HISTORY_FILE"));
#endif // HAVE_LIBREADLINE
}
bail:
free(l);
return;
}
#if HAVE_LIBREADLINE
static char*
get_current_prompt()
{
static char prompt[64] = {};
if (!gInterfaceName[0]) {
snprintf(prompt,
sizeof(prompt),
"wpanctl> "
);
} else {
snprintf(prompt,
sizeof(prompt),
"wpanctl:%s> ",
gInterfaceName
);
}
return prompt;
}
void
process_input_readline(char *l)
{
if (NULL == l)
{
gRet = ERRORCODE_QUIT;
rl_callback_handler_remove();
return;
}
process_input_line(l);
if (istty) {
#if HAVE_RL_SET_PROMPT
if (gRet == ERRORCODE_QUIT)
rl_set_prompt("");
else
#endif
rl_callback_handler_install(
get_current_prompt(), &process_input_readline);
}
}
#endif // HAVE_LIBREADLINE
#pragma mark -
#if HAVE_LIBREADLINE
static int
initialize_readline()
{
int ret = 0;
require_action(NULL != readline, bail, ret = ERRORCODE_NOREADLINE);
rl_initialize();
rl_readline_name = "wpanctl";
//rl_completer_word_break_characters = " \t\n\"\\'`@$><|&{("; // Removed '=' ';'
using_history();
read_history(getenv("WPANCTL_HISTORY_FILE"));
rl_instream = stdin;
rl_callback_handler_install(get_current_prompt(), &process_input_readline);
rl_attempted_completion_function = wpanctl_completion;
bail:
return ret;
}
#endif
static void
print_version()
{
printf("wpanctl " PACKAGE_VERSION );
if ((internal_build_source_version[0] == 0) || strequal(SOURCE_VERSION, internal_build_source_version)) {
if (strequal(PACKAGE_VERSION, SOURCE_VERSION)) {
printf(" (%s)\n", internal_build_date);
} else {
printf(" (" SOURCE_VERSION "; %s)\n", internal_build_date);
}
} else {
if (strequal(SOURCE_VERSION, PACKAGE_VERSION) || strequal(PACKAGE_VERSION, internal_build_source_version)) {
printf(" (%s; %s)\n", internal_build_source_version, internal_build_date);
} else {
printf(" (" SOURCE_VERSION "/%s; %s)\n", internal_build_source_version, internal_build_date);
}
}
}
static int
wpan_dbus_version_check(DBusConnection* connection)
{
int timeout = 5 * 1000; // Five second timeout
int ret = ERRORCODE_BADVERSION;
DBusMessage *message = NULL;
DBusMessage *reply = NULL;
uint32_t version = 0;
DBusError error;
dbus_error_init(&error);
message = dbus_message_new_method_call(
getenv("WPANCTL_DBUS_NAME"),
WPAN_TUNNEL_DBUS_PATH,
WPAN_TUNNEL_DBUS_INTERFACE,
WPAN_TUNNEL_CMD_GET_VERSION
);
if (!message) {
fprintf(stderr, "error: Unable to allocate dbus message\n");
ret = ERRORCODE_ALLOC;
goto bail;
}
reply = dbus_connection_send_with_reply_and_block(
connection,
message,
timeout,
&error
);
if (!reply) {
fprintf(stderr, "error: %s\n", error.message);
ret = ERRORCODE_TIMEOUT;
goto bail;
}
dbus_message_get_args(
reply, NULL,
DBUS_TYPE_UINT32, &version,
DBUS_TYPE_INVALID
);
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: Version check, wpanctl=%d, wpantund=%d\n", WPAN_TUNNEL_DBUS_VERSION, version);
}
if(version != WPAN_TUNNEL_DBUS_VERSION) {
fprintf(stderr, "error: `wpantund` version (%d) doesn't match `wpanctl` version (%d).\n", version, WPAN_TUNNEL_DBUS_VERSION);
goto bail;
}
ret = 0;
bail:
if (message)
dbus_message_unref(message);
if (reply)
dbus_message_unref(reply);
dbus_error_free(&error);
return ret;
}
#pragma mark -
int main(int argc, char * argv[])
{
int c;
bool ignore_driver_version_mismatch = false;
DBusError error;
DBusConnection* connection = NULL;
dbus_error_init(&error);
srandom(time(NULL));
while (1) {
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{"ignore-mismatch", no_argument, 0, 'i'},
{"debug", no_argument, 0, 'd'},
{"interface", required_argument, 0, 'I'},
{"file", required_argument, 0, 'f'},
{0, 0, 0, 0}
};
int option_index = 0;
if ((optind < argc) && (find_cmd(argv[optind]) != NULL)) {
// This is where the wpanctl command starts; skip
// parsing the flags since they may belong to the command
break;
}
c = getopt_long(argc, argv, "hvidI:f:", long_options,
&option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_version();
print_arg_list_help(option_list,
argv[0],
"[options] <sub-command> [args]");
print_commands();
gRet = ERRORCODE_HELP;
goto bail;
case 'v':
print_version();
gRet = 0;
goto bail;
case 'd':
gDebugMode++;
break;
case 'I':
snprintf(gInterfaceName, sizeof(gInterfaceName),
"%s", optarg);
break;
case 'i':
ignore_driver_version_mismatch = true;
break;
case 'f':
#if HAVE_LIBREADLINE
if (NULL == freopen(optarg, "r", stdin))
{
fprintf(stderr,
"%s: error: Unable to open file \"%s\".\n",
argv[0], optarg);
return ERRORCODE_BADARG;
}
#else
fprintf(stderr,
"%s: Cannot read from file \"%s\" : Missing readline library.\n",
argv[0], optarg);
return ERRORCODE_BADARG;
#endif
default:
break;
}
}
istty = isatty(fileno(stdin));
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: isatty(fileno(stdin)) = %d\n", istty);
}
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: Will use interface '%s'.\n", gInterfaceName);
}
#if HAVE_PWD_H
if (getuid() == 0 && strcmp(WPANTUND_SERVICE_USER, "root")) {
uid_t target_uid = 0;
gid_t target_gid = 0;
struct passwd *passwd = getpwnam(WPANTUND_SERVICE_USER);
if (passwd == NULL) {
fprintf(stderr, "getpwnam: Unable to lookup user \"%s\".", WPANTUND_SERVICE_USER);
gRet = ERRORCODE_ERRNO;
goto bail;
}
target_uid = passwd->pw_uid;
target_gid = passwd->pw_gid;
if (target_gid != 0) {
if (setgid(target_gid) != 0) {
perror("setgid");
gRet = ERRORCODE_ERRNO;
goto bail;
}
}
if (target_uid != 0) {
if (setuid(target_uid) != 0) {
perror("setuid");
gRet = ERRORCODE_ERRNO;
goto bail;
}
}
}
#endif // HAVE_PWD_H
if (getenv("WPANCTL_DBUS_NAME") && gDebugMode>=1)
fprintf(stderr, "DEBUG: Using dbus \"%s\"\n", getenv("WPANCTL_DBUS_NAME"));
setenv("WPANCTL_DBUS_NAME", WPAN_TUNNEL_DBUS_NAME, 0);
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: trying dbus_bus_get(DBUS_BUS_SYSTEM). . .\n");
}
connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
require_string(connection != NULL, bail, error.message);
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: DBusConnection: %p\n", connection);
}
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: Registering DBusConnection. . .\n");
}
dbus_bus_register(connection, &error);
require_string(error.name == NULL, bail, error.message);
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: DBusConnection registered.\n");
}
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: Requesting DBus name \"%s\". . .\n",WPAN_TUNNEL_DBUS_NAME ".wpanctl");
}
dbus_bus_request_name(connection,
WPAN_TUNNEL_DBUS_NAME ".wpanctl",
0,
&error);
if (gDebugMode >= 1) {
if (error.name != NULL) {
fprintf(stderr, "DEBUG: Requesting DBus name \"%s\" failed (no biggie): %s\n",WPAN_TUNNEL_DBUS_NAME ".wpanctl", error.name);
} else {
fprintf(stderr, "DEBUG: Requesting DBus name \"%s\" succeded.\n",WPAN_TUNNEL_DBUS_NAME ".wpanctl");
}
}
// Don't fail if we can't get the name. It isn't a big deal.
//require_string(error.name == NULL, bail, error.message);
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: Performing wpantund version check. . .\n");
}
// Make sure that we are compatible with the copy of wpantund
// that is currently running.
gRet = wpan_dbus_version_check(connection);
if (gRet != 0) {
fprintf(stderr,
"%s: error: `wpantund` is either not running, locked up, or incompatible with this version of `wpanctl`.\n",
argv[0]
);
if (!ignore_driver_version_mismatch)
goto bail;
} else {
if (gDebugMode >= 1) {
fprintf(stderr,
"DEBUG: wpantund version check succeded.\n");
}
}
if (optind < argc) {
if (gDebugMode >= 1) {
fprintf(stderr, "DEBUG: Executing command '%s'. . .\n",
argv[optind]);
}
argc -= optind;
argv += optind;
optind = 0;
gRet = exec_command(argc, argv);
goto bail;
}
if (istty) {
#if !HAVE_LIBREADLINE
fprintf(stderr,
"%s: error: Interactive mode disabled: Compiled without libeditline or libreadline support.\n",
argv[0]
);
print_arg_list_help(option_list,
argv[0],
"[options] <sub-command> [args]");
print_commands();
gRet = ERRORCODE_NOCOMMAND;
goto bail;
#else // HAVE_LIBREADLINE
setenv("WPANCTL_HISTORY_FILE", tilde_expand("~/.wpanctl_history"), 0);
gRet = initialize_readline();
if(gRet) {
fprintf(stderr,
"%s: error: Failed to initialize readline: %d\n",
argv[0], gRet
);
goto bail;
}
#endif // HAVE_LIBREADLINE
}
// Command mode.
while ((gRet != ERRORCODE_QUIT) && !feof(stdin)) {
optind = 0;
#if HAVE_LIBREADLINE
if (istty) {
int dbus_fd = -1;
dbus_connection_get_unix_fd(connection, &dbus_fd);
struct pollfd polltable[2] = {
{ fileno(stdin), POLLIN | POLLHUP, 0 },
{ dbus_fd, POLLIN | POLLHUP, 0 },
};
if (poll(
polltable,
(dbus_fd >= 0) ? 2 : 1,
1000
) < 0
) {
if (errno == EINTR) {
// We just caught a signal.
// Do nothing.
} else {
break;
}
}
if (polltable[0].revents)
rl_callback_read_char();
} else
#endif // HAVE_LIBREADLINE
{
char linebuffer[200];
process_input_line(fgets(linebuffer, sizeof(linebuffer), stdin));
}
dbus_connection_read_write_dispatch(connection, 0);
}
printf("\n");
bail:
#if HAVE_LIBREADLINE
rl_callback_handler_remove();
#endif // HAVE_LIBREADLINE
if (gRet == ERRORCODE_QUIT)
gRet = 0;
if (connection) {
dbus_connection_unref(connection);
}
dbus_error_free(&error);
return gRet;
}