Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / lib / DtSvc / DtCodelibs / shellscan.C
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /*
24  * $TOG: shellscan.C /main/9 1999/10/14 15:05:42 mgreess $
25  *
26  * (c) Copyright 1996 Digital Equipment Corporation.
27  * (c) Copyright 1993,1994,1996 Hewlett-Packard Company.
28  * (c) Copyright 1993,1994,1996 International Business Machines Corp.
29  * (c) Copyright 1993,1994,1996 Sun Microsystems, Inc.
30  * (c) Copyright 1993,1994,1996 Novell, Inc. 
31  * (c) Copyright 1996 FUJITSU LIMITED.
32  * (c) Copyright 1996 Hitachi.
33  */
34 # if defined(apollo) && !defined(___GID_T)
35 // This kludge is needed for the include conflicts mentioned below
36 // Remove when no longer necessary
37 #  define _NEED___PID_T
38 # endif
39
40 #ifdef DOMAIN_ALLOW_MALLOC_OVERRIDE
41 #include "/usr/include/apollo/shlib.h"
42 #endif
43
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #define X_INCLUDE_PWD_H
48 #define XOS_USE_XT_LOCKING
49 #include <X11/Xos_r.h>
50 #include <codelibs/nl_hack.h>
51
52 #ifdef apollo
53 // This kludge because of include conflicts between stdlib.h and unistd.h
54 // Remove when problem is fixed
55
56 # ifdef __cplusplus
57     extern "C" {
58 # endif
59     _DECL_FUNC(__pid_t,    getpid,   (void))
60 # ifdef __cplusplus
61     }
62 # endif
63 #else /* not apollo */
64 # include <unistd.h>
65 #endif /* not apollo */
66
67 #include "stringio.h"
68 #include "buf.h"
69 #include <codelibs/shellutils.h>
70
71 #include <codelibs/boolean.h>
72 #include <codelibs/stringx.h>
73 #include "DtSvcLock.h"
74
75 #ifdef XTHREADS
76 extern "C" {
77     extern void XtProcessLock(void);
78     extern void XtProcessUnlock(void);
79 }
80 #endif
81
82 #define ISIDENT(CH) (isalnum(CH) || (CH) == '_')
83
84 static _SHXbuf *buf = NULL;
85 static const char *getvar(const char *var, char *);
86
87 // Make this a global someday:
88 static const char *(*shellvarfn)(const char *, char *) = getvar;
89
90 // Parse a sequence of the ksh meta-characters ;&|<> and whitespace
91 // into a single ksh token.  Return a pointer to the token as a
92 // string.  All whitespace characters are mapped to a single space
93 // character.  If ch is not a meta-character, return a NULL pointer.
94 static char *
95 parsemeta(int ch, _StringIO &in, char *ifs, unsigned opts, char *meta)
96 {
97     if (ch == '\0')
98         return " ";             // whitespace
99
100     _DtSvcProcessLock();
101     if (buf->quote() != NOQUOTE) {
102         _DtSvcProcessUnlock();
103         return NULL;            // normal character
104     }
105
106     if (!(opts & SHX_NOSPACE) && strchr(ifs, ch) != NULL) {
107         _DtSvcProcessUnlock();
108         return " ";             // whitespace
109     }
110
111     if (!(opts & SHX_NOMETA))
112     {
113         int len = 0;
114
115         if (buf->new_token() && isascii(ch) && isdigit(ch))
116             if (in.next() == '<' || in.next() == '>')
117             {
118                 meta[len++] = (char)ch;
119                 ch = in.get();
120             }
121
122         switch (meta[len++] = (char)ch, ch)
123         {
124         case ';': 
125         case '&': 
126         case '(': 
127         case ')': 
128             meta[len++] = (in.next() == ch) ? in.get() : '\0';
129             meta[len] = '\0';
130             _DtSvcProcessUnlock();
131             return meta;
132         case '|': 
133             meta[len++] = (in.next() == '|' || in.next() == '&') ?
134                 in.get() : '\0';
135             meta[len] = '\0';
136             _DtSvcProcessUnlock();
137             return meta;
138         case '>': 
139         case '<': 
140                 if (in.next() == ch || in.next() == '&')
141                     meta[len++] = (char)in.get();
142                 meta[len] = '\0';
143             _DtSvcProcessUnlock();
144             return meta;
145         }
146     }
147
148     _DtSvcProcessUnlock();
149     return NULL; // normal character
150 }
151
152 // Takes the name of a variable, and looks up it's value.  Someday,
153 // this will be replaceable by the user.
154 static const char *
155 getvar(const char *name, char *buff)
156 {
157     if (name[0] != '\0' && name[1] == '\0')
158         switch (name[0])
159         {
160         case '$': 
161             sprintf(buff, "%d", getpid());
162             return buff;
163         case '#':
164         case '?':
165             return "0";
166         }
167
168     return getenv(name);
169 }
170
171 // Parse an environment variable name from the _StringIO stream,
172 // and push its value into the _StringIO stream stack.
173 static boolean
174 pushvar(_StringIO &in, char *buff)
175 {
176     _StringIO tmp;
177     privbuf_charbuf name;
178
179     tmp = in;
180     int ch = tmp.get();         // get the first character after the $
181
182     if (!isascii(ch))
183         return FALSE;
184
185     if (ch == '{')
186         while ((ch = tmp.get()) != '\0')
187         {
188             // ${foo!bar} form, grab everything inside {} as name
189             if (ch == '\\')     // Only \ does quoting inside ${}
190                     ch = tmp.get();
191             else if (ch == '}')
192                 break;
193             name.end() = ch;
194         }
195     else if (ispunct(ch))
196         switch (ch)             // Special non-alnum shell variables
197         {
198         case '#': 
199         case '?': 
200         case '$': 
201         case '!': 
202         case '-': 
203         case '*': 
204         case '@': 
205         case '_':
206             name.end() = ch;
207             break;
208         default: 
209             return FALSE;
210         }
211     else if (isdigit(ch))
212         name.end() = ch;        // single-digit variables
213     else if (ISIDENT(ch))
214     {
215         // normal variable
216         do
217             name.end() = ch;
218         while (isascii(ch = tmp.get()) && ISIDENT(ch));
219         tmp.unget();
220     }
221     else
222         return FALSE;
223
224     name.end() = '\0';
225
226     in = tmp;
227     in.push(shellvarfn(name.getarr(), buff));
228     return TRUE;
229 }
230
231 static boolean
232 pushenv(_StringIO &in, char const *name)
233 {
234     register char *str = getenv(name);
235     if (str == NULL || *str == '\0')
236         return FALSE;
237     else
238     {
239         in.push(str);
240         return TRUE;
241     }
242 }
243
244 static boolean
245 pushtilde(_StringIO &in)
246 {
247     _StringIO tmp;
248     int namelen = 0;
249     privbuf_charbuf name;
250
251     tmp = in;
252     int ch;
253
254     while ((ch = tmp.get()) != '\0' && ch != '/')
255         name[namelen++] = ch;
256     name[namelen] = '\0';
257     tmp.unget();
258
259     char *str = name.getarr();
260     switch (*str)
261     {
262     case '\0': 
263         if (!pushenv(tmp, "HOME"))
264             return FALSE;
265         break;
266     case '+': 
267         if (!pushenv(tmp, "PWD"))
268             return FALSE;
269         break;
270     case '-': 
271         if (!pushenv(tmp, "OLDPWD"))
272             return FALSE;
273         break;
274     default: 
275         {
276           _Xgetpwparams pwd_buf;
277           memset((char*) &pwd_buf, 0, sizeof(_Xgetpwparams));
278           struct passwd * pwd_ret = _XGetpwnam(str, pwd_buf);
279
280           if (pwd_ret == NULL)
281             return FALSE;
282           tmp.push(pwd_ret->pw_dir);
283         }
284         break;
285     }
286
287     in = tmp;
288     return TRUE;
289 }
290
291 void
292 pushgrave(_StringIO &in, const char endchar, boolean quotes, privbuf_charbuf &result)
293 {
294     int ch;
295     char quote = NOQUOTE;
296     privbuf_charbuf cmd;
297
298     do
299     {
300         ch = in.get();
301
302         if (quotes)
303             switch (ch)
304             {
305             case '"': 
306                 quote = DOUBLEQUOTE;
307                 continue;
308             case '\'': 
309                 if (quote == '"')
310                     break;      // not recognized inside of ""
311                 do
312                     cmd.end() = ch;
313                 while ((ch = in.get()) != '\'' && ch != '\0');
314                 cmd.end() = '\'';
315                 quote = NOQUOTE;
316                 continue;
317             case '\\': 
318                 cmd.end() = ch;
319                 ch = in.get();
320                 if (ch != '\0')
321                     cmd.end() = ch;
322                 continue;
323             }
324
325         if (ch == endchar)
326             ch = '\0';
327
328         cmd.end() = ch;
329     } while (ch != '\0');
330
331     result.reset();
332
333     FILE *fp = popen(cmd.getarr(), "r");
334     if (fp == NULL)
335         return;
336     while ((ch = getc(fp)) != EOF)
337         result.end() = ch;
338     pclose(fp);
339
340     // Remove trailing newline, if any
341     long end = result.size() - 1;
342     if (result[end] == '\n')
343         result.reset(end);
344
345     result.end() = '\0';
346     in.push(result.getarr());
347 }
348
349 char const *const *
350 shellscan(char const *str, int *argc, unsigned opts)
351 {
352     if (opts & SHX_COMPLETE)
353         opts |= SHX_NOSPACE | SHX_NOMETA;
354
355     char *ifs = getenv("IFS");
356
357     if (ifs == NULL)
358         ifs = " \t\n";
359
360     _DtSvcProcessLock();
361     if (buf == NULL)
362         buf = new _SHXbuf;
363     buf->reset((boolean)!(opts & SHX_NOGLOB), (boolean)(opts & SHX_COMPLETE));
364
365     _StringIO in(str);
366     int ch;
367     char buff[10], meta_buff[4];
368     privbuf_charbuf result;
369
370     do
371     {
372         ch = in.get();
373
374         // Don't recognize special characters if this is a shell
375         // variable or command substitution.
376         if (!in.in_expansion())
377         {
378             // Handle quoting rules, setting the flag array and
379             // quote variable appropriately.
380             if (!(opts & SHX_NOQUOTES))
381                 switch (ch)
382                 {
383                 case '"': 
384                     buf->quote(DOUBLEQUOTE);
385                     continue;
386                 case '\'': 
387                     if (buf->quote() == DOUBLEQUOTE)
388                         break;  // not recognized inside of ""
389                     buf->quote(SINGLEQUOTE);
390                     while ((ch = in.get()) != '\'' && ch != '\0')
391                         buf->append(ch);
392                     buf->quote(SINGLEQUOTE);
393                     continue;
394                 case '\\': 
395                     ch = in.get();
396                     if (ch == '\n') // ignore \<newline>
397                         continue;
398                     if (ch == '\0')
399                     {
400 #if defined(__aix)  /* Our Macro doesn't like '\\' (ignores rest of line) */
401                         buf->append('\\',
402                            SINGLEQUOTE);
403 #else
404                         buf->append('\\', SINGLEQUOTE);
405 #endif
406                         break;
407                     }
408                     if (buf->quote() == NOQUOTE)
409                     {
410                         buf->append(ch, SINGLEQUOTE);
411                         continue;
412                     }
413                     else
414                     {
415                         // inside "", \ only quotes these 4 characters:
416                         switch (ch)
417                         {
418                         case '$': 
419                         case '\\': 
420                         case '`': 
421                         case '"': 
422                             buf->append(ch, SINGLEQUOTE);
423                             continue;
424                         default: 
425                             // treat the \ and the following char normally
426                             buf->append('\\');
427                             break;
428                         }
429                     }
430                     break;
431                 }
432
433             if (!(opts & SHX_NOCMD))
434                 switch (ch)
435                 {
436                 case '`': 
437                     pushgrave(in, '`', (boolean)!(opts & SHX_NOQUOTES), result);
438                     continue;
439                 case '$': 
440                     if (in.next() != '(')
441                         break;
442                     in.get();   // skip the '('
443                     pushgrave(in, ')', (boolean)!(opts & SHX_NOQUOTES), result);
444                     continue;
445                 }
446
447             if (ch == '~' && buf->new_token() && buf->quote() == NOQUOTE)
448                 if (!(opts & SHX_NOTILDE))
449                 {
450                     if (pushtilde(in))
451                         continue;
452                     buf->append('~');
453                     continue;
454                 }
455
456             if (ch == '$' && !(opts & SHX_NOVARS))
457             {
458                 if (pushvar(in, buff))
459                     continue;
460                 buf->append('$');
461                 continue;
462             }
463         }
464
465         // If the next item is an unquoted whitespace character or
466         // metacharacter token, terminate the current token.  The NUL
467         // character is considered to be whitespace.
468         {
469             int curr_opts = opts;
470
471             if (in.in_expansion())
472                 curr_opts |= SHX_NOMETA;
473
474             char *meta = parsemeta(ch, in, ifs, curr_opts, meta_buff);
475
476             if (meta != NULL)   // is it a meta-character?
477             {
478                 // Terminate current token, if any
479                 if (!buf->new_token())
480                     buf->append('\0');
481                 if (*meta == ' ')   // whitespace
482                 {
483                     // ignore contiguous whitespace chars
484                     if (buf->new_token())
485                         continue;
486                 }
487                 else            // append the metachar token
488                     buf->append(meta);
489                 continue;
490             }
491         }
492
493         buf->append(ch);
494     } while (ch != '\0');
495
496     if (argc != NULL)
497         *argc = buf->ntokens();
498
499     _DtSvcProcessUnlock();
500     return ( (char const *const *) buf->vector() );
501 /* !!! error 1325: `)' missing at end of input */
502 }