2 * Remote shell command execution (common for all transports) for linux
4 * Copyright (C) 2010, Broadcom Corporation
7 * This is UNPUBLISHED PROPRIETARY SOURCE CODE of Broadcom Corporation;
8 * the contents of this file may not be disclosed to third parties, copied
9 * or duplicated in any form, in whole or in part, without the prior
10 * written permission of Broadcom Corporation.
12 * $Id: shellproc_linux.c,v 1.12 2009-08-11 08:51:01 Exp $
15 /* Linux remote shell command execution
26 #include <sys/socket.h>
27 #include <sys/select.h>
28 #include <sys/types.h>
29 #include <netinet/in.h>
36 #include "wlu_remote.h"
42 #include <sys/utsname.h>
44 #define MAX_SHELL_ASYNC_RESP 128 /* Support for maximum 5 async process */
45 #define MAX_ASYNC_FILE_LENGTH 50
46 #define MAX_PID_CMD_LENGTH 20
47 #define MAX_PID_RESP_LENTH 50
48 #define MAX_SHELL_CMD_LENTH 256
49 #define PID_TOKEN_SIZE 50
50 #define PID_SEARCH_CMD_SIZE 100
51 #define ASYNC_SHELL_CHAR "%" /* Async process identifier from the client */
52 #define FILE_PERMISSION 777
54 #define DEFAULT_SHELL_TIMEOUT 0 /* Default TimeOut Value for synchronous shell commands */
55 #define SHELL_RETURNVALUE_SIZE 2 /* Size of Return Value of the shell command */
56 #define SHELL_ASYNCCMD_ID 1 /* To identify if it is an async command */
57 #define REBOOT_MSG "Rebooting AP ...\n"
59 /* Function prototypes */
61 static int rwl_get_file_size(char *file_name
);
62 static int remote_shell_async_exec(char *buf_ptr
);
63 static int remote_shell_sync_exec(char *cmd_buf_ptr
, void *wl
);
66 /* Data structure to hold async shell information */
67 typedef struct remote_shell_async
{
69 char file_name
[MAX_ASYNC_FILE_LENGTH
];
70 } remote_shell_async_t
;
72 remote_shell_async_t g_async_resp
[MAX_SHELL_ASYNC_RESP
];
74 extern int g_shellsync_pid
;
76 extern unsigned char g_return_stat
;
77 extern void rwl_chld_handler(int num
);
79 extern void handle_ctrlc(int unused
);
81 /* Global variable to store the timeout value for the shell commands */
82 static int g_shellsync_timeout
= DEFAULT_SHELL_TIMEOUT
;
83 char globalbuffer
[MAX_SHELL_CMD_LENTH
];
85 /* Wait for process termination.
86 * This function returns immediately if the child has
87 * already exited (zombie process)
90 sigchld_handler(int s
)
94 while (waitpid(-1, NULL
, WNOHANG
) > 0);
97 /* Create a main directory \tmp\RWL\ for the shell response files */
102 if (mkdir(SHELL_RESP_PATH
, FILE_PERMISSION
) < 0) {
107 system("mkdir -p /tmp/RWL");
108 #endif /* mkdir linux command doesnot work in MAC, hence using shell */
113 /* Main function for shell command execution */
115 remote_shell_execute(char* buf_ptr
, void *wl
)
117 char *async_cmd_flag
;
120 /* Check for the "%" token in the buffer from client
121 * If "%" token is present, execute asynchronous process
122 * else, execute synchronous shell process
124 async_cmd_flag
= strstr((char*)buf_ptr
, ASYNC_SHELL_CHAR
);
126 if ((async_cmd_flag
!= NULL
) && (!strcmp(async_cmd_flag
, ASYNC_SHELL_CHAR
))) {
127 g_shellsync_pid
= SHELL_ASYNCCMD_ID
;
128 msg_len
= remote_shell_async_exec(buf_ptr
);
131 msg_len
= remote_shell_sync_exec(buf_ptr
, wl
);
132 strcpy(buf_ptr
, globalbuffer
);
137 /* Function to get the shell response from the file */
139 remote_shell_async_get_resp(char* shell_fname
, char* buf_ptr
, int msg_len
)
144 shell_fpt
= fopen(shell_fname
, "rb");
146 if (shell_fpt
== NULL
) {
147 DPRINT_ERR(ERR
, "\nShell Cmd:File open error\n");
151 /* If there is any response from the shell, Read the file and
152 * update the buffer for the shell response
153 * else Just send the return value of the command executed
155 if (g_shellsync_pid
!= SHELL_ASYNCCMD_ID
) {
157 sts
= fread(buf_ptr
, sizeof(char), msg_len
, shell_fpt
);
158 fscanf(shell_fpt
, "%2x", &sts
);
161 sts
= fread(buf_ptr
, sizeof(char), MAX_SHELL_CMD_LENTH
, shell_fpt
);
167 DPRINT_DBG(OUTPUT
, "\n Resp buff from shell cmdis %s\n", buf_ptr
);
173 * Function to get the shell response length
174 * by opening the file containing the shell response
175 * and get the total file size.
176 * For a given input file name it returns File size.
179 rwl_get_file_size(char *file_name
)
184 shell_fpt
= fopen(file_name
, "rb");
186 if (shell_fpt
== NULL
) {
187 DPRINT_DBG(OUTPUT
, "\nShell Cmd:File open error\n");
191 /* obtain file size */
192 if (fseek(shell_fpt
, 0, SEEK_END
) < 0)
195 filesize
= ftell(shell_fpt
);
202 * Function for executing asynchronous shell comamnd
203 * Stores the results in async temp file and returns the PID
206 remote_shell_async_exec(char *buf_ptr
)
208 int PID_val
, val
, msg_len
, sts
;
210 int async_count
= 0; /* counter needs to be initialized */
212 char pid_search_cmd
[MAX_PID_CMD_LENGTH
];
213 char pid_resp_buf
[MAX_PID_RESP_LENTH
];
214 char temp_async_file_name
[MAX_ASYNC_FILE_LENGTH
];
216 char *pid_token
, next_pid
[PID_TOKEN_SIZE
][PID_TOKEN_SIZE
];
219 /* Call the signal handler for reaping defunct or zombie process */
220 sa
.sa_handler
= sigchld_handler
;
221 sigemptyset(&sa
.sa_mask
);
222 sa
.sa_flags
= SA_RESTART
;
223 if (sigaction(SIGCHLD
, &sa
, NULL
) == -1) {
224 perror("sigaction:");
227 /* Store the async file name if that async process is not killed.
228 * Async file name: async_temp_0...5
230 for (val
= 0; val
< MAX_SHELL_ASYNC_RESP
; val
++) {
231 if (g_async_resp
[val
].PID
> 0) {
234 sprintf(g_async_resp
[val
].file_name
, "%s%d", "async_temp_", val
);
238 sprintf(temp_async_file_name
, "%s%s", SHELL_RESP_PATH
,
239 g_async_resp
[val
].file_name
);
241 DPRINT_DBG(OUTPUT
, "\nasync_count:%d\n", async_count
);
242 if (async_count
>= MAX_SHELL_ASYNC_RESP
) {
243 sprintf(buf_ptr
, "\n%s\n", "Exceeded max async process forking");
247 /* Open a child process. The fork will return the PID of the child process
248 * (i.e) defunct process PID in parent's thread of execution. Zero is returned
249 * for child's thread of execution.
251 if ((pid
= fork()) == 0) {
252 /* Redirect the async process output to the async file
253 * Then after the client executes the kill command for that
254 * async process, the file will give the status of async process
256 strtok(buf_ptr
, ASYNC_SHELL_CHAR
); /* Remove % character from the command buf */
259 * Checking for mips architecture
260 * different command for mips and x86
262 if (strncmp(name
.machine
, "mips", sizeof(name
.machine
)) != 0) {
263 strcat(buf_ptr
, "&> "); /* buf_ptr is now "ping 127.0.0.1&> " */
264 strcat(buf_ptr
, temp_async_file_name
); /* Add path \tmp\RWL\async_temp_* */
267 strcat(buf_ptr
, " > "); /* buf_ptr is now "ping 127.0.0.1> " */
268 strcat(buf_ptr
, temp_async_file_name
); /* Add path \tmp\RWL\async_temp_* */
269 strcat(buf_ptr
, " 2>&1 &");
271 if ((sts
= execl(SH_PATH
, "sh", "-c", buf_ptr
, NULL
)) == -1) {
272 sprintf(buf_ptr
, "%s\n", "Not able to execute shell cmd");
279 perror("\nFork error:");
280 sprintf(buf_ptr
, "%s\n", "Forking async process failed");
284 /* Find the PID of the running process (for ex: ping)
285 * pidof -s options returns latest PID of the command.
287 strtok(buf_ptr
, " ");
290 /* Checking for mips architecture */
291 if (strncmp(name
.machine
, "mips", sizeof(name
.machine
)) != 0)
292 sprintf(pid_search_cmd
, "pidof -s %s", buf_ptr
);
294 sprintf(pid_search_cmd
, "pidof %s", buf_ptr
);
298 /* Execute the command e.g "pidof ping" */
299 if ((fpt
= popen(pid_search_cmd
, "r")) == NULL
) {
300 sprintf(buf_ptr
, "%s\n", "Can't return PID");
304 /* Get the PID and copy the PID in buf_ptr to send to the client */
305 fgets(pid_resp_buf
, sizeof(pid_resp_buf
), fpt
);
307 /* Checking for mips architecture */
308 if (strncmp(name
.machine
, "mips", sizeof(name
.machine
)) != 0) {
309 PID_val
= atoi(pid_resp_buf
);
312 /* code to extract the correct PID */
313 pid_token
= strtok_r(pid_resp_buf
, " ", (char **)next_pid
);
314 if (pid_token
!= NULL
) {
315 while (pid_token
!= NULL
) {
316 /* the pid buffer will terminate with a '\n'
317 * It will affect the string tokenizing logic
318 * To avoid this we're using the if case
320 if (strncmp(pid_token
, "\n", sizeof(pid_token
)) == 0)
322 PID_val
= atoi(pid_token
);
323 pid_token
= strtok_r(NULL
, " ", (char **)next_pid
);
327 PID_val
= atoi(pid_token
);
330 msg_len
= rwl_get_file_size(temp_async_file_name
);
331 remote_shell_async_get_resp(temp_async_file_name
, buf_ptr
, msg_len
);
334 g_async_resp
[val
].PID
= PID_val
;
335 /* Update PID value in buffer to send it to client */
336 sprintf(buf_ptr
, "%d", PID_val
);
337 msg_len
= strlen(buf_ptr
);
341 /* In async case, the PID value will be copied to the input buffer only
342 * and there is no need of getting the response from the file. So return
348 /* Process for 'kill' command.
349 * Kill command can also be used from the client to get the
350 * result of asynchronous command and actually kill the mentioned process
353 remote_kill_cmd_exec(char *cmd_buf_ptr
)
355 char file_name
[MAX_ASYNC_FILE_LENGTH
];
356 int PID_val
, val
, msg_len
;
358 char *pid_token
, next_pid
[PID_TOKEN_SIZE
][PID_TOKEN_SIZE
];
362 /* Parse the PID val from the kill command.
364 pid_token
= strtok_r(cmd_buf_ptr
, " ", (char **)next_pid
);
365 while (pid_token
!= NULL
) {
366 /* to extract the PID from the kill command */
367 if (strncmp(pid_token
, "\n", sizeof(pid_token
)) == 0)
369 PID_val
= atoi(pid_token
);
370 pid_token
= strtok_r(NULL
, " ", (char **)next_pid
);
373 /* Check for the matching PID from the async structure and
374 * give the last 256 bytes statistics of the async process
377 for (val
= 0; val
< MAX_SHELL_ASYNC_RESP
; ++val
) {
378 if (g_async_resp
[val
].PID
== PID_val
) {
379 /* We found a match here. Hence get the response now from the
380 * corresponding async response file
382 sprintf(file_name
, "%s%s", SHELL_RESP_PATH
, g_async_resp
[val
].file_name
);
383 msg_len
= rwl_get_file_size(file_name
);
385 if ((fpt
= fopen(file_name
, "rb")) == NULL
) {
386 DPRINT_DBG(OUTPUT
, "\nShell Cmd:File open error\n");
390 if (fseek(fpt
, 0, SEEK_SET
) < 0) {
395 if (fread(cmd_buf_ptr
, sizeof(char), MAX_SHELL_CMD_LENTH
,
397 sprintf(cmd_buf_ptr
, "%s\n", "Shell Resp:Reading error");
405 sprintf(cmd_buf_ptr
, "ed %d: No Response\n", PID_val
);
406 remove(g_async_resp
[val
].file_name
);
408 g_async_resp
[val
].PID
= 0;
412 return MAX_SHELL_CMD_LENTH
;
415 /* Handle --timeout command line option for linux servers */
417 shell_timeout_cmd(char *cmd_buf_ptr
, char *sync_file_name
)
419 char *token1
, *token2
, *nexttoken
;
423 token1
= strtok_r(cmd_buf_ptr
, "--timeout ", &nexttoken
);
425 token2
= strtok_r(NULL
, token1
, &nexttoken
);
426 if (token1
== NULL
|| atoi(token1
) <= 0 || token2
== NULL
) {
427 fp
= fopen(sync_file_name
, "w+");
428 fprintf(fp
, "Usage: ./wl --<transport> <ip/mac> sh"
429 "--timeout <timeout value> <shell command>\n");
430 fprintf(fp
, "Eg: ./wl --socket 172.22.65.226 sh --timeout 15 ls\n");
432 msg_len
= rwl_get_file_size(sync_file_name
);
433 strcpy(cmd_buf_ptr
, sync_file_name
);
435 strcpy(globalbuffer
, sync_file_name
);
436 printf("Fix timeout problem in socket!!!!!\n");
440 g_shellsync_timeout
= atoi(token1
);
444 /* Handle synchronous shell commands here */
446 remote_shell_sync_exec(char *cmd_buf_ptr
, void *wl
)
448 char *kill_cmd_token
;
449 char sync_file_name
[] = TEMPLATE
;
451 char cmd
[(strlen(cmd_buf_ptr
) + 1)];
452 int pid
, status
, pid_final
;
453 char buf
[SHELL_RESP_SIZE
], cmd_find_lastpid
[PID_SEARCH_CMD_SIZE
];
456 static int sent_once
= 0;
460 /* Default Size of Return Value of the shell command is 2bytes */
462 kill_cmd_token
= strstr(cmd_buf_ptr
, "kill");
464 /* Synchronous Kill command processing is handled separately */
465 if (kill_cmd_token
!= NULL
) {
466 msg_len
= remote_kill_cmd_exec(cmd_buf_ptr
);
467 remote_tx_response(wl
, cmd_buf_ptr
, msg_len
);
472 /* Process synchronous command other than kill command */
473 if ((fd
= mkstemp(sync_file_name
)) < 0) {
474 perror("mkstemp failed");
475 DPRINT_ERR(ERR
, "\n errno:%d\n", errno
);
476 sprintf(cmd_buf_ptr
, "%s\n", "mkstemp failed");
482 strcpy(cmd
, cmd_buf_ptr
);
483 /* Synchronous timeout command processing is handled separately */
484 if (strstr(cmd_buf_ptr
, "--timeout") != NULL
) {
485 if ((msg_len
= shell_timeout_cmd (cmd
, sync_file_name
) > 0)) {
486 /* Signal end of command output */
487 g_rem_ptr
->msg
.len
= 0;
488 g_rem_ptr
->msg
.cmd
= g_return_stat
;
489 remote_tx_response(wl
, NULL
, 0);
492 /* Parse out --timeout <val> since command is successful
493 * point buffer to the shell command
495 strcpy(cmd
, cmd_buf_ptr
);
496 strtok_r(cmd
, " ", &cmd_buf_ptr
);
497 strcpy(cmd
, cmd_buf_ptr
);
498 strtok_r(cmd
, " ", &cmd_buf_ptr
);
502 /* Schedule an ALARM in case of timeout value of SHELL_TIMEOUT seconds */
503 /* Defalut time out only in case of Non socket transport */
504 alarm(g_shellsync_timeout
);
505 /* registering the relevant signals to handle end of child process,
506 * the ctrl+c event on the server side and the kill command on the
509 signal(SIGCHLD
, rwl_chld_handler
);
510 signal(SIGINT
, handle_ctrlc
);
511 signal(SIGTERM
, handle_ctrlc
);
513 /* Set g_sig_chld before forking */
516 if (strcmp("reboot", cmd_buf_ptr
) == 0) { /* reboot command */
517 memset(buf
, 0, sizeof(buf
));
518 strncpy(buf
, REBOOT_MSG
, sizeof(REBOOT_MSG
));
519 remote_tx_response(wl
, buf
, 0);
521 /* Signal end of command output */
522 g_rem_ptr
->msg
.len
= 0;
523 g_rem_ptr
->msg
.cmd
= 0;
524 remote_tx_response(wl
, NULL
, 0);
527 /* Clean up the temp file */
528 remove(sync_file_name
);
531 if ((pid
= fork()) == 0) {
532 close(STDOUT_FILENO
);
533 fd
= open(sync_file_name
, O_WRONLY
|O_SYNC
);
534 /* Redirect stdin to dev/null. This handles un usual commands like
535 * sh cat from the client side
538 open("/dev/null", O_RDONLY
);
539 close(STDERR_FILENO
);
540 fcntl(fd
, F_DUPFD
, STDERR_FILENO
);
541 if ((status
= execl(SH_PATH
, "sh", "-c", cmd_buf_ptr
, NULL
)) == -1) {
542 perror("Exec error");
547 g_shellsync_pid
= pid
;
548 /* The g_return_stat is being set for short commands */
549 waitpid(g_shellsync_pid
, &child_status
, WNOHANG
);
550 if (WIFEXITED(child_status
))
551 g_return_stat
= WEXITSTATUS(child_status
);
555 /* Read file in the interim from a temp file and send back the results */
556 fd
= open(sync_file_name
, O_RDONLY
|O_SYNC
);
559 /* read file in the interim and send back the results */
560 nbytes
= read(fd
, buf
, SHELL_RESP_SIZE
);
561 g_rem_ptr
->msg
.len
= nbytes
;
563 remote_tx_response(wl
, buf
, 0);
565 /* usleep introduced for flooding of data over serial port */
569 if (get_ctrlc_header(wl
) >= 0) {
570 if (g_rem_ptr
->msg
.flags
== (unsigned)CTRLC_FLAG
) {
572 /* Checking for mips architecture
573 * The mips machine responds differently to
574 * execl command. so the pid is incremented
575 * to kill the right command.
577 if (strncmp(name
.machine
, "mips", sizeof(name
.machine
)) == 0)
579 if (strncmp(name
.machine
, "armv5tel", sizeof(name
.machine
)) == 0) {
580 snprintf(cmd_find_lastpid
, sizeof(cmd_find_lastpid
),
581 "ps | awk \'PRINT $1\' | tail -n 1");
582 if ((fpt
= popen(cmd_find_lastpid
, "r")) == NULL
) {
583 sprintf(buf
, "%s\n", "Can't return PID");
586 fgets(cmd_find_lastpid
, sizeof(cmd_find_lastpid
), fpt
);
587 pid_final
= atoi(cmd_find_lastpid
);
588 while (pid
<= pid_final
) {
600 if (get_ctrlc_header(wl
) >= 0) {
601 if (g_rem_ptr
->msg
.flags
== (unsigned)CTRLC_FLAG
) {
603 /* Checking for mips architecture
604 * The mips machine responds differently to
605 * execl command. so the pid is incremented
606 * to kill the right command.
608 if (strncmp(name
.machine
, "mips", sizeof(name
.machine
)) == 0) {
612 /* Checking for arm architecture
613 * The multiple commands would not work
614 * for ctrl+C. So we kill the processes
615 * spawned after the parent. This method has
616 * its own limitations but the busybox in pxa
617 * doesnot have many options to implement it better
620 if (strncmp(name
.machine
, "armv5tel",
621 sizeof(name
.machine
)) == 0) {
622 /* The command below is used to get the
623 * PIDs and they are killed
625 snprintf(cmd_find_lastpid
,
626 sizeof(cmd_find_lastpid
),
627 "ps | awk \'PRINT $1\' | tail -n 1");
628 if ((fpt
= popen(cmd_find_lastpid
, "r")) == NULL
) {
629 sprintf(buf
, "%s\n", "Can't return PID");
632 fgets(cmd_find_lastpid
, sizeof(cmd_find_lastpid
),
634 pid_final
= atoi(cmd_find_lastpid
);
635 while (pid
<= pid_final
) {
641 /* In the case of x86, on receiving ctrl+C
642 * the child PIDs are obtained by searching
643 * the parent PID to obtain the PIDs of the
648 /* The commad below is used to get the
649 * child PIDs by using their parent PID
651 snprintf(cmd_find_lastpid
,
652 sizeof(cmd_find_lastpid
),
653 "ps al | awk \"{ if (\\$4 == %d)"
654 " {print \\$3}}\"| head -n 1",
656 if ((fpt
= popen(cmd_find_lastpid
, "r"))
662 fgets(cmd_find_lastpid
,
663 sizeof(cmd_find_lastpid
),
665 pid
= atoi(cmd_find_lastpid
);
667 kill(g_shellsync_pid
, SIGKILL
);
677 if (set_ctrlc
== 1) {
678 g_rem_ptr
->msg
.len
= 0;
679 g_rem_ptr
->msg
.cmd
= g_return_stat
;
680 remote_tx_response(wl
, NULL
, g_return_stat
);
681 unlink(sync_file_name
);
684 /* It is possible that the child would have exited
685 * However we did not get a chance to read the file
686 * In this case go once again and check the file
688 if (!sent_once
&& !g_sig_chld
) {
693 if (!(g_sig_chld
|| nbytes
))
699 /* Signal end of command output */
700 g_rem_ptr
->msg
.len
= 0;
701 g_rem_ptr
->msg
.cmd
= g_return_stat
;
703 remote_tx_response(wl
, NULL
, g_return_stat
);
704 /* Cancel the time out alarm if any */
707 /* Clean up the temp file */
708 unlink(sync_file_name
);
709 g_shellsync_timeout
= DEFAULT_SHELL_TIMEOUT
;
710 signal(SIGINT
, SIG_DFL
);
711 signal(SIGTERM
, SIG_DFL
);