/* * Copyright (c) 2024 DTB * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. * * This file incorporates work covered by the following copyright and permission * notice: * * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include /* errno */ #include /* UINT_MAX */ #include /* fprintf(3), perror(3), stderr, stdout */ #include /* free(3), realloc(3), strtol(3) */ #include /* stpcpy(3), strcmp(3), strerror(3), strlen(3) */ #if !defined EX_OK || !defined EX_INVALID || !defined EX_OSERR \ || !defined EX_USAGE # include #endif #include /* getopt(3) */ #include /* closedir(3), opendir(3), readdir(3), DIR, * struct dirent */ static char *program_name = ""; static char *dot[] = {".", NULL}; /* default (argc<2) */ static char *nul_terminator = "\0"; static char *newl_terminator = "\n"; static char *asv_terminator = "\037"; /* ASCII UNIT_SEPARATOR */ static char *terminator; static int fnprint(char *fn){ if(terminator == nul_terminator) return fprintf(stdout, "%s%c", fn, nul_terminator[0]); else return fprintf(stdout, "%s%s", fn, terminator); } /* Walks dirname, printing first its name and then the names of its files, * unless a file is a directory, in which case, as long as levels is -1 or * greater than 0, it recurses (passing the recurring walk a decremented * levels). argv0 is used for diagnostic messages; if it's NULL none are * printed. Returns EX_OK if all is well, EX_NOINPUT if dirname wasn't a * directory, and EX_OSERR if a memory allocation failed. */ static int walk(char *dirname, unsigned int levels, char *argv0){ DIR *dir; struct dirent *f; struct { size_t a; char *s; } filename = { 0, NULL }; size_t l; char *np; int retval; fnprint(dirname); if((dir = opendir(dirname)) == NULL) return EX_NOINPUT; errno = 0; while((f = readdir(dir)) != NULL){ if(strcmp(f->d_name, ".") == 0 || strcmp(f->d_name, "..") == 0) continue; /* fat pointer foolishness to avoid unnecessary reallocation; assembles * $DIR/$FILE */ if((l = strlen(dirname) + 1 + strlen(f->d_name) + 1) > filename.a){ if((np = realloc(filename.s, l)) == NULL){ free(filename.s); return EX_OSERR; } filename.a = l; filename.s = np; } stpcpy(stpcpy(stpcpy(filename.s, dirname), "/"), f->d_name); /* Walk the file if we can successfully open it as a directory. */ if((f->d_type == DT_DIR || f->d_type == DT_UNKNOWN) && levels != 0){ if((retval = walk(filename.s, levels == -1 ? -1 : levels - 1, argv0)) != EX_OK){ if(retval == EX_OSERR){ free(filename.s); return retval; } else if(retval != EX_NOINPUT && argv0 != NULL) fprintf(stderr, "%s: %s: %s\n", argv0, filename.s, strerror(errno)); } }else fnprint(filename.s); } if((errno != 0 || closedir(dir) != 0) && argv0 != NULL) fprintf(stderr, "%s: %s: %s\n", argv0, dirname, strerror(errno)); free(filename.s); return EX_OK; } int main(int argc, char *argv[]){ char *argv0; int c; int levels; int verbosity; argv0 = argv[0] == NULL ? program_name : argv[0]; levels = -1; /* no limit */ terminator = asv_terminator; verbosity = 2; if(argc > 0){ while((c = getopt(argc, argv, "0d:l:n")) != -1) switch(c){ case '0': terminator = nul_terminator; break; case 'n': terminator = newl_terminator; break; case 'd': terminator = optarg; break; case 'l': { long l; errno = 0; if((l = strtol(optarg, &optarg, 0) - 1) >= 1 && l <= INT_MAX && *optarg == '\0' && errno == 0){ levels = l; break; } } case 'q': --verbosity; break; default: fprintf(stderr, "Usage: %s (-0nq)" " (-d [delimiter]) (-l [max levels])" " (directories...)\n", argv[0]); return EX_USAGE; } argv += optind; } if(*argv == NULL) argv = dot; { int retval; for(retval = 0; *argv != NULL; ++argv) if((retval = walk(*argv, levels, verbosity >= 2 ? argv0 : NULL)) != EX_OK){ if(verbosity >= 1) fprintf(stderr, "%s: %s: %s\n", argv0, *argv, strerror(errno)); return retval; } } return EX_OK; }