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