27.4 The Source Files
27.4.2 The Shell Functions
A key design decision for devpkg is to do most of the work using external tools like curl, tar, and git. We could find libraries to do all of this internally, but it’s pointless if we just need the base features of these programs. There is no shame in running another command in Unix.
To do this I’m going to use the apr_thread_proc.h functions to run programs, but I also want to make a simple kind of "template" system. I’ll use a struct Shell that holds all the information needed to run a program, but has "holes" in the arguments list where I can replace them with values.
Look at the shell.h file to see the structure and the commands I’ll use. You can see I’m using extern to indicate that other .c files can access variables I’m defining in shell.c.
shell.h
1 #ifndef _shell_h 2 #define _shell_h 3 4 #define MAX_COMMAND_ARGS 100 5 6 #include <apr_thread_proc.h> 78 typedef struct Shell { 9 const char *dir; 10 const char *exe; 11
27.4. THE SOURCE FILES 141 12 apr_procattr_t *attr; 13 apr_proc_t proc; 14 apr_exit_why_e exit_why; 15 int exit_code; 16
17 const char *args[MAX_COMMAND_ARGS]; 18 } Shell;
19
20 int Shell_run(apr_pool_t *p, Shell *cmd); 21 int Shell_exec(Shell cmd, ...);
22
23 extern Shell CLEANUP_SH; 24 extern Shell GIT_SH; 25 extern Shell TAR_SH; 26 extern Shell CURL_SH; 27 extern Shell CONFIGURE_SH; 28 extern Shell MAKE_SH; 29 extern Shell INSTALL_SH; 30
31 #endif
Make sure you’ve created shell.h exactly, and that you’ve got the same names and number of extern Shell variables. Those are used by the Shell_run and Shell_exec functions to run commands. I define these two functions, and create the real variables in shell.c.
shell.c
1 #include "shell.h" 2 #include "dbg.h" 3 #include <stdarg.h> 4
5 int Shell_exec(Shell template, ...) 6 {
7 apr_pool_t *p = NULL; 8 int rc = -1;
9 apr_status_t rv = APR_SUCCESS; 10 va_list argp;
11 const char *key = NULL; 12 const char *arg = NULL; 13 int i = 0;
14
15 rv = apr_pool_create(&p, NULL);
16 check(rv == APR_SUCCESS, "Failed to create pool."); 17
18 va_start(argp, template); 19
20 for(key = va_arg(argp, const char *); 21 key != NULL;
22 key = va_arg(argp, const char *)) 23 {
24 arg = va_arg(argp, const char *); 25
26 for(i = 0; template.args[i] != NULL; i++) { 27 if(strcmp(template.args[i], key) == 0) {
142 CHAPTER 27. EXERCISE 26: WRITE A FIRST REAL PROGRAM 29 break; // found it 30 } 31 } 32 } 33 34 rc = Shell_run(p, &template); 35 apr_pool_destroy(p); 36 va_end(argp); 37 return rc; 38 error: 39 if(p) { 40 apr_pool_destroy(p); 41 } 42 return rc; 43 } 44
45 int Shell_run(apr_pool_t *p, Shell *cmd) 46 { 47 apr_procattr_t *attr; 48 apr_status_t rv; 49 apr_proc_t newproc; 50 51 rv = apr_procattr_create(&attr, p);
52 check(rv == APR_SUCCESS, "Failed to create proc attr."); 53
54 rv = apr_procattr_io_set(attr, APR_NO_PIPE, APR_NO_PIPE,
55 APR_NO_PIPE);
56 check(rv == APR_SUCCESS, "Failed to set IO of command."); 57
58 rv = apr_procattr_dir_set(attr, cmd->dir);
59 check(rv == APR_SUCCESS, "Failed to set root to %s", cmd->dir); 60
61 rv = apr_procattr_cmdtype_set(attr, APR_PROGRAM_PATH); 62 check(rv == APR_SUCCESS, "Failed to set cmd type."); 63
64 rv = apr_proc_create(&newproc, cmd->exe, cmd->args, NULL, attr, p); 65 check(rv == APR_SUCCESS, "Failed to run command.");
66
67 rv = apr_proc_wait(&newproc, &cmd->exit_code, &cmd->exit_why, APR_WAIT); 68 check(rv == APR_CHILD_DONE, "Failed to wait.");
69
70 check(cmd->exit_code == 0, "%s exited badly.", cmd->exe);
71 check(cmd->exit_why == APR_PROC_EXIT, "%s was killed or crashed", cmd->exe); 72 73 return 0; 74 75 error: 76 return -1; 77 } 78 79 Shell CLEANUP_SH = { 80 .exe = "rm", 81 .dir = "/tmp",
82 .args = {"rm", "-rf", "/tmp/pkg-build", "/tmp/pkg-src.tar.gz", 83 "/tmp/pkg-src.tar.bz2", "/tmp/DEPENDS", NULL}
27.4. THE SOURCE FILES 143
85
86 Shell GIT_SH = { 87 .dir = "/tmp", 88 .exe = "git",
89 .args = {"git", "clone", "URL", "pkg-build", NULL} 90 };
91
92 Shell TAR_SH = {
93 .dir = "/tmp/pkg-build", 94 .exe = "tar",
95 .args = {"tar", "-xzf", "FILE", "--strip-components", "1", NULL} 96 };
97
98 Shell CURL_SH = { 99 .dir = "/tmp", 100 .exe = "curl",
101 .args = {"curl", "-L", "-o", "TARGET", "URL", NULL} 102 };
103
104 Shell CONFIGURE_SH = { 105 .exe = "./configure", 106 .dir = "/tmp/pkg-build",
107 .args = {"configure", "OPTS", NULL}, 108 };
109
110 Shell MAKE_SH = { 111 .exe = "make",
112 .dir = "/tmp/pkg-build",
113 .args = {"make", "OPTS", NULL} 114 };
115
116 Shell INSTALL_SH = { 117 .exe = "sudo",
118 .dir = "/tmp/pkg-build",
119 .args = {"sudo", "make", "TARGET", NULL} 120 };
Read the shell.c from the bottom to the top (which is a common C source layout) and you see I’ve created the actual Shell variables that you indicated were extern in shell.h. They live here, but are available to the rest of the program. This is how you make global variables that live in one .o file but are used everywhere. You shouldn’t make many of these, but they are handy for things like this.
Continuing up the file we get to the Shell_run function, which is a "base" function that just runs a command based on what’s in a Shell struct. It uses many of the functions defined in apr_thread_proc.h so go look up each one to see how it works. This seems like a lot of work compared to just using the system function call, but this also gives you more control over the other program’s execution. For example, in our Shell struct we have a .dir attribute which forces the program to be in a specific directory before running.
Finally, I have the Shell_exec function, which is a "variable arguments" function. You’ve seen this before, but make sure you grasp the stdarg.h functions and how to write one of these. In the challenge for this section you are going to analyze this function.
Challenge 2: Analyze Shell_exec
Challenge for these files (in addition to a full code review just like you did in Challenge 1) is to fully analyze Shell_exec and break down exactly how it works. You should be able to understand each line, how the two for-loops work, and how arguments are being replaced.
144 CHAPTER 27. EXERCISE 26: WRITE A FIRST REAL PROGRAM
Once you have it analyzed, add a field to struct Shell that gives the number of variable args that must be replaced. Update all the commands to have the right count of args, and then have an error check that confirms these args have been replaced and error exit.