blob: e2cc8252e2c7eef7e3f9a35da7b83e675ee00b85 [file] [log] [blame]
/*
* Remote shell command execution (common for all transports) for linux
*
* Broadcom Proprietary and Confidential. Copyright (C) 2017,
* All Rights Reserved.
*
* This is UNPUBLISHED PROPRIETARY SOURCE CODE of Broadcom;
* the contents of this file may not be disclosed to third parties, copied
* or duplicated in any form, in whole or in part, without the prior
* written permission of Broadcom.
*
*
* <<Broadcom-WL-IPTag/Proprietary:>>
*
* $Id: shellproc_linux.c 514727 2014-11-12 03:02:48Z $
*/
/* Linux remote shell command execution
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <typedefs.h>
#include <bcmutils.h>
#include <bcmcdc.h>
#include "wlu_remote.h"
#include <sys/poll.h>
#include <malloc.h>
#include <miniopt.h>
#include <sys/utsname.h>
#define ASYNC_RESP 0
#define MAX_SHELL_ASYNC_RESP 128 /* Support for maximum 5 async process */
#define MAX_ASYNC_FILE_LENGTH 50
#define MAX_PID_CMD_LENGTH 20
#define MAX_PID_RESP_LENTH 50
#define MAX_SHELL_CMD_LENTH 256
#define PID_TOKEN_SIZE 50
#define PID_SEARCH_CMD_SIZE 100
#define ASYNC_SHELL_CHAR "%" /* Async process identifier from the client */
#define FILE_PERMISSION 777
#define DEFAULT_SHELL_TIMEOUT 0 /* Default TimeOut Value for synchronous shell commands */
#define SHELL_RETURNVALUE_SIZE 2 /* Size of Return Value of the shell command */
#define SHELL_ASYNCCMD_ID 1 /* To identify if it is an async command */
#define REBOOT_MSG "Rebooting AP ...\n"
/* Function prototypes */
static int rwl_get_file_size(char *file_name);
static int remote_shell_async_exec(char *buf_ptr);
static int remote_shell_sync_exec(char *cmd_buf_ptr, void *wl);
/* Data structure to hold async shell information */
typedef struct remote_shell_async {
pid_t PID;
char file_name[MAX_ASYNC_FILE_LENGTH];
} remote_shell_async_t;
remote_shell_async_t g_async_resp[MAX_SHELL_ASYNC_RESP];
extern int g_shellsync_pid;
extern unsigned char g_return_stat;
extern void rwl_chld_handler(int num);
extern int set_ctrlc;
extern void handle_ctrlc(int unused);
/* Global variable to store the timeout value for the shell commands */
static int g_shellsync_timeout = DEFAULT_SHELL_TIMEOUT;
char globalbuffer[MAX_SHELL_CMD_LENTH];
/* Wait for process termination.
* This function returns immediately if the child has
* already exited (zombie process)
*/
static void
sigchld_handler(int s)
{
UNUSED_PARAMETER(s);
while (waitpid(-1, NULL, WNOHANG) > 0);
}
/* Create a main directory \tmp\RWL\ for the shell response files */
int
rwl_create_dir(void)
{
if (mkdir(SHELL_RESP_PATH, FILE_PERMISSION) < 0) {
if (errno != EEXIST)
return BCME_ERROR;
}
return SUCCESS;
}
/* Main function for shell command execution */
int
remote_shell_execute(char* buf_ptr, void *wl)
{
char *async_cmd_flag;
int msg_len;
/* Check for the "%" token in the buffer from client
* If "%" token is present, execute asynchronous process
* else, execute synchronous shell process
*/
async_cmd_flag = strstr((char*)buf_ptr, ASYNC_SHELL_CHAR);
if ((async_cmd_flag != NULL) && (!strcmp(async_cmd_flag, ASYNC_SHELL_CHAR))) {
g_shellsync_pid = SHELL_ASYNCCMD_ID;
msg_len = remote_shell_async_exec(buf_ptr);
}
else {
msg_len = remote_shell_sync_exec(buf_ptr, wl);
strcpy(buf_ptr, globalbuffer);
}
return msg_len;
}
/* Function to get the shell response from the file */
int
remote_shell_async_get_resp(char* shell_fname, char* buf_ptr, int msg_len)
{
int sts = 0;
FILE *shell_fpt;
shell_fpt = fopen(shell_fname, "rb");
if (shell_fpt == NULL) {
DPRINT_ERR(ERR, "\nShell Cmd:File open error\n");
return sts;
}
/* If there is any response from the shell, Read the file and
* update the buffer for the shell response
* else Just send the return value of the command executed
*/
if (g_shellsync_pid != SHELL_ASYNCCMD_ID) {
if (msg_len)
sts = fread(buf_ptr, sizeof(char), msg_len, shell_fpt);
fscanf(shell_fpt, "%2x", &sts);
}
else
sts = fread(buf_ptr, sizeof(char), MAX_SHELL_CMD_LENTH, shell_fpt);
fclose(shell_fpt);
remove(shell_fname);
DPRINT_DBG(OUTPUT, "\n Resp buff from shell cmdis %s\n", buf_ptr);
return sts;
}
/*
* Function to get the shell response length
* by opening the file containing the shell response
* and get the total file size.
* For a given input file name it returns File size.
*/
static int
rwl_get_file_size(char *file_name)
{
FILE *shell_fpt;
int filesize = 0;
shell_fpt = fopen(file_name, "rb");
if (shell_fpt == NULL) {
DPRINT_DBG(OUTPUT, "\nShell Cmd:File open error\n");
return filesize;
}
/* obtain file size */
if (fseek(shell_fpt, 0, SEEK_END) < 0)
return filesize;
filesize = ftell(shell_fpt);
fclose(shell_fpt);
return filesize;
}
/*
* Function for executing asynchronous shell comamnd
* Stores the results in async temp file and returns the PID
*/
static int
remote_shell_async_exec(char *buf_ptr)
{
int PID_val = 0, val, msg_len, sts;
FILE *fpt;
int async_count = 0; /* counter needs to be initialized */
struct sigaction sa;
char pid_search_cmd[MAX_PID_CMD_LENGTH];
char pid_resp_buf[MAX_PID_RESP_LENTH];
char temp_async_file_name[MAX_ASYNC_FILE_LENGTH];
pid_t pid;
char *pid_token, next_pid[PID_TOKEN_SIZE][PID_TOKEN_SIZE];
struct utsname name;
/* Call the signal handler for reaping defunct or zombie process */
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction:");
}
/* Store the async file name if that async process is not killed.
* Async file name: async_temp_0...5
*/
for (val = 0; val < MAX_SHELL_ASYNC_RESP; val++) {
if (g_async_resp[val].PID > 0) {
async_count++;
} else {
sprintf(g_async_resp[val].file_name, "%s%d", "async_temp_", val);
break;
}
}
sprintf(temp_async_file_name, "%s%s", SHELL_RESP_PATH,
g_async_resp[val].file_name);
DPRINT_DBG(OUTPUT, "\nasync_count:%d\n", async_count);
if (async_count >= MAX_SHELL_ASYNC_RESP) {
sprintf(buf_ptr, "\n%s\n", "Exceeded max async process forking");
return BCME_ERROR;
}
/* Open a child process. The fork will return the PID of the child process
* (i.e) defunct process PID in parent's thread of execution. Zero is returned
* for child's thread of execution.
*/
if ((pid = fork()) == 0) {
/* Redirect the async process output to the async file
* Then after the client executes the kill command for that
* async process, the file will give the status of async process
*/
strtok(buf_ptr, ASYNC_SHELL_CHAR); /* Remove % character from the command buf */
uname(&name);
/*
* Checking for mips architecture
* different command for mips and x86
*/
if (strncmp(name.machine, "mips", sizeof(name.machine)) != 0) {
strcat(buf_ptr, "&> "); /* buf_ptr is now "ping 127.0.0.1&> " */
strcat(buf_ptr, temp_async_file_name); /* Add path \tmp\RWL\async_temp_* */
}
else {
strcat(buf_ptr, " > "); /* buf_ptr is now "ping 127.0.0.1> " */
strcat(buf_ptr, temp_async_file_name); /* Add path \tmp\RWL\async_temp_* */
strcat(buf_ptr, " 2>&1 &");
}
if ((sts = execl(SH_PATH, "sh", "-c", buf_ptr, NULL)) == -1) {
sprintf(buf_ptr, "%s\n", "Not able to execute shell cmd");
return BCME_ERROR;
}
exit(0);
} /* end of fork */
if (pid < 0) {
perror("\nFork error:");
sprintf(buf_ptr, "%s\n", "Forking async process failed");
return BCME_ERROR;
}
/* Find the PID of the running process (for ex: ping)
* pidof -s options returns latest PID of the command.
*/
strtok(buf_ptr, " ");
uname(&name);
/* Checking for mips architecture */
if (strncmp(name.machine, "mips", sizeof(name.machine)) != 0)
sprintf(pid_search_cmd, "pidof -s %s", buf_ptr);
else
sprintf(pid_search_cmd, "pidof %s", buf_ptr);
sleep(1);
/* Execute the command e.g "pidof ping" */
if ((fpt = popen(pid_search_cmd, "r")) == NULL) {
sprintf(buf_ptr, "%s\n", "Can't return PID");
return BCME_ERROR;
}
/* Get the PID and copy the PID in buf_ptr to send to the client */
fgets(pid_resp_buf, sizeof(pid_resp_buf), fpt);
/* Checking for mips architecture */
if (strncmp(name.machine, "mips", sizeof(name.machine)) != 0) {
PID_val = atoi(pid_resp_buf);
}
else {
/* code to extract the correct PID */
pid_token = strtok_r(pid_resp_buf, " ", (char **)next_pid);
/* the pid buffer will terminate with a '\n' */
while (pid_token != NULL && *pid_token != '\n') {
PID_val = atoi(pid_token);
pid_token = strtok_r(NULL, " ", (char **)next_pid);
}
}
if (PID_val == 0) {
msg_len = rwl_get_file_size(temp_async_file_name);
remote_shell_async_get_resp(temp_async_file_name, buf_ptr, msg_len);
} else {
g_async_resp[val].PID = PID_val;
/* Update PID value in buffer to send it to client */
sprintf(buf_ptr, "%d", PID_val);
msg_len = strlen(buf_ptr);
}
pclose(fpt);
/* In async case, the PID value will be copied to the input buffer only
* and there is no need of getting the response from the file. So return
* value can be -1.
*/
return msg_len;
}
/* Process for 'kill' command.
* Kill command can also be used from the client to get the
* result of asynchronous command and actually kill the mentioned process
*/
static int
remote_kill_cmd_exec(char *cmd_buf_ptr)
{
char file_name[MAX_ASYNC_FILE_LENGTH];
int PID_val = 0, val, msg_len;
FILE *fpt;
char *pid_token, next_pid[PID_TOKEN_SIZE][PID_TOKEN_SIZE];
system(cmd_buf_ptr);
/* Parse the PID val from the kill command.
*/
pid_token = strtok_r(cmd_buf_ptr, " ", (char **)next_pid);
while (pid_token != NULL && *pid_token != '\n') {
/* to extract the PID from the kill command */
PID_val = atoi(pid_token);
pid_token = strtok_r(NULL, " ", (char **)next_pid);
}
/* Check for the matching PID from the async structure and
* give the last 256 bytes statistics of the async process
* that was running
*/
for (val = 0; val < MAX_SHELL_ASYNC_RESP; ++val) {
if (g_async_resp[val].PID == PID_val) {
/* We found a match here. Hence get the response now from the
* corresponding async response file
*/
sprintf(file_name, "%s%s", SHELL_RESP_PATH, g_async_resp[val].file_name);
msg_len = rwl_get_file_size(file_name);
if (msg_len > 0) {
if ((fpt = fopen(file_name, "rb")) == NULL) {
DPRINT_DBG(OUTPUT, "\nShell Cmd:File open error\n");
return BCME_ERROR;
}
if (fseek(fpt, 0, SEEK_SET) < 0) {
fclose(fpt);
return BCME_ERROR;
}
if (fread(cmd_buf_ptr, sizeof(char), MAX_SHELL_CMD_LENTH,
fpt) <= 0) {
sprintf(cmd_buf_ptr, "%s\n", "Shell Resp:Reading error");
fclose(fpt);
return BCME_ERROR;
}
fclose(fpt);
}
else
sprintf(cmd_buf_ptr, "ed %d: No Response\n", PID_val);
remove(g_async_resp[val].file_name);
g_async_resp[val].PID = 0;
break;
}
}
return MAX_SHELL_CMD_LENTH;
}
/* Handle --timeout command line option for linux servers */
int
shell_timeout_cmd(char *cmd_buf_ptr, char *sync_file_name)
{
char *token1, *token2, *nexttoken;
FILE* fp;
int msg_len;
token1 = strtok_r(cmd_buf_ptr, "--timeout ", &nexttoken);
if (token1)
token2 = strtok_r(NULL, token1, &nexttoken);
if (token1 == NULL || atoi(token1) <= 0 || token2 == NULL) {
fp = fopen(sync_file_name, "w+");
fprintf(fp, "Usage: ./wl --<transport> <ip/mac> sh"
"--timeout <timeout value> <shell command>\n");
fprintf(fp, "Eg: ./wl --socket 172.22.65.226 sh --timeout 15 ls\n");
fflush(fp);
msg_len = rwl_get_file_size(sync_file_name);
strcpy(cmd_buf_ptr, sync_file_name);
fclose(fp);
strcpy(globalbuffer, sync_file_name);
printf("Fix timeout problem in socket!!!!!\n");
return msg_len;
}
else
g_shellsync_timeout = atoi(token1);
return BCME_OK;
}
/* Handle synchronous shell commands here */
static int
remote_shell_sync_exec(char *cmd_buf_ptr, void *wl)
{
char *kill_cmd_token;
char sync_file_name[] = TEMPLATE;
int fd, msg_len;
char cmd[(strlen(cmd_buf_ptr) + 1)];
int pid, status, pid_final;
char buf[SHELL_RESP_SIZE], cmd_find_lastpid[PID_SEARCH_CMD_SIZE];
int nbytes = 0;
int child_status;
static int sent_once = 0;
struct utsname name;
FILE *fpt;
/* Default Size of Return Value of the shell command is 2bytes */
kill_cmd_token = strstr(cmd_buf_ptr, "kill");
/* Synchronous Kill command processing is handled separately */
if (kill_cmd_token != NULL) {
msg_len = remote_kill_cmd_exec(cmd_buf_ptr);
remote_tx_response(wl, cmd_buf_ptr, msg_len);
return 0;
}
/* Process synchronous command other than kill command */
if ((fd = mkstemp(sync_file_name)) < 0) {
perror("mkstemp failed");
DPRINT_ERR(ERR, "\n errno:%d\n", errno);
sprintf(cmd_buf_ptr, "%s\n", "mkstemp failed");
return BCME_ERROR;
}
close(fd);
strcpy(cmd, cmd_buf_ptr);
/* Synchronous timeout command processing is handled separately */
if (strstr(cmd_buf_ptr, "--timeout") != NULL) {
if ((msg_len = shell_timeout_cmd (cmd, sync_file_name) > 0)) {
/* Signal end of command output */
g_rem_ptr->msg.len = 0;
g_rem_ptr->msg.cmd = g_return_stat;
remote_tx_response(wl, NULL, 0);
return msg_len;
} else {
/* Parse out --timeout <val> since command is successful
* point buffer to the shell command
*/
strcpy(cmd, cmd_buf_ptr);
strtok_r(cmd, " ", &cmd_buf_ptr);
strcpy(cmd, cmd_buf_ptr);
strtok_r(cmd, " ", &cmd_buf_ptr);
}
}
/* Schedule an ALARM in case of timeout value of SHELL_TIMEOUT seconds */
/* Defalut time out only in case of Non socket transport */
alarm(g_shellsync_timeout);
/* registering the relevant signals to handle end of child process,
* the ctrl+c event on the server side and the kill command on the
* server process
*/
signal(SIGCHLD, rwl_chld_handler);
signal(SIGINT, handle_ctrlc);
signal(SIGTERM, handle_ctrlc);
/* Set g_sig_chld before forking */
g_sig_chld = 1;
if (strcmp("reboot", cmd_buf_ptr) == 0) { /* reboot command */
memset(buf, 0, sizeof(buf));
strncpy(buf, REBOOT_MSG, sizeof(REBOOT_MSG));
remote_tx_response(wl, buf, 0);
/* Signal end of command output */
g_rem_ptr->msg.len = 0;
g_rem_ptr->msg.cmd = 0;
remote_tx_response(wl, NULL, 0);
sleep(1);
/* Clean up the temp file */
remove(sync_file_name);
}
if ((pid = fork()) == 0) {
close(STDOUT_FILENO);
fd = open(sync_file_name, O_WRONLY|O_SYNC);
/* Redirect stdin to dev/null. This handles un usual commands like
* sh cat from the client side
*/
close(STDIN_FILENO);
open("/dev/null", O_RDONLY);
close(STDERR_FILENO);
fcntl(fd, F_DUPFD, STDERR_FILENO);
if ((status = execl(SH_PATH, "sh", "-c", cmd_buf_ptr, NULL)) == -1) {
perror("Exec error");
}
} /* end of fork */
g_shellsync_pid = pid;
/* The g_return_stat is being set for short commands */
waitpid(g_shellsync_pid, &child_status, WNOHANG);
if (WIFEXITED(child_status))
g_return_stat = WEXITSTATUS(child_status);
else
g_return_stat = 1;
/* Read file in the interim from a temp file and send back the results */
fd = open(sync_file_name, O_RDONLY|O_SYNC);
while (1) {
/* read file in the interim and send back the results */
nbytes = read(fd, buf, SHELL_RESP_SIZE);
g_rem_ptr->msg.len = nbytes;
if (nbytes > 0) {
remote_tx_response(wl, buf, 0);
#ifdef RWL_SERIAL
/* usleep introduced for flooding of data over serial port */
usleep(1);
#endif
}
if (get_ctrlc_header(wl) >= 0) {
if (g_rem_ptr->msg.flags == (unsigned)CTRLC_FLAG) {
uname(&name);
/* Checking for mips architecture
* The mips machine responds differently to
* execl command. so the pid is incremented
* to kill the right command.
*/
if (strncmp(name.machine, "mips", sizeof(name.machine)) == 0)
pid++;
if (strncmp(name.machine, "armv5tel", sizeof(name.machine)) == 0) {
snprintf(cmd_find_lastpid, sizeof(cmd_find_lastpid),
"ps | awk \'PRINT $1\' | tail -n 1");
if ((fpt = popen(cmd_find_lastpid, "r")) == NULL) {
sprintf(buf, "%s\n", "Can't return PID");
return BCME_ERROR;
}
fgets(cmd_find_lastpid, sizeof(cmd_find_lastpid), fpt);
pid_final = atoi(cmd_find_lastpid);
while (pid <= pid_final) {
kill(pid, SIGKILL);
pid++;
}
pclose(fpt);
}
else {
kill(pid, SIGKILL);
}
break;
}
}
if (get_ctrlc_header(wl) >= 0) {
if (g_rem_ptr->msg.flags == (unsigned)CTRLC_FLAG) {
uname(&name);
/* Checking for mips architecture
* The mips machine responds differently to
* execl command. so the pid is incremented
* to kill the right command.
*/
if (strncmp(name.machine, "mips", sizeof(name.machine)) == 0) {
pid++;
kill(pid, SIGKILL);
}
/* Checking for arm architecture
* The multiple commands would not work
* for ctrl+C. So we kill the processes
* spawned after the parent. This method has
* its own limitations but the busybox in pxa
* doesnot have many options to implement it better
*/
else {
if (strncmp(name.machine, "armv5tel",
sizeof(name.machine)) == 0) {
/* The command below is used to get the
* PIDs and they are killed
*/
snprintf(cmd_find_lastpid,
sizeof(cmd_find_lastpid),
"ps | awk \'PRINT $1\' | tail -n 1");
if ((fpt = popen(cmd_find_lastpid, "r")) == NULL) {
sprintf(buf, "%s\n", "Can't return PID");
return BCME_ERROR;
}
fgets(cmd_find_lastpid, sizeof(cmd_find_lastpid),
fpt);
pid_final = atoi(cmd_find_lastpid);
while (pid <= pid_final) {
kill(pid, SIGKILL);
pid++;
}
pclose(fpt);
}
/* In the case of x86, on receiving ctrl+C
* the child PIDs are obtained by searching
* the parent PID to obtain the PIDs of the
* and kill them
*/
else {
while (pid != 0) {
/* The commad below is used to get the
* child PIDs by using their parent PID
*/
snprintf(cmd_find_lastpid,
sizeof(cmd_find_lastpid),
"ps al | awk \"{ if (\\$4 == %d)"
" {print \\$3}}\"| head -n 1",
g_shellsync_pid);
if ((fpt = popen(cmd_find_lastpid, "r"))
== NULL) {
sprintf(buf, "%s\n",
"Can't return PID");
return BCME_ERROR;
}
fgets(cmd_find_lastpid,
sizeof(cmd_find_lastpid),
fpt);
pid = atoi(cmd_find_lastpid);
if (pid == 0)
kill(g_shellsync_pid, SIGKILL);
else
kill(pid, SIGKILL);
pclose(fpt);
}
}
}
break;
}
}
if (set_ctrlc == 1) {
g_rem_ptr->msg.len = 0;
g_rem_ptr->msg.cmd = g_return_stat;
remote_tx_response(wl, NULL, g_return_stat);
unlink(sync_file_name);
kill(0, SIGKILL);
}
/* It is possible that the child would have exited
* However we did not get a chance to read the file
* In this case go once again and check the file
*/
if (!sent_once && !g_sig_chld) {
sent_once = 1;
continue;
}
if (!(g_sig_chld || nbytes))
break;
}
wait(NULL);
close(fd);
/* Signal end of command output */
g_rem_ptr->msg.len = 0;
g_rem_ptr->msg.cmd = g_return_stat;
remote_tx_response(wl, NULL, g_return_stat);
/* Cancel the time out alarm if any */
alarm(0);
sent_once = 0;
/* Clean up the temp file */
unlink(sync_file_name);
g_shellsync_timeout = DEFAULT_SHELL_TIMEOUT;
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
return BCME_OK;
}