C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
exec.c
Go to the documentation of this file.
1/** @file exec.c
2 @brief Functions to execute external commands
3 @author Bill Waller
4 Copyright (c) 2025
5 MIT License
6 billxwaller@gmail.com
7 @date 2026-02-09
8 */
9
10/** @defgroup exec External Commands
11 @brief This module provides functions to execute external commands
12 @details Handles terminal settings, signal handling, and error reporting to
13 ensure a smooth user experience when executing commands from within the
14 application. The main functions include full_screen_fork_exec,
15 full_screen_shell, and fork_exec, which manage the execution of commands
16 while maintaining the integrity of the application's user interface.
17 */
18
19#include <cm.h>
20#include <errno.h>
21#include <fcntl.h>
22#include <stddef.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/stat.h>
26#include <sys/types.h>
27#include <sys/wait.h>
28#include <termios.h>
29#include <unistd.h>
30
31int full_screen_fork_exec(char **);
32int full_screen_shell(char *);
33int shell(char *);
34int fork_exec(char **);
35int nf_error(int ec, char *s);
36int dmon(char **);
37/** @brief Execute a command in full screen mode
38 @ingroup exec
39 @param argv - array of arguments for the command to execute
40 @return the return code from the executed command
41 @details Clear the screen,
42 move the cursor to the bottom, and refresh the screen before executing
43 the command.
44 After the command completes, clear the screen, move the cursor to the
45 top, refresh the screen, and restore the windows. */
46int full_screen_fork_exec(char **argv) {
47 int rc;
48
49 fflush(stderr);
50 wmove(stdscr, LINES - 1, 0);
51 rc = fork_exec(argv);
52 return (rc);
53}
54/** @brief Execute a shell command in full screen mode
55 @ingroup exec
56 @param shellCmdPtr - pointer to the shell command string
57 @return the return code from the executed shell command
58 @details Clear the screen, move the cursor to the top, and refresh the
59 screen before executing the shell command. After the command completes,
60 restore the windows.
61 */
62int full_screen_shell(char *shellCmdPtr) {
63 int rc;
64
65 fflush(stderr);
66 werase(stdscr);
67 wmove(stdscr, 0, 0);
68 wrefresh(stdscr);
69 rc = shell(shellCmdPtr);
70 touchwin(stdscr);
71 wnoutrefresh(stdscr);
73 wrefresh(stdscr);
74 return (rc);
75}
76/** @brief Execute a shell command
77 @ingroup exec
78 @param shellCmdPtr - pointer to the shell command string
79 @return the return code from the executed shell command
80 @details Executes the command string using the user's shell.
81 If the SHELL environment variable is not set, use /bin/sh. */
82int shell(char *shellCmdPtr) {
83 int Eargc;
84 char *Eargv[MAXARGS];
85 char *shellPtr;
86 int rc;
87
88 Eargc = 0;
89 shellPtr = getenv("SHELL");
90 if (shellPtr == nullptr || *shellPtr == '\0')
91 shellPtr = DEFAULTSHELL;
92 Eargv[Eargc++] = strdup(shellPtr);
93 Eargv[Eargc++] = "-c";
94 Eargv[Eargc++] = shellCmdPtr;
95 Eargv[Eargc++] = nullptr;
96 rc = fork_exec(Eargv);
97 free(Eargv[0]);
98 return (rc);
99}
100/** @brief Fork and exec a command
101 @ingroup exec
102 @param argv - array of arguments for the command to execute
103 @return the return code from the executed command, or -1 on error
104 @details Captures and restores terminal settings around the fork and exec.
105 Sets signal handlers to default in the child process.
106 Waits for the child process to complete in the parent process.
107 Handles errors from fork and execvp, and reports child exit status.
108 Restores curses mode and keypad settings after execution.
109 Restores window states after execution.
110 Uses a temporary string buffer tmp_str for error messages.
111 Uses Perror for error reporting.
112 Uses sig_dfl_mode and sig_prog_mode for signal handling.
113 Uses capture_curses_tioctl and restore_curses_tioctl for terminal
114 settings.
115 Uses restore_shell_tioctl for shell terminal settings.
116 Uses waitpid to wait for the child process.
117 Uses WIFEXITED, WEXITSTATUS, WIFSIGNALED, and WTERMSIG to interpret
118 child status.
119 Uses keypad to manage keypad mode in curses.
120 Uses restore_wins to restore window states.
121 Uses errno for error codes.
122 Uses pid_t for process IDs.
123 Uses standard file descriptors STDIN_FILENO, STDOUT_FILENO,
124 STDERR_FILENO.
125 Uses execvp for executing the command.
126 Uses fork for creating a new process.
127 Uses ssnprintf for formatting error messages.
128 Uses switch-case for handling fork results.
129 Uses default shell if SHELL environment variable is not set. */
130int fork_exec(char **argv) {
131 char tmp_str[MAXLEN];
132 pid_t pid;
133 int status;
134 int rc;
135
136 if (argv[0] == 0) {
137 Perror("fork_exec: missing argument for execvp");
138 return (-1);
139 }
141 curs_set(1);
143
144 tmp_str[0] = '\0';
145 pid = fork();
146 switch (pid) {
147 case -1: // parent fork failed
149 keypad(stdscr, true);
150 ssnprintf(tmp_str, sizeof(tmp_str), "fork failed: %s, errno: %d",
151 argv[0], errno);
152 Perror(tmp_str);
153 return (-1);
154 case 0: // child
155 /** Prevent child process from writing to terminal */
156 // if (flags & F_NO_STDERR) {
157 // int dev_null = open("/dev/null", O_WRONLY);
158 // if (dev_null == -1) {
159 // Perror("open(/dev/null) failed in init_pick child process");
160 // exit(EXIT_FAILURE);
161 // }
162 // dup2(dev_null, STDOUT_FILENO);
163 // dup2(dev_null, STDERR_FILENO);
164 // close(dev_null);
165 // }
167 werase(stdscr);
168 wrefresh(stdscr);
169 execvp(argv[0], argv);
172 keypad(stdscr, true);
173 ssnprintf(tmp_str, sizeof(tmp_str), "execvp failed: %s, errno: %d",
174 argv[0], errno);
175 Perror(tmp_str);
176 exit(-1);
177 default: // parent
178 rc = 0;
181 waitpid(pid, &status, 0);
182 if (WIFEXITED(status)) {
183 rc = WEXITSTATUS(status);
184 if (rc != 0) {
185 keypad(stdscr, true);
186 ssnprintf(tmp_str, sizeof(tmp_str),
187 "Child %s exited with status %d", argv[0], rc);
188 }
189 } else {
190 if (WIFSIGNALED(status)) {
191 rc = WTERMSIG(status);
192 keypad(stdscr, true);
193 ssnprintf(tmp_str, sizeof(tmp_str),
194 "Child %s terminated by signal %d", argv[0], rc);
195 } else {
196 keypad(stdscr, true);
197 ssnprintf(tmp_str, sizeof(tmp_str),
198 "Child %s terminated abnormally", argv[0]);
199 }
200 }
201 break;
202 }
205 touchwin(stdscr);
206 wnoutrefresh(stdscr);
207 wrefresh(stdscr);
209 tmp_str[0] = '\0';
210 if (tmp_str[0] != '\0') {
211 Perror(tmp_str);
212 }
213 return (rc);
214}
215/** @brief Fork and execute a command as a daemon
216 @ingroup exec
217 @param eargv - array of arguments for the command to execute
218 @return EXIT_SUCCESS on success, EXIT_FAILURE on failure
219 @details Forks the process twice to create a daemon.
220 Sets the session ID and working directory.
221 Redirects standard file descriptors to /dev/null.
222 Closes all open file descriptors.
223 Executes the command using execvp.
224 Exits with failure if any step fails.
225 @note Don't use this code. It is not finished. */
226int dmon(char **eargv) {
227
228 pid_t pid = fork();
230 curs_set(1);
232
233 if (pid < 0) {
234 fprintf(stderr, "Failed to fork: %s\n", strerror(errno));
235 return EXIT_FAILURE;
236 }
237 if (pid > 0) {
240 touchwin(stdscr);
241 wnoutrefresh(stdscr);
242 wrefresh(stdscr);
244 return 0;
245 }
246 if (setsid() < 0) {
247 fprintf(stderr, "Failed to set session ID: %s\n", strerror(errno));
248 exit(EXIT_FAILURE);
249 }
250 pid = fork();
251 if (pid < 0) {
252 fprintf(stderr, "Second fork failed: %s\n", strerror(errno));
253 exit(EXIT_FAILURE);
254 }
255 if (pid > 0) {
256 exit(EXIT_SUCCESS);
257 }
258 char *e = getenv("HOME");
259 if (e != NULL) {
260 if (chdir(e) < 0)
261 exit(EXIT_FAILURE);
262 }
263 umask(0);
264 close(STDIN_FILENO);
265 close(STDOUT_FILENO);
266 close(STDERR_FILENO);
267 int dev_null = open("/dev/null", O_RDWR);
268 if (dev_null != -1) {
269 dup2(dev_null, STDIN_FILENO);
270 dup2(dev_null, STDOUT_FILENO);
271 dup2(dev_null, STDERR_FILENO);
272 if (dev_null > 2) {
273 close(dev_null);
274 }
275 }
276 long max_fd = sysconf(_SC_OPEN_MAX);
277 for (long fd = 3; fd < max_fd; fd++) {
278 close(fd);
279 }
280 execvp(eargv[0], eargv);
281 exit(EXIT_FAILURE);
282}
#define DEFAULTSHELL
Definition cm.h:217
#define MAXARGS
Definition cm.h:30
#define nullptr
Definition cm.h:25
#define MAXLEN
Definition curskeys.c:15
void restore_wins()
Restore all windows after a screen resize.
Definition dwin.c:933
int nf_error(int, char *)
Display error message and wait for key press.
Definition dwin.c:1576
int Perror(char *)
Display a simple error message window or print to stderr.
Definition dwin.c:1162
int fork_exec(char **)
Fork and exec a command.
Definition exec.c:130
int shell(char *)
Execute a shell command.
Definition exec.c:82
int dmon(char **)
Fork and execute a command as a daemon.
Definition exec.c:226
int full_screen_fork_exec(char **)
Execute a command in full screen mode.
Definition exec.c:46
int full_screen_shell(char *)
Execute a shell command in full screen mode.
Definition exec.c:62
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
Definition futil.c:311
bool restore_curses_tioctl()
restore_curses_tioctl() - restore curses terminal settings
Definition scriou.c:81
bool capture_curses_tioctl()
capture_curses_tioctl() - capture curses terminal settings
Definition scriou.c:68
bool restore_shell_tioctl()
restore_shell_tioctl() - restore shell terminal settings
Definition scriou.c:56
void sig_dfl_mode()
Set signal handlers to default behavior.
Definition sig.c:42
void sig_prog_mode()
Set up signal handlers for interrupt signals.
Definition sig.c:62