1 /*
2 * filefuncs.c - Builtin functions that provide initial minimal iterface
3 * to the file system.
4 *
5 * Arnold Robbins, update for 3.1, Mon Nov 23 12:53:39 EST 1998
6 * Arnold Robbins and John Haque, update for 3.1.4, applied Mon Jun 14 13:55:30 IDT 2004
7 * Arnold Robbins and Andrew Schorr, revised for new extension API, May 2012.
8 * Arnold Robbins, add fts(), August 2012
9 * Arnold Robbins, add statvfs(), November 2015
10 */
11
12 /*
13 * Copyright (C) 2001, 2004, 2005, 2010-2021,
14 * the Free Software Foundation, Inc.
15 *
16 * This file is part of GAWK, the GNU implementation of the
17 * AWK Programming Language.
18 *
19 * GAWK is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
23 *
24 * GAWK is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
32 */
33
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
37
38 #define _BSD_SOURCE
39
40 #ifdef __VMS
41 #if (__CRTL_VER >= 70200000) && !defined (__VAX)
42 #define _LARGEFILE 1
43 #endif
44
45 #ifndef __VAX
46 #ifdef __CRTL_VER
47 #if __CRTL_VER >= 80200000
48 #define _USE_STD_STAT 1
49 #endif
50 #endif
51 #endif
52 #define _POSIX_C_SOURCE 1
53 #define _XOPEN_SOURCE 1
54 #include <stat.h>
55 #ifndef S_ISVTX
56 #define S_ISVTX (0)
57 #endif
58 #ifndef major
59 #define major(s) (s)
60 #endif
61 #ifndef minor
62 #define minor(s) (0)
63 #endif
64 #include <unixlib.h>
65 #endif
66
67
68 #include <stdio.h>
69 #include <assert.h>
70 #include <errno.h>
71 #include <stdlib.h>
72 #include <string.h>
73 #include <unistd.h>
74
75 #ifdef HAVE_SYS_PARAM_H
76 #include <sys/param.h>
77 #endif /* HAVE_SYS_PARAM_H */
78
79 #if HAVE_SYS_SYSMACROS_H
80 #include <sys/sysmacros.h>
81 #elif HAVE_SYS_MKDEV_H
82 #include <sys/mkdev.h>
83 #endif /* HAVE_SYS_MKDEV_H */
84
85 #include <sys/types.h>
86
87 #include <sys/stat.h>
88
89 #if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
90 #include <sys/statvfs.h>
91 #endif
92
93 #include "gawkapi.h"
94
95 #include "gettext.h"
96 #define _(msgid) gettext(msgid)
97 #define N_(msgid) msgid
98
99 #include "gawkfts.h"
100 #include "stack.h"
101
102 #ifndef S_IFLNK
103 #define lstat stat
104 #define S_ISLNK(s) 0
105 #define readlink(f,b,bs) (-1)
106 #endif
107
108 #ifdef __MINGW32__
109 #define S_IRGRP S_IRUSR
110 #define S_IWGRP S_IWUSR
111 #define S_IXGRP S_IXUSR
112 #define S_IROTH S_IRUSR
113 #define S_IWOTH S_IWUSR
114 #define S_IXOTH S_IXUSR
115 #define S_ISUID 0
116 #define S_ISGID 0
117 #define S_ISVTX 0
118 #define major(s) (s)
119 #define minor(s) (0)
120
121 #define WIN32_LEAN_AND_MEAN
122 #include <windows.h>
123
124 /* get_inode --- get the inode of a file */
125 static long long
get_inode(const char * fname)126 get_inode(const char *fname)
127 {
128 HANDLE fh;
129 BOOL ok;
130 BY_HANDLE_FILE_INFORMATION info;
131
132 fh = CreateFile(fname, 0, 0, NULL, OPEN_EXISTING,
133 FILE_FLAG_BACKUP_SEMANTICS, NULL);
134 if (fh == INVALID_HANDLE_VALUE)
135 return 0;
136 ok = GetFileInformationByHandle(fh, &info);
137 CloseHandle(fh);
138 if (ok) {
139 long long inode = info.nFileIndexHigh;
140
141 inode <<= 32;
142 inode += info.nFileIndexLow;
143 return inode;
144 }
145 return 0;
146 }
147 #endif
148
149 static const gawk_api_t *api; /* for convenience macros to work */
150 static awk_ext_id_t ext_id;
151 static awk_bool_t init_filefuncs(void);
152 static awk_bool_t (*init_func)(void) = init_filefuncs;
153 static const char *ext_version = "filefuncs extension: version 1.0";
154
155 int plugin_is_GPL_compatible;
156
157 /* do_chdir --- provide dynamically loaded chdir() function for gawk */
158
159 static awk_value_t *
do_chdir(int nargs,awk_value_t * result,struct awk_ext_func * unused)160 do_chdir(int nargs, awk_value_t *result, struct awk_ext_func *unused)
161 {
162 awk_value_t newdir;
163 int ret = -1;
164
165 assert(result != NULL);
166
167 if (get_argument(0, AWK_STRING, & newdir)) {
168 ret = chdir(newdir.str_value.str);
169 if (ret < 0)
170 update_ERRNO_int(errno);
171 }
172
173 return make_number(ret, result);
174 }
175
176 /* format_mode --- turn a stat mode field into something readable */
177
178 static char *
format_mode(unsigned long fmode)179 format_mode(unsigned long fmode)
180 {
181 static char outbuf[12];
182 static struct ftype_map {
183 unsigned int mask;
184 int charval;
185 } ftype_map[] = {
186 { S_IFREG, '-' }, /* redundant */
187 { S_IFBLK, 'b' },
188 { S_IFCHR, 'c' },
189 { S_IFDIR, 'd' },
190 #ifdef S_IFSOCK
191 { S_IFSOCK, 's' },
192 #endif
193 #ifdef S_IFIFO
194 { S_IFIFO, 'p' },
195 #endif
196 #ifdef S_IFLNK
197 { S_IFLNK, 'l' },
198 #endif
199 #ifdef S_IFDOOR /* Solaris weirdness */
200 { S_IFDOOR, 'D' },
201 #endif /* S_IFDOOR */
202 };
203 static struct mode_map {
204 unsigned int mask;
205 int rep;
206 } map[] = {
207 { S_IRUSR, 'r' }, { S_IWUSR, 'w' }, { S_IXUSR, 'x' },
208 { S_IRGRP, 'r' }, { S_IWGRP, 'w' }, { S_IXGRP, 'x' },
209 { S_IROTH, 'r' }, { S_IWOTH, 'w' }, { S_IXOTH, 'x' },
210 };
211 static struct setuid_map {
212 unsigned int mask;
213 int index;
214 int small_rep;
215 int big_rep;
216 } setuid_map[] = {
217 { S_ISUID, 3, 's', 'S' }, /* setuid bit */
218 { S_ISGID, 6, 's', 'l' }, /* setgid without execute == locking */
219 { S_ISVTX, 9, 't', 'T' }, /* the so-called "sticky" bit */
220 };
221 int i, j, k;
222
223 strcpy(outbuf, "----------");
224
225 /* first, get the file type */
226 i = 0;
227 for (j = 0, k = sizeof(ftype_map)/sizeof(ftype_map[0]); j < k; j++) {
228 if ((fmode & S_IFMT) == ftype_map[j].mask) {
229 outbuf[i] = ftype_map[j].charval;
230 break;
231 }
232 }
233
234 /* now the permissions */
235 for (j = 0, k = sizeof(map)/sizeof(map[0]); j < k; j++) {
236 i++;
237 if ((fmode & map[j].mask) != 0)
238 outbuf[i] = map[j].rep;
239 }
240
241 i++;
242 outbuf[i] = '\0';
243
244 /* tweaks for the setuid / setgid / sticky bits */
245 for (j = 0, k = sizeof(setuid_map)/sizeof(setuid_map[0]); j < k; j++) {
246 if (fmode & setuid_map[j].mask) {
247 if (outbuf[setuid_map[j].index] == 'x')
248 outbuf[setuid_map[j].index] = setuid_map[j].small_rep;
249 else
250 outbuf[setuid_map[j].index] = setuid_map[j].big_rep;
251 }
252 }
253
254 return outbuf;
255 }
256
257 /* read_symlink --- read a symbolic link into an allocated buffer.
258 This is based on xreadlink; the basic problem is that lstat cannot be relied
259 upon to return the proper size for a symbolic link. This happens,
260 for example, on GNU/Linux in the /proc filesystem, where the symbolic link
261 sizes are often 0. */
262
263 #ifndef SIZE_MAX
264 # define SIZE_MAX ((size_t) -1)
265 #endif
266 #ifndef SSIZE_MAX
267 # define SSIZE_MAX ((ssize_t) (SIZE_MAX / 2))
268 #endif
269
270 #define MAXSIZE (SIZE_MAX < SSIZE_MAX ? SIZE_MAX : SSIZE_MAX)
271
272 static char *
read_symlink(const char * fname,size_t bufsize,ssize_t * linksize)273 read_symlink(const char *fname, size_t bufsize, ssize_t *linksize)
274 {
275 if (bufsize)
276 bufsize += 2;
277 else
278 bufsize = BUFSIZ * 2;
279
280 /* Make sure that bufsize >= 2 and within range */
281 if (bufsize > MAXSIZE || bufsize < 2)
282 bufsize = MAXSIZE;
283
284 while (1) {
285 char *buf;
286
287 emalloc(buf, char *, bufsize, "read_symlink");
288 if ((*linksize = readlink(fname, buf, bufsize)) < 0) {
289 /* On AIX 5L v5.3 and HP-UX 11i v2 04/09, readlink
290 returns -1 with errno == ERANGE if the buffer is
291 too small. */
292 if (errno != ERANGE) {
293 gawk_free(buf);
294 return NULL;
295 }
296 }
297 /* N.B. This test is safe because bufsize must be >= 2 */
298 else if ((size_t)*linksize <= bufsize-2) {
299 buf[*linksize] = '\0';
300 return buf;
301 }
302 gawk_free(buf);
303 if (bufsize <= MAXSIZE/2)
304 bufsize *= 2;
305 else if (bufsize < MAXSIZE)
306 bufsize = MAXSIZE;
307 else
308 return NULL;
309 }
310 return NULL;
311 }
312
313
314 /* device_blocksize --- try to figure out units of st_blocks */
315
316 static int
device_blocksize()317 device_blocksize()
318 {
319 /* some of this derived from GNULIB stat-size.h */
320 #if defined(DEV_BSIZE)
321 /* <sys/param.h>, most systems */
322 return DEV_BSIZE;
323 #elif defined(S_BLKSIZE)
324 /* <sys/stat.h>, BSD systems */
325 return S_BLKSIZE;
326 #elif defined hpux || defined __hpux__ || defined __hpux
327 return 1024;
328 #elif defined _AIX && defined _I386
329 /* AIX PS/2 counts st_blocks in 4K units. */
330 return 4 * 1024;
331 #elif defined __MINGW32__
332 return 1024;
333 #else
334 return 512;
335 #endif
336 }
337
338 /* array_set --- set an array element */
339
340 static void
array_set(awk_array_t array,const char * sub,awk_value_t * value)341 array_set(awk_array_t array, const char *sub, awk_value_t *value)
342 {
343 awk_value_t index;
344
345 set_array_element(array,
346 make_const_string(sub, strlen(sub), & index),
347 value);
348
349 }
350
351 /* array_set_numeric --- set an array element with a number */
352
353 static void
array_set_numeric(awk_array_t array,const char * sub,double num)354 array_set_numeric(awk_array_t array, const char *sub, double num)
355 {
356 awk_value_t tmp;
357
358 array_set(array, sub, make_number(num, & tmp));
359 }
360
361 /* fill_stat_array --- do the work to fill an array with stat info */
362
363 static int
fill_stat_array(const char * name,awk_array_t array,struct stat * sbuf)364 fill_stat_array(const char *name, awk_array_t array, struct stat *sbuf)
365 {
366 char *pmode; /* printable mode */
367 const char *type = "unknown";
368 awk_value_t tmp;
369 static struct ftype_map {
370 unsigned int mask;
371 const char *type;
372 } ftype_map[] = {
373 { S_IFREG, "file" },
374 { S_IFBLK, "blockdev" },
375 { S_IFCHR, "chardev" },
376 { S_IFDIR, "directory" },
377 #ifdef S_IFSOCK
378 { S_IFSOCK, "socket" },
379 #endif
380 #ifdef S_IFIFO
381 { S_IFIFO, "fifo" },
382 #endif
383 #ifdef S_IFLNK
384 { S_IFLNK, "symlink" },
385 #endif
386 #ifdef S_IFDOOR /* Solaris weirdness */
387 { S_IFDOOR, "door" },
388 #endif /* S_IFDOOR */
389 };
390 int j, k;
391
392 /* empty out the array */
393 clear_array(array);
394
395 /* fill in the array */
396 array_set(array, "name", make_const_string(name, strlen(name), & tmp));
397 array_set_numeric(array, "dev", sbuf->st_dev);
398 #ifdef __MINGW32__
399 array_set_numeric(array, "ino", (double)get_inode (name));
400 #else
401 array_set_numeric(array, "ino", sbuf->st_ino);
402 #endif
403 array_set_numeric(array, "mode", sbuf->st_mode);
404 array_set_numeric(array, "nlink", sbuf->st_nlink);
405 array_set_numeric(array, "uid", sbuf->st_uid);
406 array_set_numeric(array, "gid", sbuf->st_gid);
407 array_set_numeric(array, "size", sbuf->st_size);
408 #ifdef __MINGW32__
409 array_set_numeric(array, "blocks", (double)((sbuf->st_size +
410 device_blocksize() - 1) / device_blocksize()));
411 #else
412 array_set_numeric(array, "blocks", sbuf->st_blocks);
413 #endif
414 array_set_numeric(array, "atime", sbuf->st_atime);
415 array_set_numeric(array, "mtime", sbuf->st_mtime);
416 array_set_numeric(array, "ctime", sbuf->st_ctime);
417
418 /* for block and character devices, add rdev, major and minor numbers */
419 if (S_ISBLK(sbuf->st_mode) || S_ISCHR(sbuf->st_mode)) {
420 array_set_numeric(array, "rdev", sbuf->st_rdev);
421 array_set_numeric(array, "major", major(sbuf->st_rdev));
422 array_set_numeric(array, "minor", minor(sbuf->st_rdev));
423 }
424
425 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
426 array_set_numeric(array, "blksize", sbuf->st_blksize);
427 #elif defined(__MINGW32__)
428 array_set_numeric(array, "blksize", 4096);
429 #endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
430
431 /* the size of a block for st_blocks */
432 array_set_numeric(array, "devbsize", device_blocksize());
433
434 pmode = format_mode(sbuf->st_mode);
435 array_set(array, "pmode", make_const_string(pmode, strlen(pmode), & tmp));
436
437 /* for symbolic links, add a linkval field */
438 if (S_ISLNK(sbuf->st_mode)) {
439 char *buf;
440 ssize_t linksize;
441
442 if ((buf = read_symlink(name, sbuf->st_size,
443 & linksize)) != NULL)
444 array_set(array, "linkval", make_malloced_string(buf, linksize, & tmp));
445 else
446 warning(ext_id, _("stat: unable to read symbolic link `%s'"), name);
447 }
448
449 /* add a type field */
450 type = "unknown"; /* shouldn't happen */
451 for (j = 0, k = sizeof(ftype_map)/sizeof(ftype_map[0]); j < k; j++) {
452 if ((sbuf->st_mode & S_IFMT) == ftype_map[j].mask) {
453 type = ftype_map[j].type;
454 break;
455 }
456 }
457
458 array_set(array, "type", make_const_string(type, strlen(type), & tmp));
459
460 return 0;
461 }
462
463 /* do_stat --- provide a stat() function for gawk */
464
465 static awk_value_t *
do_stat(int nargs,awk_value_t * result,struct awk_ext_func * unused)466 do_stat(int nargs, awk_value_t *result, struct awk_ext_func *unused)
467 {
468 awk_value_t file_param, array_param;
469 char *name;
470 awk_array_t array;
471 int ret;
472 struct stat sbuf;
473 int (*statfunc)(const char *path, struct stat *sbuf) = lstat; /* default */
474
475 assert(result != NULL);
476
477 /* file is first arg, array to hold results is second */
478 if (! get_argument(0, AWK_STRING, & file_param)) {
479 warning(ext_id, _("stat: first argument is not a string"));
480 return make_number(-1, result);
481 }
482
483 if (! get_argument(1, AWK_ARRAY, & array_param)) {
484 warning(ext_id, _("stat: second argument is not an array"));
485 return make_number(-1, result);
486 }
487
488 if (nargs == 3) {
489 statfunc = stat;
490 }
491
492 name = file_param.str_value.str;
493 array = array_param.array_cookie;
494
495 /* always empty out the array */
496 clear_array(array);
497
498 /* stat the file; if error, set ERRNO and return */
499 ret = statfunc(name, & sbuf);
500 if (ret < 0) {
501 update_ERRNO_int(errno);
502 return make_number(ret, result);
503 }
504
505 ret = fill_stat_array(name, array, & sbuf);
506
507 return make_number(ret, result);
508 }
509
510 #if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
511
512 /* do_statvfs --- provide a statvfs() function for gawk */
513
514 static awk_value_t *
do_statvfs(int nargs,awk_value_t * result,struct awk_ext_func * unused)515 do_statvfs(int nargs, awk_value_t *result, struct awk_ext_func *unused)
516 {
517 awk_value_t file_param, array_param;
518 char *name;
519 awk_array_t array;
520 int ret;
521 struct statvfs vfsbuf;
522
523 assert(result != NULL);
524
525 /* file is first arg, array to hold results is second */
526 if ( ! get_argument(0, AWK_STRING, & file_param)
527 || ! get_argument(1, AWK_ARRAY, & array_param)) {
528 warning(ext_id, _("stat: bad parameters"));
529 return make_number(-1, result);
530 }
531
532 name = file_param.str_value.str;
533 array = array_param.array_cookie;
534
535 /* always empty out the array */
536 clear_array(array);
537
538 /* statvfs the filesystem; if error, set ERRNO and return */
539 ret = statvfs(name, & vfsbuf);
540 if (ret < 0) {
541 update_ERRNO_int(errno);
542 return make_number(ret, result);
543 }
544
545 array_set_numeric(array, "bsize", vfsbuf.f_bsize); /* filesystem block size */
546 array_set_numeric(array, "frsize", vfsbuf.f_frsize); /* fragment size */
547 array_set_numeric(array, "blocks", vfsbuf.f_blocks); /* size of fs in f_frsize units */
548 array_set_numeric(array, "bfree", vfsbuf.f_bfree); /* # free blocks */
549 array_set_numeric(array, "bavail", vfsbuf.f_bavail); /* # free blocks for unprivileged users */
550 array_set_numeric(array, "files", vfsbuf.f_files); /* # inodes */
551 array_set_numeric(array, "ffree", vfsbuf.f_ffree); /* # free inodes */
552 array_set_numeric(array, "favail", vfsbuf.f_favail); /* # free inodes for unprivileged users */
553 #ifndef _AIX
554 array_set_numeric(array, "fsid", vfsbuf.f_fsid); /* filesystem ID */
555 #endif
556 array_set_numeric(array, "flag", vfsbuf.f_flag); /* mount flags */
557 array_set_numeric(array, "namemax", vfsbuf.f_namemax); /* maximum filename length */
558
559
560 return make_number(ret, result);
561 }
562 #endif
563
564 /* init_filefuncs --- initialization routine */
565
566 static awk_bool_t
init_filefuncs(void)567 init_filefuncs(void)
568 {
569 int errors = 0;
570 int i;
571 awk_value_t value;
572
573 #ifndef __MINGW32__
574 /* at least right now, only FTS needs initializing */
575 #define FTS_NON_RECURSIVE FTS_STOP /* Don't step into directories. */
576 static struct flagtab {
577 const char *name;
578 int value;
579 } opentab[] = {
580 #define ENTRY(x) { #x, x }
581 ENTRY(FTS_COMFOLLOW),
582 ENTRY(FTS_LOGICAL),
583 ENTRY(FTS_NOCHDIR),
584 ENTRY(FTS_PHYSICAL),
585 ENTRY(FTS_SEEDOT),
586 ENTRY(FTS_XDEV),
587 {"FTS_SKIP", FTS_NON_RECURSIVE},
588 { NULL, 0 }
589 };
590
591 for (i = 0; opentab[i].name != NULL; i++) {
592 (void) make_number(opentab[i].value, & value);
593 if (! sym_update(opentab[i].name, & value)) {
594 warning(ext_id, _("fts init: could not create variable %s"),
595 opentab[i].name);
596 errors++;
597 }
598 }
599 #endif
600 return errors == 0;
601 }
602
603 #ifdef __MINGW32__
604 /* do_fts --- walk a hierarchy and fill in an array */
605
606 /*
607 * Usage from awk:
608 * flags = or(FTS_PHYSICAL, ...)
609 * result = fts(pathlist, flags, filedata)
610 */
611
612 static awk_value_t *
do_fts(int nargs,awk_value_t * result,struct awk_ext_func * unused)613 do_fts(int nargs, awk_value_t *result, struct awk_ext_func *unused)
614 {
615 fatal(ext_id, _("fts is not supported on this system"));
616
617 return NULL; /* for the compiler */
618 }
619
620 #else /* __MINGW32__ */
621
622 static int fts_errors = 0;
623
624 /* fill_stat_element --- fill in stat element of array */
625
626 static void
fill_stat_element(awk_array_t element_array,const char * name,struct stat * sbuf)627 fill_stat_element(awk_array_t element_array, const char *name, struct stat *sbuf)
628 {
629 awk_value_t index, value;
630 awk_array_t stat_array;
631
632 stat_array = create_array();
633 if (stat_array == NULL) {
634 warning(ext_id, _("fill_stat_element: could not create array, out of memory"));
635 fts_errors++;
636 return;
637 }
638 fill_stat_array(name, stat_array, sbuf);
639 (void) make_const_string("stat", 4, & index);
640 value.val_type = AWK_ARRAY;
641 value.array_cookie = stat_array;
642 if (! set_array_element(element_array, & index, & value)) {
643 warning(ext_id, _("fill_stat_element: could not set element"));
644 fts_errors++;
645 }
646 }
647
648 /* fill_path_element --- fill in path element of array */
649
650 static void
fill_path_element(awk_array_t element_array,const char * path)651 fill_path_element(awk_array_t element_array, const char *path)
652 {
653 awk_value_t index, value;
654
655 (void) make_const_string("path", 4, & index);
656 (void) make_const_string(path, strlen(path), & value);
657 if (! set_array_element(element_array, & index, & value)) {
658 warning(ext_id, _("fill_path_element: could not set element"));
659 fts_errors++;
660 }
661 }
662
663 /* fill_error_element --- fill in error element of array */
664
665 static void
fill_error_element(awk_array_t element_array,const int errcode)666 fill_error_element(awk_array_t element_array, const int errcode)
667 {
668 awk_value_t index, value;
669 const char *err = strerror(errcode);
670
671 (void) make_const_string("error", 5, & index);
672 (void) make_const_string(err, strlen(err), & value);
673 if (! set_array_element(element_array, & index, & value)) {
674 warning(ext_id, _("fill_error_element: could not set element"));
675 fts_errors++;
676 }
677 }
678
679 /* fill_default_elements --- fill in stat and path elements */
680
681 static void
fill_default_elements(awk_array_t element_array,const FTSENT * const fentry,awk_bool_t bad_ret)682 fill_default_elements(awk_array_t element_array, const FTSENT *const fentry, awk_bool_t bad_ret)
683 {
684 /* full path */
685 fill_path_element(element_array, fentry->fts_path);
686
687 /* stat info */
688 if (! bad_ret) {
689 fill_stat_element(element_array,
690 fentry->fts_name,
691 fentry->fts_statp);
692 }
693
694 /* error info */
695 if (bad_ret || fentry->fts_errno != 0) {
696 fill_error_element(element_array, fentry->fts_errno);
697 }
698 }
699
700 /* process --- process the hierarchy */
701
702 static void
process(FTS * hierarchy,awk_array_t destarray,int seedot,int skipset)703 process(FTS *hierarchy, awk_array_t destarray, int seedot, int skipset)
704 {
705 FTSENT *fentry;
706 awk_value_t index, value;
707 awk_array_t element_array, newdir_array, dot_array;
708 awk_bool_t bad_ret = awk_false;
709
710 /* path is full path, pathlen is length thereof */
711 /* name is name in directory, namelen is length thereof */
712 while ((fentry = fts_read(hierarchy)) != NULL) {
713 bad_ret = awk_false;
714
715 switch (fentry->fts_info) {
716 case FTS_D:
717 /* directory */
718
719 if (skipset && fentry->fts_level == 0)
720 fts_set(hierarchy, fentry, FTS_SKIP);
721
722 /* create array to hold entries */
723 /* this will be empty if doing FTS_SKIP */
724 newdir_array = create_array();
725 if (newdir_array == NULL) {
726 warning(ext_id, _("fts-process: could not create array"));
727 fts_errors++;
728 break;
729 }
730
731 /* store new directory in its parent directory */
732 (void) make_const_string(fentry->fts_name, fentry->fts_namelen, & index);
733 value.val_type = AWK_ARRAY;
734 value.array_cookie = newdir_array;
735 if (! set_array_element(destarray, & index, & value)) {
736 warning(ext_id, _("fts-process: could not set element"));
737 fts_errors++;
738 break;
739 }
740 newdir_array = value.array_cookie;
741
742 /* push current directory */
743 stack_push(destarray);
744
745 /* new directory becomes current */
746 destarray = newdir_array;
747 break;
748
749 case FTS_DNR:
750 case FTS_DC:
751 case FTS_ERR:
752 case FTS_NS:
753 /* error */
754 bad_ret = awk_true;
755 /* fall through */
756
757 case FTS_NSOK:
758 case FTS_SL:
759 case FTS_SLNONE:
760 case FTS_F:
761 case FTS_DOT:
762 /* if see dot, skip "." */
763 if (seedot && strcmp(fentry->fts_name, ".") == 0)
764 break;
765
766 /*
767 * File case.
768 * destarray is the directory we're reading.
769 * step 1: create new empty array
770 */
771 element_array = create_array();
772 if (element_array == NULL) {
773 warning(ext_id, _("fts-process: could not create array"));
774 fts_errors++;
775 break;
776 }
777
778 /* step 2: add element array to parent array */
779 (void) make_const_string(fentry->fts_name, fentry->fts_namelen, & index);
780 value.val_type = AWK_ARRAY;
781 value.array_cookie = element_array;
782 if (! set_array_element(destarray, & index, & value)) {
783 warning(ext_id, _("fts-process: could not set element"));
784 fts_errors++;
785 break;
786 }
787
788 /* step 3: fill in path, stat, error elements */
789 fill_default_elements(element_array, fentry, bad_ret);
790 break;
791
792 case FTS_DP:
793 /* create "." subarray */
794 dot_array = create_array();
795
796 /* add it to parent */
797 (void) make_const_string(".", 1, & index);
798 value.val_type = AWK_ARRAY;
799 value.array_cookie = dot_array;
800 if (! set_array_element(destarray, & index, & value)) {
801 warning(ext_id, _("fts-process: could not set element"));
802 fts_errors++;
803 break;
804 }
805
806 /* fill it in with path, stat, error elements */
807 fill_default_elements(dot_array, fentry, bad_ret);
808
809 /* now pop the parent directory off the stack */
810 if (! stack_empty()) {
811 /* pop stack */
812 destarray = stack_pop();
813 }
814
815 break;
816
817 case FTS_DEFAULT:
818 /* nothing to do */
819 break;
820 }
821 }
822 }
823
824 /* do_fts --- walk a hierarchy and fill in an array */
825
826 /*
827 * Usage from awk:
828 * flags = or(FTS_PHYSICAL, ...)
829 * result = fts(pathlist, flags, filedata)
830 */
831
832 static awk_value_t *
do_fts(int nargs,awk_value_t * result,struct awk_ext_func * unused)833 do_fts(int nargs, awk_value_t *result, struct awk_ext_func *unused)
834 {
835 awk_value_t pathlist, flagval, dest;
836 awk_flat_array_t *path_array = NULL;
837 char **pathvector = NULL;
838 FTS *hierarchy;
839 int flags;
840 size_t i, count;
841 int ret = -1;
842 static const int mask = (
843 FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOCHDIR | FTS_PHYSICAL
844 | FTS_SEEDOT | FTS_XDEV | FTS_NON_RECURSIVE);
845
846 assert(result != NULL);
847 fts_errors = 0; /* ensure a fresh start */
848
849 if (nargs > 3)
850 lintwarn(ext_id, _("fts: called with incorrect number of arguments, expecting 3"));
851
852 if (! get_argument(0, AWK_ARRAY, & pathlist)) {
853 warning(ext_id, _("fts: first argument is not an array"));
854 update_ERRNO_int(EINVAL);
855 goto out;
856 }
857
858 if (! get_argument(1, AWK_NUMBER, & flagval)) {
859 warning(ext_id, _("fts: second argument is not a number"));
860 update_ERRNO_int(EINVAL);
861 goto out;
862 }
863
864 if (! get_argument(2, AWK_ARRAY, & dest)) {
865 warning(ext_id, _("fts: third argument is not an array"));
866 update_ERRNO_int(EINVAL);
867 goto out;
868 }
869
870 /* flatten pathlist */
871 if (! flatten_array(pathlist.array_cookie, & path_array)) {
872 warning(ext_id, _("fts: could not flatten array\n"));
873 goto out;
874 }
875
876 /* check the flags first, before the array flattening */
877
878 /* get flags */
879 flags = flagval.num_value;
880
881 /* enforce physical or logical but not both, and not no_stat */
882 if ((flags & (FTS_PHYSICAL|FTS_LOGICAL)) == 0
883 || (flags & (FTS_PHYSICAL|FTS_LOGICAL)) == (FTS_PHYSICAL|FTS_LOGICAL)) {
884 update_ERRNO_int(EINVAL);
885 goto out;
886 }
887 if ((flags & FTS_NOSTAT) != 0) {
888 flags &= ~FTS_NOSTAT;
889 if (do_lint)
890 lintwarn(ext_id, _("fts: ignoring sneaky FTS_NOSTAT flag. nyah, nyah, nyah."));
891 }
892 flags &= mask; /* turn off anything else */
893
894 if (flags & FTS_NON_RECURSIVE)
895 flags |= FTS_NOCHDIR;
896
897 /* make pathvector */
898 count = path_array->count + 1;
899 ezalloc(pathvector, char **, count * sizeof(char *), "do_fts");
900
901 /* fill it in */
902 count--; /* ignore final NULL at end of vector */
903 for (i = 0; i < count; i++)
904 pathvector[i] = path_array->elements[i].value.str_value.str;
905
906
907 /* clear dest array */
908 assert(clear_array(dest.array_cookie));
909
910 /* let's do it! */
911 hierarchy = fts_open(pathvector, flags & ~FTS_NON_RECURSIVE, NULL);
912 if (hierarchy != NULL) {
913 process(hierarchy, dest.array_cookie,
914 (flags & FTS_SEEDOT) != 0,
915 (flags & FTS_NON_RECURSIVE) != 0);
916 fts_close(hierarchy);
917
918 if (fts_errors == 0)
919 ret = 0;
920 } else
921 update_ERRNO_int(errno);
922
923 out:
924 if (pathvector != NULL)
925 gawk_free(pathvector);
926 if (path_array != NULL)
927 (void) release_flattened_array(pathlist.array_cookie, path_array);
928
929 return make_number(ret, result);
930 }
931 #endif /* ! __MINGW32__ */
932
933 static awk_ext_func_t func_table[] = {
934 { "chdir", do_chdir, 1, 1, awk_false, NULL },
935 { "stat", do_stat, 3, 2, awk_false, NULL },
936 #ifndef __MINGW32__
937 { "fts", do_fts, 3, 3, awk_false, NULL },
938 #endif
939 #if defined(HAVE_SYS_STATVFS_H) && defined(HAVE_STATVFS)
940 { "statvfs", do_statvfs, 2, 2, awk_false, NULL },
941 #endif
942 };
943
944
945 /* define the dl_load function using the boilerplate macro */
946
947 dl_load_func(func_table, filefuncs, "")
948