]> diplodocus.org Git - nmh/blob - sbr/arglist.c
0dde2c86d06d52edf067499b60be5ff8e087c9ab
[nmh] / sbr / arglist.c
1 /* arglist.c -- Routines for handling argument lists for execvp() and friends
2 *
3 * This code is Copyright (c) 2013, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include "h/mh.h"
9 #include "error.h"
10 #include "arglist.h"
11 #include "h/utils.h"
12
13 /*
14 * Split up a command into an appropriate array to pass to execvp()
15 * or similar function. Returns an allocated argv[] array.
16 *
17 * Function arguments:
18 *
19 * command - String to split up
20 * file - the first argument to "command", suitable for the first argument
21 * to execvp(). Returns allocated memory that must be free()d.
22 * argp - Index to last element (NULL) of returned argv[] array.
23 *
24 * Our basic algorithm is this:
25 *
26 * - If there are no spaces or shell metacharacters in "command", then
27 * take it as-is.
28 * - If there are spaces in command, space-split command string.
29 * - If we have shell metacharacters, run the command using
30 * /bin/sh -c 'command "$@"'. In this case, any additional arguments
31 * appended to the arglist will be expanded by "$@".
32 *
33 * In all cases additional arguments can be added to the argv[] array.
34 */
35
36 /* Shell metacharacters we use to trigger a call to the shell */
37
38 #define METACHARS "$&*(){}[]'\";\\|?<>~`\n"
39
40 char **
41 argsplit(char *command, char **file, int *argp)
42 {
43 char **argvarray, *p;
44 int i;
45
46 bool space = false;
47 bool metachar = false;
48 for (p = command; *p; p++) {
49 if (*p == ' ' || *p == '\t') {
50 space = true;
51 } else if (strchr(METACHARS, *p)) {
52 metachar = true;
53 break;
54 }
55 }
56
57 argvarray = mh_xmalloc(sizeof *argvarray * (MAXARGS + 5));
58
59 /*
60 * The simple case - no spaces or shell metacharacters
61 */
62
63 if (!space && !metachar) {
64 argvarray[0] = mh_xstrdup(r1bindex(command, '/'));
65 argvarray[1] = NULL;
66 *file = mh_xstrdup(command);
67 if (argp)
68 *argp = 1;
69 return argvarray;
70 }
71
72 /*
73 * Spaces, but no shell metacharacters; space-split into separate
74 * arguments
75 */
76
77 if (space && !metachar) {
78 char **split;
79 p = mh_xstrdup(command);
80 split = brkstring(p, " \t", NULL);
81 if (split[0] == NULL) {
82 die("Invalid blank command found");
83 }
84 argvarray[0] = mh_xstrdup(r1bindex(split[0], '/'));
85 for (i = 1; split[i] != NULL; i++) {
86 if (i > MAXARGS) {
87 die("Command exceeded argument limit");
88 }
89 argvarray[i] = mh_xstrdup(split[i]);
90 }
91 argvarray[i] = NULL;
92 *file = mh_xstrdup(split[0]);
93 if (argp)
94 *argp = i;
95 free(p);
96 return argvarray;
97 }
98
99 /*
100 * Remaining option - pass to the shell.
101 *
102 * Some notes here:
103 *
104 * - The command passed to "sh -c" is actually:
105 * command "$@"
106 *
107 * If there are additional arguments they will be expanded by the
108 * shell, otherwise "$@" expands to nothing.
109 *
110 * - Every argument after the -c string gets put into positional
111 * parameters starting at $0, but $@ starts expanding with $1.
112 * So we put in a dummy argument (we just use /bin/sh)
113 */
114
115 *file = mh_xstrdup("/bin/sh");
116 argvarray[0] = mh_xstrdup("sh");
117 argvarray[1] = mh_xstrdup("-c");
118 argvarray[2] = mh_xstrdup(command);
119 argvarray[2] = add(" \"$@\"", argvarray[2]);
120 argvarray[3] = mh_xstrdup("/bin/sh");
121 argvarray[4] = NULL;
122
123 if (argp)
124 *argp = 4;
125
126 return argvarray;
127 }
128
129 /*
130 * Free our argument array
131 */
132
133 void
134 arglist_free(char *command, char **argvarray)
135 {
136 int i;
137
138 free(command);
139
140 if (argvarray != NULL) {
141 for (i = 0; argvarray[i] != NULL; i++)
142 free(argvarray[i]);
143 free(argvarray);
144 }
145 }
146
147 /*
148 * Similar in functionality to argsplit, but is designed to deal with
149 * a msgs_array.
150 */
151
152 void
153 argsplit_msgarg(struct msgs_array *msgs, char *command, char **program)
154 {
155 int argp, i;
156 char **vec;
157
158 vec = argsplit(command, program, &argp);
159
160 /*
161 * As usual, there is lousy memory management in nmh. Nothing ever
162 * free's the msgs_array, and a lot of the arguments are allocated
163 * from static memory. I could have app_msgarg() allocate new
164 * memory for each pointer, but for now I decided to stick with
165 * the existing interface; maybe that will be revisited later.
166 * So we'll just copy over our pointers and free the pointer list
167 * (not the actual pointers themselves). Note that we don't
168 * include a trailing NULL, since we are expecting the application
169 * to take care of that.
170 */
171
172 for (i = 0; i < argp; i++) {
173 app_msgarg(msgs, vec[i]);
174 }
175
176 free(vec);
177 }
178
179 /*
180 * Insert a arglist vector into the beginning of an struct msgs array
181 *
182 * Uses by some programs (e.g., show) who want to decide which proc
183 * to use after the argument vector has been constructed
184 */
185
186 #ifndef MAXMSGS
187 #define MAXMSGS 256
188 #endif
189
190 void
191 argsplit_insert(struct msgs_array *msgs, char *command, char **program)
192 {
193 int argp, i;
194 char **vec;
195
196 vec = argsplit(command, program, &argp);
197
198 /*
199 * Okay, we want to shuffle all of our arguments down so we have room
200 * for argp number of arguments. This means we need to know about
201 * msgs_array internals. If that changes, we need to change this
202 * code here.
203 */
204
205 if (msgs->size + argp >= msgs->max) {
206 msgs->max += max(MAXMSGS, argp);
207 msgs->msgs = mh_xrealloc(msgs->msgs, msgs->max * sizeof(*msgs->msgs));
208 }
209
210 for (i = msgs->size - 1; i >= 0; i--)
211 msgs->msgs[i + argp] = msgs->msgs[i];
212
213 msgs->size += argp;
214
215 /*
216 * Now fill in the arguments at the beginning of the vector.
217 */
218
219 for (i = 0; i < argp; i++)
220 msgs->msgs[i] = vec[i];
221
222 free(vec);
223 }