Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
att
GitHub Repository: att/ast
Path: blob/master/src/lib/libtksh/tcl/tclGlob.c
1810 views
1
/*
2
* tclGlob.c --
3
*
4
* This file provides procedures and commands for file name
5
* manipulation, such as tilde expansion and globbing.
6
*
7
* Copyright (c) 1990-1994 The Regents of the University of California.
8
* Copyright (c) 1994 Sun Microsystems, Inc.
9
*
10
* See the file "license.terms" for information on usage and redistribution
11
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12
*/
13
14
#ifndef lint
15
static char sccsid[] = "@(#) tclGlob.c 1.42 95/06/08 10:56:13";
16
#endif /* not lint */
17
18
#include "tclInt.h"
19
#include "tclPort.h"
20
21
/*
22
* Declarations for procedures local to this file:
23
*/
24
25
static int DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
26
char *rem));
27
28
/*
29
*----------------------------------------------------------------------
30
*
31
* DoGlob --
32
*
33
* This recursive procedure forms the heart of the globbing
34
* code. It performs a depth-first traversal of the tree
35
* given by the path name to be globbed.
36
*
37
* Results:
38
* The return value is a standard Tcl result indicating whether
39
* an error occurred in globbing. After a normal return the
40
* result in interp will be set to hold all of the file names
41
* given by the dir and rem arguments. After an error the
42
* result in interp will hold an error message.
43
*
44
* Side effects:
45
* None.
46
*
47
*----------------------------------------------------------------------
48
*/
49
50
static int
51
DoGlob(interp, dir, rem)
52
Tcl_Interp *interp; /* Interpreter to use for error
53
* reporting (e.g. unmatched brace). */
54
char *dir; /* Name of a directory at which to
55
* start glob expansion. This name
56
* is fixed: it doesn't contain any
57
* globbing chars. */
58
char *rem; /* Path to glob-expand. */
59
{
60
/*
61
* When this procedure is entered, the name to be globbed may
62
* already have been partly expanded by ancestor invocations of
63
* DoGlob. The part that's already been expanded is in "dir"
64
* (this may initially be empty), and the part still to expand
65
* is in "rem". This procedure expands "rem" one level, making
66
* recursive calls to itself if there's still more stuff left
67
* in the remainder.
68
*/
69
70
Tcl_DString newName; /* Holds new name consisting of
71
* dir plus the first part of rem. */
72
register char *p;
73
register char c;
74
char *openBrace, *closeBrace, *name, *dirName;
75
int gotSpecial, baseLength;
76
int result = TCL_OK;
77
struct stat statBuf;
78
79
/*
80
* Make sure that the directory part of the name really is a
81
* directory. If the directory name is "", use the name "."
82
* instead, because some UNIX systems don't treat "" like "."
83
* automatically. Keep the "" for use in generating file names,
84
* otherwise "glob foo.c" would return "./foo.c".
85
*/
86
87
if (*dir == '\0') {
88
dirName = ".";
89
} else {
90
dirName = dir;
91
}
92
if ((stat(dirName, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) {
93
return TCL_OK;
94
}
95
Tcl_DStringInit(&newName);
96
97
/*
98
* First, find the end of the next element in rem, checking
99
* along the way for special globbing characters.
100
*/
101
102
gotSpecial = 0;
103
openBrace = closeBrace = NULL;
104
for (p = rem; ; p++) {
105
c = *p;
106
if ((c == '\0') || ((openBrace == NULL) && (c == '/'))) {
107
break;
108
}
109
if ((c == '{') && (openBrace == NULL)) {
110
openBrace = p;
111
}
112
if ((c == '}') && (openBrace != NULL) && (closeBrace == NULL)) {
113
closeBrace = p;
114
}
115
if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
116
gotSpecial = 1;
117
}
118
}
119
120
/*
121
* If there is an open brace in the argument, then make a recursive
122
* call for each element between the braces. In this case, the
123
* recursive call to DoGlob uses the same "dir" that we got.
124
* If there are several brace-pairs in a single name, we just handle
125
* one here, and the others will be handled in recursive calls.
126
*/
127
128
if (openBrace != NULL) {
129
char *element;
130
131
if (closeBrace == NULL) {
132
Tcl_ResetResult(interp);
133
interp->result = "unmatched open-brace in file name";
134
result = TCL_ERROR;
135
goto done;
136
}
137
Tcl_DStringAppend(&newName, rem, openBrace-rem);
138
baseLength = newName.length;
139
for (p = openBrace; *p != '}'; ) {
140
element = p+1;
141
for (p = element; ((*p != '}') && (*p != ',')); p++) {
142
/* Empty loop body. */
143
}
144
Tcl_DStringAppend(&newName, element, p-element);
145
Tcl_DStringAppend(&newName, closeBrace+1, -1);
146
result = DoGlob(interp, dir, newName.string);
147
if (result != TCL_OK) {
148
goto done;
149
}
150
newName.length = baseLength;
151
}
152
goto done;
153
}
154
155
/*
156
* Start building up the next-level name with dir plus a slash if
157
* needed to separate it from the next file name.
158
*/
159
160
Tcl_DStringAppend(&newName, dir, -1);
161
if ((dir[0] != 0) && (newName.string[newName.length-1] != '/')) {
162
Tcl_DStringAppend(&newName, "/", 1);
163
}
164
baseLength = newName.length;
165
166
/*
167
* If there were any pattern-matching characters, then scan through
168
* the directory to find all the matching names.
169
*/
170
171
if (gotSpecial) {
172
DIR *d;
173
struct dirent *entryPtr;
174
char savedChar;
175
176
d = opendir(dirName);
177
if (d == NULL) {
178
Tcl_ResetResult(interp);
179
Tcl_AppendResult(interp, "couldn't read directory \"",
180
dirName, "\": ", Tcl_PosixError(interp), (char *) NULL);
181
result = TCL_ERROR;
182
goto done;
183
}
184
185
/*
186
* Temporarily store a null into rem so that the pattern string
187
* is now null-terminated.
188
*/
189
190
savedChar = *p;
191
*p = 0;
192
193
while (1) {
194
entryPtr = readdir(d);
195
if (entryPtr == NULL) {
196
break;
197
}
198
199
/*
200
* Don't match names starting with "." unless the "." is
201
* present in the pattern.
202
*/
203
204
if ((*entryPtr->d_name == '.') && (*rem != '.')) {
205
continue;
206
}
207
if (Tcl_StringMatch(entryPtr->d_name, rem)) {
208
newName.length = baseLength;
209
Tcl_DStringAppend(&newName, entryPtr->d_name, -1);
210
if (savedChar == 0) {
211
Tcl_AppendElement(interp, newName.string);
212
} else {
213
result = DoGlob(interp, newName.string, p+1);
214
if (result != TCL_OK) {
215
break;
216
}
217
}
218
}
219
}
220
closedir(d);
221
*p = savedChar;
222
goto done;
223
}
224
225
/*
226
* The current element is a simple one with no fancy features. Add
227
* it to the new name. If there are more elements still to come,
228
* then recurse to process them.
229
*/
230
231
Tcl_DStringAppend(&newName, rem, p-rem);
232
if (*p != 0) {
233
result = DoGlob(interp, newName.string, p+1);
234
goto done;
235
}
236
237
/*
238
* There are no more elements in the pattern. Check to be sure the
239
* file actually exists, then add its name to the list being formed
240
* in interp-result.
241
*/
242
243
name = newName.string;
244
if (*name == 0) {
245
name = ".";
246
}
247
if (access(name, F_OK) != 0) {
248
goto done;
249
}
250
Tcl_AppendElement(interp, name);
251
252
done:
253
Tcl_DStringFree(&newName);
254
return result;
255
}
256
257
/*
258
*----------------------------------------------------------------------
259
*
260
* Tcl_TildeSubst --
261
*
262
* Given a name starting with a tilde, produce a name where
263
* the tilde and following characters have been replaced by
264
* the home directory location for the named user.
265
*
266
* Results:
267
* The result is a pointer to a static string containing
268
* the new name. If there was an error in processing the
269
* tilde, then an error message is left in interp->result
270
* and the return value is NULL. The result may be stored
271
* in bufferPtr; the caller must call Tcl_DStringFree(bufferPtr)
272
* to free the name.
273
*
274
* Side effects:
275
* Information may be left in bufferPtr.
276
*
277
*----------------------------------------------------------------------
278
*/
279
280
char *
281
Tcl_TildeSubst(interp, name, bufferPtr)
282
Tcl_Interp *interp; /* Interpreter in which to store error
283
* message (if necessary). */
284
char *name; /* File name, which may begin with "~/"
285
* (to indicate current user's home directory)
286
* or "~<user>/" (to indicate any user's
287
* home directory). */
288
Tcl_DString *bufferPtr; /* May be used to hold result. Must not hold
289
* anything at the time of the call, and need
290
* not even be initialized. */
291
{
292
char *dir;
293
register char *p;
294
295
Tcl_DStringInit(bufferPtr);
296
if (name[0] != '~') {
297
return name;
298
}
299
300
if ((name[1] == '/') || (name[1] == '\0')) {
301
dir = getenv("HOME");
302
if (dir == NULL) {
303
Tcl_ResetResult(interp);
304
Tcl_AppendResult(interp, "couldn't find HOME environment ",
305
"variable to expand \"", name, "\"", (char *) NULL);
306
return NULL;
307
}
308
Tcl_DStringAppend(bufferPtr, dir, -1);
309
Tcl_DStringAppend(bufferPtr, name+1, -1);
310
} else {
311
struct passwd *pwPtr;
312
313
for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
314
/* Null body; just find end of name. */
315
}
316
Tcl_DStringAppend(bufferPtr, name+1, p - (name+1));
317
pwPtr = getpwnam(bufferPtr->string);
318
if (pwPtr == NULL) {
319
endpwent();
320
Tcl_ResetResult(interp);
321
Tcl_AppendResult(interp, "user \"", bufferPtr->string,
322
"\" doesn't exist", (char *) NULL);
323
Tcl_DStringFree(bufferPtr);
324
return NULL;
325
}
326
Tcl_DStringFree(bufferPtr);
327
Tcl_DStringAppend(bufferPtr, pwPtr->pw_dir, -1);
328
Tcl_DStringAppend(bufferPtr, p, -1);
329
endpwent();
330
}
331
return bufferPtr->string;
332
}
333
334
/*
335
*----------------------------------------------------------------------
336
*
337
* Tcl_GlobCmd --
338
*
339
* This procedure is invoked to process the "glob" Tcl command.
340
* See the user documentation for details on what it does.
341
*
342
* Results:
343
* A standard Tcl result.
344
*
345
* Side effects:
346
* See the user documentation.
347
*
348
*----------------------------------------------------------------------
349
*/
350
351
/* ARGSUSED */
352
int
353
Tcl_GlobCmd(dummy, interp, argc, argv)
354
ClientData dummy; /* Not used. */
355
Tcl_Interp *interp; /* Current interpreter. */
356
int argc; /* Number of arguments. */
357
char **argv; /* Argument strings. */
358
{
359
int i, result, noComplain, firstArg;
360
361
if (argc < 2) {
362
notEnoughArgs:
363
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
364
" ?switches? name ?name ...?\"", (char *) NULL);
365
return TCL_ERROR;
366
}
367
noComplain = 0;
368
for (firstArg = 1; (firstArg < argc) && (argv[firstArg][0] == '-');
369
firstArg++) {
370
if (strcmp(argv[firstArg], "-nocomplain") == 0) {
371
noComplain = 1;
372
} else if (strcmp(argv[firstArg], "--") == 0) {
373
firstArg++;
374
break;
375
} else {
376
Tcl_AppendResult(interp, "bad switch \"", argv[firstArg],
377
"\": must be -nocomplain or --", (char *) NULL);
378
return TCL_ERROR;
379
}
380
}
381
if (firstArg >= argc) {
382
goto notEnoughArgs;
383
}
384
385
for (i = firstArg; i < argc; i++) {
386
char *thisName;
387
Tcl_DString buffer;
388
389
thisName = Tcl_TildeSubst(interp, argv[i], &buffer);
390
if (thisName == NULL) {
391
if (noComplain) {
392
Tcl_ResetResult(interp);
393
continue;
394
} else {
395
return TCL_ERROR;
396
}
397
}
398
if (*thisName == '/') {
399
if (thisName[1] == '/') {
400
/*
401
* This is a special hack for systems like those from Apollo
402
* where there is a super-root at "//": need to treat the
403
* double-slash as a single name.
404
*/
405
result = DoGlob(interp, "//", thisName+2);
406
} else {
407
result = DoGlob(interp, "/", thisName+1);
408
}
409
} else {
410
result = DoGlob(interp, "", thisName);
411
}
412
Tcl_DStringFree(&buffer);
413
if (result != TCL_OK) {
414
return result;
415
}
416
}
417
if ((*interp->result == 0) && !noComplain) {
418
char *sep = "";
419
420
Tcl_AppendResult(interp, "no files matched glob pattern",
421
(argc == 2) ? " \"" : "s \"", (char *) NULL);
422
for (i = firstArg; i < argc; i++) {
423
Tcl_AppendResult(interp, sep, argv[i], (char *) NULL);
424
sep = " ";
425
}
426
Tcl_AppendResult(interp, "\"", (char *) NULL);
427
return TCL_ERROR;
428
}
429
return TCL_OK;
430
}
431
432