From a2af41e2a41ea3df053456fa76f866be375efe7e Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Tue, 11 Feb 2020 18:36:15 -0800 Subject: [PATCH] Dump some old code up in here. --- .hgignore | 4 + LICENSE | 16 ++ README | 8 + doc/README.kst | 36 +++ doc/libdirutils.3 | 95 +++++++ doc/libiniparser.3 | 285 ++++++++++++++++++++ doc/srm.1 | 84 ++++++ include/kst/dirlist.h | 42 +++ include/kst/dirutils.h | 53 ++++ include/kst/iniparser.h | 51 ++++ include/kst/queue.h | 568 ++++++++++++++++++++++++++++++++++++++++ kte/defs.h | 2 + src/Makefile | 36 +++ src/dirlist.c | 121 +++++++++ src/dirlist_test.c | 212 +++++++++++++++ src/dirutils.c | 192 ++++++++++++++ src/dirutils_test.c | 267 +++++++++++++++++++ src/dirwalk.c | 163 ++++++++++++ src/iniparser.c | 280 ++++++++++++++++++++ src/iniparser_test.c | 96 +++++++ src/srm.c | 370 ++++++++++++++++++++++++++ 21 files changed, 2981 insertions(+) create mode 100644 LICENSE create mode 100644 README create mode 100644 doc/README.kst create mode 100644 doc/libdirutils.3 create mode 100644 doc/libiniparser.3 create mode 100644 doc/srm.1 create mode 100644 include/kst/dirlist.h create mode 100644 include/kst/dirutils.h create mode 100644 include/kst/iniparser.h create mode 100644 include/kst/queue.h create mode 100644 src/Makefile create mode 100644 src/dirlist.c create mode 100644 src/dirlist_test.c create mode 100644 src/dirutils.c create mode 100644 src/dirutils_test.c create mode 100644 src/dirwalk.c create mode 100644 src/iniparser.c create mode 100644 src/iniparser_test.c create mode 100644 src/srm.c diff --git a/.hgignore b/.hgignore index 0110280..9c694bf 100644 --- a/.hgignore +++ b/.hgignore @@ -1,2 +1,6 @@ .o$ ke/ke +kte/kte +.a$ +src/srm +src/iniparser-test diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ce9f494 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) 2011-2020 Kyle Isom + +Permission to use, copy, modify, and distribute this software for +any purpose with or without fee is hereby granted, provided that +the above copyright notice and this permission notice appear in all +copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + diff --git a/README b/README new file mode 100644 index 0000000..f3440a3 --- /dev/null +++ b/README @@ -0,0 +1,8 @@ +.:[ kst: kyle's standard/system tools ]:. + +This is stuff I use to get things done. The code is released open +source, but as it's tooling that I wrote for me, I'd be reticent +to receive patches that alter the functionality in ways that I don't +want. I also don't provide support for this. You can ask questions, +I probably won't answer. It's nothing personal, I just probably +don't have time. diff --git a/doc/README.kst b/doc/README.kst new file mode 100644 index 0000000..dd42197 --- /dev/null +++ b/doc/README.kst @@ -0,0 +1,36 @@ +srm - securely wipe files +-------------------------- +srm is a utility to overwrite files with random data in one or more passes. + + +Dependencies +------------ +None. + + +Compatibility +------------- +srm has been tested on the following operating systems: + * OpenBSD (5.1-snap) + * OS X (10.8) + * Linux (Debian 6.0) + + +Installation +------------ +make build install + + +Usage +----- +srm [-v] [-n number] file list + +options: + -n : specify number of passes + (default is 3 passes) + -v: verbose mode. display list of failures and wiped files after wiping + + +Known bugs / caveats +-------------------- +srm can't recursively remove files, i.e. it can't remove directories. diff --git a/doc/libdirutils.3 b/doc/libdirutils.3 new file mode 100644 index 0000000..b6ff0a7 --- /dev/null +++ b/doc/libdirutils.3 @@ -0,0 +1,95 @@ +.Dd November 20, 2012 +.Dt LIBDIRUTILS 3 +.Os +.Sh NAME +.Nm libdirutils +.Nd Python-esque file utility functions for C +.Sh SYNOPSIS +.In dirutils.h +.Ft int +.Fo makedirs +.Fa "const char *path" +.Fc +.Ft EXISTS_STATUS +.Fo path_exists +.Fa "const char *path" +.Fc +.Ft int +.Fo rmdirs +.Fa "const char *path" +.Fc +.Ft int +.Fo walkpath +.Fa "const char *root" +.Fa "dirwalk_action action" +.Fa "unsigned char mask" +.Sh DESCRIPTION +.Nm +provides a number of convenience functions for working with files and +directories; as the name implies, it is aimed primarily at directories. +.Nm makedirs +provides functionality similar to +.Ic mkdir -p , +creating all the parent directories required. They are created with the +caller's umask applied to the mode 0777. +.Nm path_exists +returns an EXISTS_STATUS value (described below) indicated the status +of the file. +.Nm rmdirs +provides functionality similar to +.Ic rm -r . +.Nm walkpath +walks through a path on disk and calls an action on a file if it +matches the file type mask. The mask should include some of the +file types listed in +.Xr readdir 3 , +such as DT_DIR or DT_REG. The convenience definitions +.Ic FT_ANY +and +.Ic FT_STD +are provided; the former matches any file type and the latter matches +regular files and directories. Note that this will not follow links. +The action is defined in the dirutils.h header as +.Ic typedef int (*dirwalk_action)(const char *) ; +it takes a NULL-terminated path and does what it will with that path. +It should return -1 on failure and 0 on success. Note that in the case +where the path provided to +.Nm walkdir +is not a directory, but its type matches the mask, the action will +still be run on it. +.Sh RETURN VALUES +.Nm makedirs +and +.Nm rmdirs +return EXIT_SUCCESS on success and EXIT_FAILURE on failure. The enum +returned by +.Nm path_exists +is defined in +.Sy dirutils.h +as: +.Bd -literal +enum E_EXISTS_STATUS { + EXISTS_ERROR, /* an error occurred looking at the file */ + EXISTS_NOENT, /* the path does not exist */ + EXISTS_NOPERM, /* the process does not have appropriate permissions */ + EXISTS_DIR, /* the path exists and is a directory */ + EXISTS_FILE, /* the path exists and is a regular file */ + EXISTS_OTHER /* the path exists and is not a directory or regular */ + /* file. */ +}; +.Ed +.Sh ERRORS +The most common error will be a permissions error; it may be prudent to +compare the value of +.Nm errno +before and after calling the function. +.Sh HISTORY +.Nm +was inspired by similar functions in the Python and Go programming languages. +The author found himself in need of such functions in several projects, +and decided to write a utility library to handle these functions. +.Sh AUTHORS +The +.Nm +library was written by +.An Kyle Isom Aq Mt kyle@tyrfingr.is . diff --git a/doc/libiniparser.3 b/doc/libiniparser.3 new file mode 100644 index 0000000..aa959f2 --- /dev/null +++ b/doc/libiniparser.3 @@ -0,0 +1,285 @@ +.Dd Sep 9, 2015 +.Dt LIBINIPARSER 3 +.Os +.Sh NAME +.Nm libiniparser +.Nd simple parser for .ini files +.Sh SYNOPSIS +.In stdint.h +.In iniparser/iniparser.h +.Ft int +.Fo iniparser_init +.Fa void +.Fc +.Ft void +.Fo iniparser_destroy +.Fa void +.Fc +.Ft int +.Fo iniparser_open +.Fa "const char *path" +.Fa "iniparser_file_s **file" +.Fc +.Ft int +.Fo iniparser_close +.Fa "iniparser_file_s *file" +.Fc +.Ft int +.Fo iniparser_readline +.Fa "iniparser_file_s *file" +.Fa "inipaser_line_s *line" +.Fc +.Ft void +.Fo iniparser_line_init +.Fa "iniparser_line_s *line" +.Fc +.Ft void +.Fo iniparser_line_destroy +.Fa "iniparser_line_s *line" +.Fc +.Sh DESCRIPTION +.Nm +is a parsing library for .ini files. An ini file is of the form +.Bd -literal +[ section1 ] +keyA = valueA +keyB = valueB + +[ section2 ] +keyA = valueA +keyB = valueB +keyC = +.Ed +.Ss OVERVIEW +The library is initialised with +.Nm iniparser_init , +which will +.Sy must +be called before using the library. When no longer needed, the +.Nm libparser_destroy +function should be called to free resources. +Configuration files are represented by the +.Nm iniparser_file_s structure, which is defined as +.Bd -literal +typedef struct { + FILE *source; + char *lineptr; + size_t linelen; + ssize_t readlen; +} iniparser_file_s; +.Ed +.Pp +A configuration file may be opened with +.Nm iniparser_open , +which if given a pointer to a NULL pointer to an +.Nm iniparser_file_s , +will allocate space using +.Xr calloc 3 . +The caller must be sure then to free this memory when it is no longer +needed. Callers may also explicitly set the +.Ic source +pointer to the appropriate source file handle, and ensure that the +other fields are at zero values. Similarly, +.Nm iniparser_close +will close the source file handle and free any memory allocated inside +the structure. Users should still ensure that the structure itself is +freed as required. +.Nm iniparser_readline +will attempt to fill in the results of parsing the next line in the +configuration file. Note that it will keep reading lines, skipping +comments and blank lines, until it reads a valid section or key-value +line or until it encounters an error. The +.Nm iniparser_line_s +structure is defined as +.Bd -literal +typedef struct { + uint8_t is_section; + uint8_t is_set; + char *name; + char *value; +} iniparser_line_s; +.Ed +.Pp +A line should be initialised before first use with +.Nm iniparser_line_init , +and should be destroyed after last use with +.Nm iniparser_line_destroy . +The fields are defined as: +.Bl -bullet -width Ds +.It +The is_section field will be set to 1 if the line was a new section. +.It +The is_set field will be set to 1 if valid data was set. This is +useful when an EOF is reached. +.It +The name field contains the section name if the is_section field is 1, +or the key if is_section is 0 and is_set is 1. It should be +disregarded outside of these two cases. +.It +The value field contains the key's value if is_section is 0 and is_set +is 1. It should be disregarded otherwise. If it is NULL, the key had +no value. +.El +Both the name and value field will have leading and trailing spaces +stripped. The line that is passed in is reset and its memory freed +every time +.Nm iniparser_readline +is run. Callers should copy any relevant data from the line elsewhere. +.Ss TYPICAL USAGE +Processing a file +generally follows the following steps: +.Bl -bullet +.It +Call +.Nm iniparser_open +with the path to the ini file and a pointer to an +.Nm iniparser_file_s +structure that will be used to manage the file parsing. +.It +Prepare the line storage variable with +.Nm iniparser_line_init . +.It +Call +.Nm iniparser_readline +to obtain successive values of the file. +.It +While iniparser_readline continues to succeed, keep processing the +results. +.It +Clean up with the following sequence: +.Bl -enum +.It +.Nm iniparser_line_destroy +.It +.Nm iniparser_close +.It +If the parser is no longer needed, +.Nm iniparser_destroy . +Once this is called, the library cannot be used until +.Nm iniparser_init +is called again. +.El +.El +.Ss THE REGULAR EXPRESSIONS +The following regular expressions are used: +.Bl -tag -width Ds +.It sections +"^\\[ *([a-zA-Z0-9_-]+) *\\]$" +.It blank lines +"^[ \t]*$" +.It comments +"^[ \t]*[#;]" +.It key-value lines +"^[ \t]*([a-zA-Z0-9_-]+)[ \t]*=(.*)$" +.El +.Sh RETURN VALUES +.Nm iniparser_init , +.Nm iniparser_open , +and +.Nm iniparser_readline +all return 0 on success and -1 on failure. Additionally, +.Nm iniparser_readline +uses 1 to signal EOF; the line value should be checked to determine if +there is new data and the file closed. +.Sh EXAMPLES +The following program will parse the files specified on the command +line and print sections and key/value pairs. +.Bd -literal +#include +#include +#include + +#include "iniparser/iniparser.h" + + +int +main(int argc, char *argv[]) +{ + iniparser_file_s *file = NULL; + iniparser_line_s line; + int i; + int ret; + + ret = iniparser_init(); + if (0 != ret) { + fprintf(stderr, "init failed: %d\n", ret); + goto exit; + } + + argc--; + argv++; + + for (i = 0; i < argc; i++) { + printf("Processing %s\n", argv[i]); + ret = iniparser_open(argv[i], &file); + if (0 != ret) { + perror("_open"); + fprintf(stderr, "retval: %d\n", ret); + goto exit; + } + iniparser_line_init(&line); + + while (1) { + ret = iniparser_readline(file, &line); + /* -1 is returned on error. */ + if (-1 == ret) { + perror("_readline"); + fprintf(stderr, "retval: %d\n", ret); + goto exit; + } + /* 1 means EOF. */ + else if (1 == ret) { + ret = 0; + break; + } + + if (line.is_section) { + printf("Now in section '%s'\n", line.name); + } + else { + printf("Read key '%s' with value '%s'\n", + line.name, line.value); + } + + iniparser_line_destroy(&line); + } + + iniparser_close(file); + free(file); + file = NULL; + iniparser_line_destroy(&line); + } + +exit: + iniparser_line_destroy(&line); + if (argc > 0) { + iniparser_destroy(); + } + + return ret==0; +} +.Ed +.Sh ERRORS +.Nm iniparser_open +will fail if its +.Ic file +argument is NULL, the call to +.Xr calloc 3 +fails, or the source file could not be opened. Note that in the case +where memory is allocated by the library, it will be freed on exit. +.Pp +.Nm iniparser_close +returns the exit value of the call to +.Xr fclose 3 . +.Pp +.Nm iniparser_readline +will fail if there are any improperly formatted lines; comments and +blank lines will be skipped. It will also fail if it fails to read +a line from the file. +.Sh AUTHORS +.Nm +was written by +.An Kyle Isom Aq Mt kyle@tyrfingr.is . +.Sh LICENSE +.Nm +is released under the MIT license. diff --git a/doc/srm.1 b/doc/srm.1 new file mode 100644 index 0000000..3837af0 --- /dev/null +++ b/doc/srm.1 @@ -0,0 +1,84 @@ +.Dd $Mdocdate$ +.Dt SRM 1 +.Os +.Sh NAME +.Nm srm +.Nd securely delete files +.Sh SYNOPSIS +.Nm +.Op Fl h +.Op Fl n Ar number +.Op Fl r +.Op Fl v +.Op Fl V +.Ar files +.Sh DESCRIPTION +.Nm +is a simple secure file deletion tool. It overwrites the file with several +passes of random data before unlinking it. If no options are specified, Nm +defaults to three passes. +.Nm +supports the following options: +.Bl -tag -width .Ds +.It Fl h +Display a brief help message. +.It Fl n Ar number +Specify the number of times to overwrite each target with random data. +.It Fl r +Recursive mode. Delete any directories and all subdirectories underneath. +.It Fl v +Verbose mode. Displays a list of both files that failed to wipe and files that +were successfully wiped. +.It Fl V +Print version information. +.El +.Sh EXIT STATUS +.Ex -std +The exit values are standard +.Xr sysexits 3 +values. +.Sh EXAMPLES +Wipe files +.Pa foo +and +.Pa bar +with three passes: +.Dl $ srm foo bar +Wipe files +.Pa baz +and +.Pa quux +with ten passes: +.Dl $ srm -n 10 baz quux +Wipe all PGP keys, i.e. files with extension +.Pa *.asc : +.Dl $ srm *.asc +Recursive deletes aren't implemented yet. A workaround is to use +.Nm +and +.Xr find 1 , +for example to delete all +.Pa *.pgp +files: +.Dl $ find . -iname '*.pgp' -exec srm '{}' \; +.Sh DIAGNOSTICS +.Nm +uses the standard +.Xr err 3 +facilities to report any errors that occur. +.Sh SEE ALSO +The srm page on +.Lk http://www.tyrfingr.is/projects/srm/ "tyrfinger" . +.Sh STANDARDS +.Nm +conforms to +.St -ansiC . +.Sh AUTHORS +.Nm +was written by +.An "Kyle Isom" Aq Mt kyle@tyrfingr.is . +.Sh BUGS +None known. Report bugs to the author. +.Sh LICENSE +.Nm +is released under an ISC license. diff --git a/include/kst/dirlist.h b/include/kst/dirlist.h new file mode 100644 index 0000000..313187a --- /dev/null +++ b/include/kst/dirlist.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * --------------------------------------------------------------------- + */ + + +#ifndef __DIRUTILS_DIRLIST_H +#define __DIRUTILS_DIRLIST_H + + +#include +#include +#include "queue.h" + + +struct dirlst { + char path[FILENAME_MAX + 1]; + TAILQ_ENTRY(dirlst) dirs; +}; +TAILQ_HEAD(tq_dirlst, dirlst); + + +struct tq_dirlst *dirlst_create(const char *, size_t); +int dirlst_push(struct tq_dirlst *, const char *, size_t); +struct dirlst *dirlst_pop(struct tq_dirlst *); +int dirlst_destroy(struct tq_dirlst **); + + +#endif diff --git a/include/kst/dirutils.h b/include/kst/dirutils.h new file mode 100644 index 0000000..be2a78f --- /dev/null +++ b/include/kst/dirutils.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2012 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * --------------------------------------------------------------------- + */ + + +#ifndef __DIRUTILS_DIRUTILS_H +#define __DIRUTILS_DIRUTILS_H + + +#include +#include +#include + +enum E_EXISTS_STATUS { + EXISTS_ERROR, + EXISTS_NOENT, + EXISTS_NOPERM, + EXISTS_DIR, + EXISTS_FILE, + EXISTS_OTHER +}; +typedef enum E_EXISTS_STATUS EXISTS_STATUS; + + +typedef int (*dirwalk_action)(const char *); + + +extern const unsigned char FT_ANY; +extern const unsigned char FT_STD; +extern const unsigned char FT_NODESCEND; + + +int makedirs(const char *); +int rmdirs(const char *); +EXISTS_STATUS path_exists(const char *); +int walkdir(const char *, dirwalk_action, unsigned char); + + +#endif diff --git a/include/kst/iniparser.h b/include/kst/iniparser.h new file mode 100644 index 0000000..55f819f --- /dev/null +++ b/include/kst/iniparser.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015 Kyle Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __LIBINIPARSER_INIPARSER_H +#define __LIBINIPARSER_INIPARSER_H + + +typedef struct { + FILE *source; + char *lineptr; + size_t linelen; + ssize_t readlen; +} iniparser_file_s; + +typedef struct { + uint8_t is_section; + uint8_t is_set; + char *name; + char *value; +} iniparser_line_s; + + +int iniparser_init(void); +void iniparser_destroy(void); +int iniparser_open(const char *, iniparser_file_s **); +int iniparser_close(iniparser_file_s *); +int iniparser_readline(iniparser_file_s *, iniparser_line_s *); +void iniparser_line_init(iniparser_line_s *); +void iniparser_line_destroy(iniparser_line_s *); + + +#endif diff --git a/include/kst/queue.h b/include/kst/queue.h new file mode 100644 index 0000000..9899234 --- /dev/null +++ b/include/kst/queue.h @@ -0,0 +1,568 @@ +/* $OpenBSD: queue.h,v 1.36 2012/04/11 13:29:14 naddy Exp $ */ +/* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.5 (Berkeley) 8/20/94 + */ + +#ifndef _SYS_QUEUE_H_ +#define _SYS_QUEUE_H_ + +/* + * This file defines five types of data structures: singly-linked lists, + * lists, simple queues, tail queues, and circular queues. + * + * + * A singly-linked list is headed by a single forward pointer. The elements + * are singly linked for minimum space and pointer manipulation overhead at + * the expense of O(n) removal for arbitrary elements. New elements can be + * added to the list after an existing element or at the head of the list. + * Elements being removed from the head of the list should use the explicit + * macro for this purpose for optimum efficiency. A singly-linked list may + * only be traversed in the forward direction. Singly-linked lists are ideal + * for applications with large datasets and few or no removals or for + * implementing a LIFO queue. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before + * or after an existing element or at the head of the list. A list + * may only be traversed in the forward direction. + * + * A simple queue is headed by a pair of pointers, one the head of the + * list and the other to the tail of the list. The elements are singly + * linked to save space, so elements can only be removed from the + * head of the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the + * list. A simple queue may only be traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) +#define _Q_INVALIDATE(a) (a) = ((void *)-1) +#else +#define _Q_INVALIDATE(a) +#endif + +/* + * Singly-linked List definitions. + */ +#define SLIST_HEAD(name, type) \ +struct name { \ + struct type *slh_first; /* first element */ \ +} + +#define SLIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define SLIST_ENTRY(type) \ +struct { \ + struct type *sle_next; /* next element */ \ +} + +/* + * Singly-linked List access methods. + */ +#define SLIST_FIRST(head) ((head)->slh_first) +#define SLIST_END(head) NULL +#define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) +#define SLIST_NEXT(elm, field) ((elm)->field.sle_next) + +#define SLIST_FOREACH(var, head, field) \ + for((var) = SLIST_FIRST(head); \ + (var) != SLIST_END(head); \ + (var) = SLIST_NEXT(var, field)) + +#define SLIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SLIST_FIRST(head); \ + (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Singly-linked List functions. + */ +#define SLIST_INIT(head) { \ + SLIST_FIRST(head) = SLIST_END(head); \ +} + +#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ + (elm)->field.sle_next = (slistelm)->field.sle_next; \ + (slistelm)->field.sle_next = (elm); \ +} while (0) + +#define SLIST_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.sle_next = (head)->slh_first; \ + (head)->slh_first = (elm); \ +} while (0) + +#define SLIST_REMOVE_AFTER(elm, field) do { \ + (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE_HEAD(head, field) do { \ + (head)->slh_first = (head)->slh_first->field.sle_next; \ +} while (0) + +#define SLIST_REMOVE(head, elm, type, field) do { \ + if ((head)->slh_first == (elm)) { \ + SLIST_REMOVE_HEAD((head), field); \ + } else { \ + struct type *curelm = (head)->slh_first; \ + \ + while (curelm->field.sle_next != (elm)) \ + curelm = curelm->field.sle_next; \ + curelm->field.sle_next = \ + curelm->field.sle_next->field.sle_next; \ + _Q_INVALIDATE((elm)->field.sle_next); \ + } \ +} while (0) + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_HEAD_INITIALIZER(head) \ + { NULL } + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List access methods + */ +#define LIST_FIRST(head) ((head)->lh_first) +#define LIST_END(head) NULL +#define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) +#define LIST_NEXT(elm, field) ((elm)->field.le_next) + +#define LIST_FOREACH(var, head, field) \ + for((var) = LIST_FIRST(head); \ + (var)!= LIST_END(head); \ + (var) = LIST_NEXT(var, field)) + +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST(head); \ + (var) && ((tvar) = LIST_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * List functions. + */ +#define LIST_INIT(head) do { \ + LIST_FIRST(head) = LIST_END(head); \ +} while (0) + +#define LIST_INSERT_AFTER(listelm, elm, field) do { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.le_prev = (listelm)->field.le_prev; \ + (elm)->field.le_next = (listelm); \ + *(listelm)->field.le_prev = (elm); \ + (listelm)->field.le_prev = &(elm)->field.le_next; \ +} while (0) + +#define LIST_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} while (0) + +#define LIST_REMOVE(elm, field) do { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +#define LIST_REPLACE(elm, elm2, field) do { \ + if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ + (elm2)->field.le_next->field.le_prev = \ + &(elm2)->field.le_next; \ + (elm2)->field.le_prev = (elm)->field.le_prev; \ + *(elm2)->field.le_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.le_prev); \ + _Q_INVALIDATE((elm)->field.le_next); \ +} while (0) + +/* + * Simple queue definitions. + */ +#define SIMPLEQ_HEAD(name, type) \ +struct name { \ + struct type *sqh_first; /* first element */ \ + struct type **sqh_last; /* addr of last next element */ \ +} + +#define SIMPLEQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).sqh_first } + +#define SIMPLEQ_ENTRY(type) \ +struct { \ + struct type *sqe_next; /* next element */ \ +} + +/* + * Simple queue access methods. + */ +#define SIMPLEQ_FIRST(head) ((head)->sqh_first) +#define SIMPLEQ_END(head) NULL +#define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) +#define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) + +#define SIMPLEQ_FOREACH(var, head, field) \ + for((var) = SIMPLEQ_FIRST(head); \ + (var) != SIMPLEQ_END(head); \ + (var) = SIMPLEQ_NEXT(var, field)) + +#define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = SIMPLEQ_FIRST(head); \ + (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +/* + * Simple queue functions. + */ +#define SIMPLEQ_INIT(head) do { \ + (head)->sqh_first = NULL; \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (head)->sqh_first = (elm); \ +} while (0) + +#define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.sqe_next = NULL; \ + *(head)->sqh_last = (elm); \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ + (head)->sqh_last = &(elm)->field.sqe_next; \ + (listelm)->field.sqe_next = (elm); \ +} while (0) + +#define SIMPLEQ_REMOVE_HEAD(head, field) do { \ + if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ + (head)->sqh_last = &(head)->sqh_first; \ +} while (0) + +#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ + if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ + == NULL) \ + (head)->sqh_last = &(elm)->field.sqe_next; \ +} while (0) + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_HEAD_INITIALIZER(head) \ + { NULL, &(head).tqh_first } + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * tail queue access methods + */ +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +/* XXX */ +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) +#define TAILQ_EMPTY(head) \ + (TAILQ_FIRST(head) == TAILQ_END(head)) + +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) + +#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_NEXT(var, field), 1); \ + (var) = (tvar)) + + +#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ + for((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_PREV(var, headname, field)) + +#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = TAILQ_LAST(head, headname); \ + (var) != TAILQ_END(head) && \ + ((tvar) = TAILQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} while (0) + +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +#define TAILQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ + (elm2)->field.tqe_next->field.tqe_prev = \ + &(elm2)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm2)->field.tqe_next; \ + (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ + *(elm2)->field.tqe_prev = (elm2); \ + _Q_INVALIDATE((elm)->field.tqe_prev); \ + _Q_INVALIDATE((elm)->field.tqe_next); \ +} while (0) + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_HEAD_INITIALIZER(head) \ + { CIRCLEQ_END(&head), CIRCLEQ_END(&head) } + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue access methods + */ +#define CIRCLEQ_FIRST(head) ((head)->cqh_first) +#define CIRCLEQ_LAST(head) ((head)->cqh_last) +#define CIRCLEQ_END(head) ((void *)(head)) +#define CIRCLEQ_NEXT(elm, field) ((elm)->field.cqe_next) +#define CIRCLEQ_PREV(elm, field) ((elm)->field.cqe_prev) +#define CIRCLEQ_EMPTY(head) \ + (CIRCLEQ_FIRST(head) == CIRCLEQ_END(head)) + +#define CIRCLEQ_FOREACH(var, head, field) \ + for((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_NEXT(var, field)) + +#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = CIRCLEQ_FIRST(head); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_NEXT(var, field), 1); \ + (var) = (tvar)) + +#define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ + for((var) = CIRCLEQ_LAST(head); \ + (var) != CIRCLEQ_END(head); \ + (var) = CIRCLEQ_PREV(var, field)) + +#define CIRCLEQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ + for ((var) = CIRCLEQ_LAST(head, headname); \ + (var) != CIRCLEQ_END(head) && \ + ((tvar) = CIRCLEQ_PREV(var, headname, field), 1); \ + (var) = (tvar)) + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) do { \ + (head)->cqh_first = CIRCLEQ_END(head); \ + (head)->cqh_last = CIRCLEQ_END(head); \ +} while (0) + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = CIRCLEQ_END(head); \ + if ((head)->cqh_last == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} while (0) + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.cqe_next = CIRCLEQ_END(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} while (0) + +#define CIRCLEQ_REMOVE(head, elm, field) do { \ + if ((elm)->field.cqe_next == CIRCLEQ_END(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == CIRCLEQ_END(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#define CIRCLEQ_REPLACE(head, elm, elm2, field) do { \ + if (((elm2)->field.cqe_next = (elm)->field.cqe_next) == \ + CIRCLEQ_END(head)) \ + (head).cqh_last = (elm2); \ + else \ + (elm2)->field.cqe_next->field.cqe_prev = (elm2); \ + if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) == \ + CIRCLEQ_END(head)) \ + (head).cqh_first = (elm2); \ + else \ + (elm2)->field.cqe_prev->field.cqe_next = (elm2); \ + _Q_INVALIDATE((elm)->field.cqe_prev); \ + _Q_INVALIDATE((elm)->field.cqe_next); \ +} while (0) + +#endif /* !_SYS_QUEUE_H_ */ diff --git a/kte/defs.h b/kte/defs.h index 17d9565..3eccc6f 100644 --- a/kte/defs.h +++ b/kte/defs.h @@ -7,7 +7,9 @@ #ifndef KTE_DEFS_H #define KTE_DEFS_H + #include +#include #define KTE_TAB_STOP 8 diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..c6d57bd --- /dev/null +++ b/src/Makefile @@ -0,0 +1,36 @@ +BINS := srm +EDS := ke kte +LIBS := libdirutils.a libiniparser.a + +LDFLAGS := +CFLAGS := -pedantic -Wall -Werror -Wextra -O0 -std=c99 -g -I../include/ + +.PHONY: all +all: $(BINS) $(EDS) $(LIBS) + +.PHONY: clean +clean: + rm -f $(BINS) $(EDS) $(LIBS) *.o *.core + + +ke kte: + cd ../$@ && make && cp $@ ../src + +srm: srm.c + $(CC) $(CFLAGS) -o $@ srm.c + +libiniparser.a: iniparser.o + $(AR) -rcs $@ iniparser.o + +iniparser-test: iniparser_test.c libiniparser.a + $(CC) $(CFLAGS) -o $@ iniparser_test.c libiniparser.a + +libdirutils.a: dirlist.o dirutils.o dirwalk.o + $(AR) -rcs $@ dirlist.o dirutils.o dirwalk.o + +dirlist-test: dirlist_test.c libdirutils.a + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ dirlist_test.c libdirutils.a + +dirutils-test: dirutils_test.c libdirutils.a + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ dirutils_test.c libdirutils.a + diff --git a/src/dirlist.c b/src/dirlist.c new file mode 100644 index 0000000..0b0e41f --- /dev/null +++ b/src/dirlist.c @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2012 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * --------------------------------------------------------------------- + */ + +#include +#include + +#include +#include +#include +#include + +#include "kst/dirlist.h" + + +/* + * create and initialise the directory list. + */ +struct tq_dirlst +*dirlst_create(const char *init_path, size_t init_path_sz) +{ + struct tq_dirlst *path_lst; + struct dirlst *elm; + + if (init_path_sz > FILENAME_MAX) { + return NULL; + } + + path_lst = calloc(1, sizeof(struct tq_dirlst)); + if (NULL == path_lst) { + return path_lst; + } + + elm = calloc(1, sizeof(struct dirlst)); + if (NULL == elm) { + free(path_lst); + return NULL; + } + memcpy(elm->path, init_path, init_path_sz); + + TAILQ_INIT(path_lst); + TAILQ_INSERT_HEAD(path_lst, elm, dirs); + + return path_lst; +} + + +/* + * add a new directory to the list. + */ +int +dirlst_push(struct tq_dirlst *lst, const char *new_dir, size_t new_dir_sz) +{ + struct dirlst *elm; + + if (new_dir_sz > FILENAME_MAX) { + return EXIT_FAILURE; + } + + elm = calloc(1, sizeof(struct dirlst)); + if (NULL == elm) { + return EXIT_FAILURE; + } + + strncpy(elm->path, new_dir, FILENAME_MAX); + TAILQ_INSERT_HEAD(lst, elm, dirs); + return EXIT_SUCCESS; +} + + +/* + * remove the first directory in the list and return it + */ +struct dirlst +*dirlst_pop(struct tq_dirlst *lst) +{ + struct dirlst *elm; + + if (TAILQ_EMPTY(lst)) { + return NULL; + } + + elm = TAILQ_FIRST(lst); + TAILQ_REMOVE(lst, elm, dirs); + return elm; +} + + +int +dirlst_destroy(struct tq_dirlst **lstp) +{ + struct dirlst *elm; + + while ((elm = TAILQ_FIRST(*lstp))) { + TAILQ_REMOVE(*lstp, elm, dirs); + free(elm); + } + + if (!TAILQ_EMPTY(*lstp)) { + return EXIT_FAILURE; + } + + free(*lstp); + *lstp = NULL; + return EXIT_SUCCESS; +} + diff --git a/src/dirlist_test.c b/src/dirlist_test.c new file mode 100644 index 0000000..2f2eb1b --- /dev/null +++ b/src/dirlist_test.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2012 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * --------------------------------------------------------------------- + */ + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* + * helper that, given a list and the expected key, determines whether + * the the popped path matches. Return values use if semantics: success + * is 1, failure is 0. It is up to the caller to ensure that keylen is + * in the acceptable range (FILENAME_MAX) for tests that are to be + * successful. + */ +int +test_pop_helper(struct tq_dirlst *lst, const char *key, size_t keylen) +{ + struct dirlst *elm; + int match; + + if (keylen > FILENAME_MAX) + return 0; + + elm = dirlst_pop(lst); + if (NULL == elm) + return 0; + match = strncmp(elm->path, key, keylen); + free(elm); + return 0 == match; +} + + +void +test_dirlst_create(void) +{ + struct tq_dirlst *lst; + + lst = dirlst_create((const char *)"foo", 3); + CU_ASSERT(lst != NULL); + CU_ASSERT(EXIT_SUCCESS == dirlst_destroy(&lst)); +} + +void +test_dirlst_single(void) +{ + struct tq_dirlst *lst; + struct dirlst *elm; + int n_elms, ret; + + lst = dirlst_create((const char *)"foo", 3); + ret = dirlst_push(lst, (const char *)"bar", 3); + CU_ASSERT(EXIT_SUCCESS == ret); + n_elms = 0; + TAILQ_FOREACH(elm, lst, dirs) + n_elms++; + + CU_ASSERT(2 == n_elms); + + CU_ASSERT(test_pop_helper(lst, "bar", 3)); + n_elms = 0; + TAILQ_FOREACH(elm, lst, dirs) + n_elms++; + + CU_ASSERT(1 == n_elms); + CU_ASSERT(EXIT_SUCCESS == dirlst_destroy(&lst)); +} + + +/* + * ensure an invalid path_len aborts the push + */ +void +test_dirlst_bad(void) +{ + struct tq_dirlst *lst; + + lst = dirlst_create((const char *)"foo", 3); + CU_ASSERT(EXIT_SUCCESS == dirlst_push(lst, "bar", 3)); + CU_ASSERT(EXIT_FAILURE == dirlst_push(lst, "baz", FILENAME_MAX * 2)); + CU_ASSERT(test_pop_helper(lst, "bar", 3)); + CU_ASSERT(EXIT_SUCCESS == dirlst_destroy(&lst)); +} + + +void +test_dirlst_multi(void) +{ + char testkeys[][4] = {"bar", "baz", "quux", ""}; + int testkeylen[4]; + struct tq_dirlst *lst; + struct dirlst *elm; + int i, n_elms, ret; + + lst = dirlst_create((const char *)"foo", 3); + for(i = 0; i < 3; ++i) { + testkeylen[i] = strlen(testkeys[i]); + ret = dirlst_push(lst, testkeys[i], testkeylen[i]); + CU_ASSERT(EXIT_SUCCESS == ret); + } + + n_elms = 0; + TAILQ_FOREACH(elm, lst, dirs) + n_elms++; + CU_ASSERT(4 == n_elms); + + for (i = 2; i >= 0; i--) + CU_ASSERT(test_pop_helper(lst, testkeys[i], testkeylen[i])); + CU_ASSERT(test_pop_helper(lst, "foo", 3)); + CU_ASSERT(EXIT_SUCCESS == dirlst_destroy(&lst)); +} + + +/* + * Stubs required by the test suite, but for which no functionality is + * required in this code. init_test is called each time a test is run, + * and cleanup is run after every test. + */ +int init_test(void) +{ + return 0; +} + +int cleanup_test(void) +{ + return 0; +} + + +/* + * fireball is the code called when adding test fails: cleanup the test + * registry and exit. + */ +void +fireball(void) +{ + CU_cleanup_registry(); + exit(CU_get_error()); +} + + +/* + * The main function sets up the test suite, registers the test cases, + * runs through them, and hopefully doesn't explode. + */ +int +main(void) +{ + CU_pSuite tsuite = NULL; + unsigned int fails; + + printf("\n\n[+] running tests for dirutils\n"); + + if (!(CUE_SUCCESS == CU_initialize_registry())) { + errx(EX_CONFIG, "failed to initialise test registry"); + return EXIT_FAILURE; + } + + tsuite = CU_add_suite(TEST_SUITE, init_test, cleanup_test); + if (NULL == tsuite) + fireball(); + + if (NULL == CU_add_test(tsuite, "dirlst create", test_dirlst_create)) + fireball(); + + if (NULL == CU_add_test(tsuite, "single push/pop", test_dirlst_single)) + fireball(); + + if (NULL == CU_add_test(tsuite, "multi push/pop", test_dirlst_multi)) + fireball(); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + fails = CU_get_number_of_tests_failed(); + warnx("%u tests failed", fails); + + CU_cleanup_registry(); + return fails; +} + + +/* + * This is an empty test provided for reference. + */ +void +empty_test() +{ + CU_ASSERT(1 == 0); +} diff --git a/src/dirutils.c b/src/dirutils.c new file mode 100644 index 0000000..a05baf3 --- /dev/null +++ b/src/dirutils.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2012 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * --------------------------------------------------------------------- + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kst/dirutils.h" +#include "kst/dirlist.h" + + +static int _parent_exists(const char *); +static int _rmdirs(const char *); + +/* + * Determines whether a directory exists. + */ +EXISTS_STATUS +path_exists(const char *path) +{ + struct stat st; + int rv; + + rv = stat(path, &st); + if (rv == -1) { + switch (errno) { + case EACCES: + return EXISTS_NOPERM; + break; + case ENOENT: + return EXISTS_NOENT; + break; + default: + return EXISTS_ERROR; + } + } + + if (st.st_mode & S_IFDIR) { + return EXISTS_DIR; + } + else if (st.st_mode & S_IFREG) { + return EXISTS_FILE; + } + else { + return EXISTS_OTHER; + } +} + + +/* + * create a path and any directories above it required + */ +int +makedirs(const char *path) +{ + struct tq_dirlst *lst; + struct dirlst *elm; + size_t path_sz; + char *dnam_p, *curpath; + + path_sz = strlen(path); + lst = dirlst_create(path, path_sz); + if (NULL == lst) + return EXIT_FAILURE; + + curpath = strdup(path); + while (!_parent_exists(curpath)) { + dnam_p = dirname(curpath); + curpath = strdup(dnam_p); + dirlst_push(lst, curpath, strlen(curpath)); + } + free(curpath); + + while (NULL != (elm = dirlst_pop(lst))) { + if (-1 == mkdir(elm->path, 0777)) { + free(elm); + return EXIT_FAILURE; + } + free(elm); + } + + return dirlst_destroy(&lst); +} + + +/* + * remove a directory and any subdirectories + */ +int +rmdirs(const char *path) +{ + return _rmdirs(path); +} + + +/* + * Determine whether the parent directory exists, and return true if it + * does. + */ +int +_parent_exists(const char *path) +{ + char *name_buf; + char *dnam_p; + + name_buf = strdup(path); + + dnam_p = dirname(name_buf); + if (EXISTS_DIR != path_exists(dnam_p)) { + return 0; + } + else { + return 1; + } +} + + +/* + * remove a directory, all files in it, and all subdirectories. + */ +int +_rmdirs(const char *path) +{ + char child[FILENAME_MAX + 1]; + struct dirent *dp; + DIR *dirp; + int fail; + + if (NULL == (dirp = opendir(path))) { + return EXIT_FAILURE; + } + + while (NULL != (dp = readdir(dirp))) { + if (0 == strncmp("..", dp->d_name, 3)) { + continue; + } + + if (0 == strncmp(".", dp->d_name, 2)) { + continue; + } + + + snprintf(child, FILENAME_MAX, "%s/%s", path, dp->d_name); + if (DT_DIR == dp->d_type) { + fail = _rmdirs(child); + if (EXIT_FAILURE == fail) { + break; + } + } + else { + fail = unlink(child); + if (-1 == fail) { + fail = EXIT_FAILURE; + break; + } + } + } + if (-1 == closedir(dirp)) + return EXIT_FAILURE; + if (EXIT_FAILURE == fail) { + return EXIT_FAILURE; + } + else if (-1 == rmdir(path)) { + return EXIT_FAILURE; + } + else { + return EXIT_SUCCESS; + } +} diff --git a/src/dirutils_test.c b/src/dirutils_test.c new file mode 100644 index 0000000..2a12e07 --- /dev/null +++ b/src/dirutils_test.c @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2012 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * --------------------------------------------------------------------- + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int test_write_file_helper(const char *, const char *); +static int test_touch_file_helper(const char *); + +/* + * test the use of the exists function + */ +void +test_exists(void) +{ + char testdir[] = "testdata/testdir"; + char testfil[] = "testdata/testfile"; + char testnot[] = "testdata/nosuchfile"; + EXISTS_STATUS ftype; + + ftype = path_exists(testdir); + CU_ASSERT(EXISTS_DIR == ftype); + + ftype = path_exists(testfil); + CU_ASSERT(EXISTS_FILE == ftype); + + ftype = path_exists(testnot); + CU_ASSERT(EXISTS_NOENT == ftype); +} + +void +test_makedirs(void) +{ + char testpath[] = "testdata/foo/bar/baz\0"; + + /* + * use the system to ensure we have a clean slate for this test + */ + if (EXISTS_DIR == path_exists(testpath)) + system("rm -fr testdata/foo/"); + CU_ASSERT(EXIT_SUCCESS == makedirs(testpath)); + CU_ASSERT(EXISTS_DIR == path_exists(testpath)); + /* + * we can't guarantee rmdirs yet; this ensures a clean slate. + */ + system("rm -r testdata/foo/"); +} + +void +test_empty_rmdirs(void) +{ + char testpath[20] = "testdata/foo"; + char cmd[FILENAME_MAX]; + int rv; + + memset(cmd, 0x0, FILENAME_MAX); + snprintf(cmd, FILENAME_MAX, "mkdir -p %s/bar/baz", testpath); + system(cmd); + rv = rmdirs(testpath); + if (EXIT_FAILURE == rv) { + printf("\n"); + warn("rmdirs"); + system("rm -fr testdata/foo/"); + } + CU_ASSERT(EXIT_SUCCESS == rv); + CU_ASSERT(EXISTS_NOENT == path_exists(testpath)); + /* + * we can't guarantee rmdirs yet; this ensures a clean slate. + */ +} + + +void +test_rmdirs_simple(void) +{ + char testpath[] = "testdata/foo"; + char cmd[FILENAME_MAX]; + int rv; + + memset(cmd, 0x0, FILENAME_MAX); + snprintf(cmd, FILENAME_MAX, "mkdir -p %s/bar/baz", testpath); + system(cmd); + memset(cmd, 0x0, FILENAME_MAX); + snprintf(cmd, FILENAME_MAX, "touch %s/bar/quux", testpath); + system(cmd); + rv = rmdirs(testpath); + if (EXIT_FAILURE == rv) { + printf("\n"); + warn("rmdirs"); + /* + * we can't guarantee rmdirs yet; this ensures a clean slate. + */ + system("rm -r testdata/foo/ 2>/dev/null"); + } + CU_ASSERT(EXIT_SUCCESS == rv); + CU_ASSERT(EXISTS_NOENT == path_exists(testpath)); +} + + +void +test_dirutils(void) +{ + char testpath[] = "testdata/dirutils"; + char tmp_path[FILENAME_MAX + 1]; + char lnk_path[FILENAME_MAX + 1]; + + /* set up directory structure */ + CU_ASSERT(EXISTS_NOENT == path_exists(testpath)); + snprintf(tmp_path, FILENAME_MAX, "%s/foo/bar", testpath); + CU_ASSERT(EXIT_SUCCESS == makedirs(tmp_path)); + CU_ASSERT(EXISTS_DIR == path_exists(tmp_path)); + snprintf(tmp_path, FILENAME_MAX, "%s/foo/baz", testpath); + CU_ASSERT(EXIT_SUCCESS == makedirs(tmp_path)); + CU_ASSERT(EXISTS_DIR == path_exists(tmp_path)); + + /* add a few files */ + snprintf(tmp_path, FILENAME_MAX, "%s/foo/quux", testpath); + CU_ASSERT(EXIT_SUCCESS == test_touch_file_helper(tmp_path)); + CU_ASSERT(EXISTS_FILE == path_exists(tmp_path)); + snprintf(tmp_path, FILENAME_MAX, "%s/foo/bar/xyzzy", testpath); + snprintf(lnk_path, FILENAME_MAX, "%s/foo/baz/xyzzy", testpath); + CU_ASSERT(EXIT_SUCCESS == test_write_file_helper(tmp_path, + "some data should go here")); + CU_ASSERT(EXISTS_FILE == path_exists(tmp_path)); + CU_ASSERT(-1 != link(tmp_path, lnk_path)); + CU_ASSERT(EXISTS_FILE == path_exists(lnk_path)); + snprintf(tmp_path, FILENAME_MAX, "%s/foo/bar/thud", testpath); + snprintf(lnk_path, FILENAME_MAX, "%s/foo/baz/fred", testpath); + CU_ASSERT(EXIT_SUCCESS == test_write_file_helper(tmp_path, + "we want something for the link")); + CU_ASSERT(EXISTS_FILE == path_exists(tmp_path)); + CU_ASSERT(-1 != symlink(tmp_path, lnk_path)); + + CU_ASSERT_FATAL(EXIT_SUCCESS == rmdirs(testpath)); + CU_ASSERT_FATAL(EXISTS_NOENT == path_exists(testpath)); +} + + +/* + * utility function to touch a file + */ +static int +test_write_file_helper(const char *path, const char *data) +{ + ssize_t wrsz; + size_t data_len; + int fail, fd; + + fail = EXIT_SUCCESS; + data_len = strlen(data); + fd = open(path, O_WRONLY|O_CREAT, S_IRUSR| S_IWUSR); + if (-1 == fd) + return EXIT_FAILURE; + wrsz = write(fd, data, data_len); + if (wrsz != data_len) + fail = EXIT_FAILURE; + if (-1 == close(fd)) + fail = EXIT_FAILURE; + return fail; +} + + +static int +test_touch_file_helper(const char *path) +{ + return test_write_file_helper(path, ""); +} + +/* + * Stubs required by the test suite, but for which no functionality is + * required in this code. init_test is called each time a test is run, + * and cleanup is run after every test. + */ +int init_test(void) +{ + return 0; +} + +int cleanup_test(void) +{ + return 0; +} + + +/* + * fireball is the code called when adding test fails: cleanup the test + * registry and exit. + */ +void +fireball(void) +{ + CU_cleanup_registry(); + exit(CU_get_error()); +} + + +/* + * The main function sets up the test suite, registers the test cases, + * runs through them, and hopefully doesn't explode. + */ +int +main(void) +{ + CU_pSuite tsuite = NULL; + unsigned int fails; + + printf("\n\n[+] running tests for dirutils\n"); + + if (!(CUE_SUCCESS == CU_initialize_registry())) { + errx(EX_CONFIG, "failed to initialise test registry"); + return EXIT_FAILURE; + } + + tsuite = CU_add_suite(TEST_SUITE, init_test, cleanup_test); + if (NULL == tsuite) + fireball(); + + if (NULL == CU_add_test(tsuite, "path_exists", test_exists)) + fireball(); + + if (NULL == CU_add_test(tsuite, "makedirs simple", test_makedirs)) + fireball(); + + if (NULL == CU_add_test(tsuite, "empty dir rmdirs", test_empty_rmdirs)) + fireball(); + + if (NULL == CU_add_test(tsuite, "simple rmdirs", test_rmdirs_simple)) + fireball(); + + if (NULL == CU_add_test(tsuite, "full test", test_dirutils)) + fireball(); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + fails = CU_get_number_of_tests_failed(); + warnx("%u tests failed", fails); + + CU_cleanup_registry(); + return fails; +} diff --git a/src/dirwalk.c b/src/dirwalk.c new file mode 100644 index 0000000..d01c15f --- /dev/null +++ b/src/dirwalk.c @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2015 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA + * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * --------------------------------------------------------------------- + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kst/dirutils.h" + + +const unsigned char FT_ANY = DT_BLK|DT_CHR|DT_DIR|DT_FIFO|DT_LNK|DT_REG| + DT_SOCK|DT_UNKNOWN; +const unsigned char FT_STD = DT_REG|DT_DIR; + +/* + * The highest that the DT_* constants are seems to be around 14, so 0x80 + * seems like a sane choice. + */ +const unsigned char FT_NODESCEND = 0x80; + + +static int +walkpath(const char *root, dirwalk_action action, unsigned char mask) +{ + char child[FILENAME_MAX+1]; + struct dirent *ent; + DIR *dh; + + if (NULL == (dh = opendir(root))) { + return -1; + } + + while (NULL != (ent = readdir(dh))) { + if (0 == strncmp("..", ent->d_name, 3)) { + continue; + } + + if (0 == strncmp(".", ent->d_name, 2)) { + continue; + } + + snprintf(child, FILENAME_MAX, "%s/%s", root, ent->d_name); + if (mask | ent->d_type) { + if (-1 == action(child)) { + break; + } + } + if (DT_DIR == ent->d_type && (!(FT_NODESCEND | mask))) { + if (-1 == walkpath(child, action, mask)) { + break; + } + } + } + + if (-1 == closedir(dh)) { + return -1; + } + + return 0; +} + + +/* + * Return a valid readdir(3) type from a mode. + */ +static unsigned char +stat_mode_to_dt_type(mode_t mode) +{ + if (S_ISDIR(mode)) { + return DT_DIR; + } + + if (S_ISREG(mode)) { + return DT_REG; + } + + if (S_ISCHR(mode)) { + return DT_CHR; + } + + if (S_ISBLK(mode)) { + return DT_BLK; + } + + if (S_ISFIFO(mode)) { + return DT_FIFO; + } + + if (S_ISLNK(mode)) { + return DT_LNK; + } + + if (S_ISSOCK(mode)) { + return DT_SOCK; + } + + return DT_UNKNOWN; +} + + +/* + * walkdir goes through every directory entry under root and calls action on + * every entry matching the mask. + */ +int +walkdir(const char *root, dirwalk_action action, unsigned char mask) +{ + struct stat st; + char *rootdup = NULL; + size_t rootlen; + int ret = -1; + unsigned char type; + + if (-1 == stat(root, &st)) { + return -1; + } + + type = stat_mode_to_dt_type(st.st_mode); + if (mask | type) { + if (0 != (ret = action(root))) { + return ret; + } + } + + if (DT_DIR == type) { + rootlen = strnlen(root, FILENAME_MAX); + rootdup = strndup(root, FILENAME_MAX); + if (NULL == rootdup) { + return -1; + } + + if ('/' == rootdup[rootlen-1]) { + rootdup[rootlen-1] = 0; + } + ret = walkpath(rootdup, action, mask); + } + + free(rootdup); + return ret; +} + diff --git a/src/iniparser.c b/src/iniparser.c new file mode 100644 index 0000000..3c93192 --- /dev/null +++ b/src/iniparser.c @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2015 Kyle Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include + +#include +#include +#include +#include +#include + +#include "kst/iniparser.h" + + +#define SECTION_STRING "^\\[ *([a-zA-Z0-9_-]+) *\\]$" +#define SKIPLINE_STRING "^[ \t]*$" +#define COMMENT_STRING "^[ \t]*[#;]" +#define KEYVALUE_STRING "^[ \t]*([a-zA-Z0-9_-]+)[ \t]*=(.*)$" + + +static regex_t section_regex; +static regex_t skipline_regex; +static regex_t comment_regex; +static regex_t keyvalue_regex; + + + +void +iniparser_line_init(iniparser_line_s *line) +{ + line->is_section = 0; + line->name = NULL; + line->value = NULL; +} + + +void +iniparser_line_destroy(iniparser_line_s *line) +{ + free(line->name); + free(line->value); + line->name = NULL; + line->value = NULL; + line->is_section = 0; + line->is_set = 0; +} + + +int +iniparser_open(const char *path, iniparser_file_s **file) +{ + iniparser_file_s *f = NULL; + int allocated = 0; + int ret = -1; + + /* + * If file is NULL, we can allocate space for it. + */ + if (NULL == file) { + goto exit; + } + + f = *file; + if (NULL == f) { + if (NULL == (f = calloc(1, sizeof(*f)))) { + return -1; + } + allocated = 1; + } + + f->source = fopen(path, "r"); + if (NULL == f->source) { + goto exit; + } + + f->lineptr = NULL; + f->linelen = 0; + f->readlen = 0; + *file = f; + ret = 0; + +exit: + if (allocated && ret) { + free(f); + *file = NULL; + } + + return ret; +} + + +int +iniparser_close(iniparser_file_s *file) +{ + int ret; + + if (NULL != file->lineptr) { + free(file->lineptr); + file->lineptr = NULL; + } + + file->linelen = 0; + file->readlen = 0; + ret = fclose(file->source); + file->source = NULL; + return ret; +} + +static char * +extract_match(const char *line, regmatch_t *pmatch) +{ + ssize_t len; + regoff_t eo = pmatch->rm_eo; + regoff_t so = pmatch->rm_so; + char *s = NULL; + + /* Technically, these are signed. They should be checked. */ + if ((pmatch->rm_eo < 0) || (pmatch->rm_eo < 0)) { + return NULL; + } + + /* trim space from the end. */ + while ((eo > pmatch->rm_so) && (0x20 == line[eo-1])) { + eo--; + } + + /* trim space from the beginning. */ + while ((so < eo) && (0x20 == line[so])) { + so++; + } + + len = eo - so; + + if (len <= 0) { + return NULL; + } + + if (NULL == (s = calloc((size_t)len+1, sizeof(*s)))) { + return NULL; + } + + memcpy(s, line + so, (size_t)len); + return s; +} + + +int +iniparser_readline(iniparser_file_s *file, iniparser_line_s *line) +{ + /* + * The longest matching regex has two matches. + */ + regmatch_t pmatch[3]; + + iniparser_line_destroy(line); + if (NULL != file->lineptr) { + free(file->lineptr); + file->lineptr = NULL; + file->linelen = 0; + file->readlen = 0; + } + + file->readlen = getline(&(file->lineptr), &(file->linelen), + file->source); + if (file->readlen < 1) { + if (0 != feof(file->source)) { + return 1; + } + + return -1; + } + + if (0xa == file->lineptr[file->readlen-1]) { + file->lineptr[file->readlen-1] = 0; + } + + if (REG_NOMATCH != regexec(§ion_regex, file->lineptr, 2, pmatch, 0)) { + line->is_section = 1; + line->name = extract_match(file->lineptr, &pmatch[1]); + if (NULL == line->name) { + return -1; + } + line->is_set = 1; + return 0; + } + + /* If a skipline or comment is detected, skip to the next line. */ + if (REG_NOMATCH != regexec(&skipline_regex, file->lineptr, + 0, NULL, 0)) { + return iniparser_readline(file, line); + } + + if (REG_NOMATCH != regexec(&comment_regex, file->lineptr, + 0, NULL, 0)) { + + return iniparser_readline(file, line); + } + + /* Try to parse a key-value pair. */ + if (REG_NOMATCH != regexec(&keyvalue_regex, file->lineptr, + 3, pmatch, 0)) { + line->is_section = 0; + line->name = extract_match(file->lineptr, &pmatch[1]); + line->value = extract_match(file->lineptr, &pmatch[2]); + if (NULL == line->name) { + return -1; + } + line->is_set = 1; + return 0; + } + + /* Reaching this point means there's an invalid line in the file. */ + return -1; +} + + +void +iniparser_destroy() +{ + regfree(§ion_regex); + regfree(&skipline_regex); + regfree(&comment_regex); + regfree(&keyvalue_regex); +} + + +int +iniparser_init() +{ + int ret = -1; + + ret = regcomp(§ion_regex, SECTION_STRING, REG_EXTENDED); + if (0 != ret) { + goto exit; + } + + ret = regcomp(&skipline_regex, SKIPLINE_STRING, REG_NOSUB|REG_EXTENDED); + if (0 != ret) { + goto exit; + } + + ret = regcomp(&comment_regex, COMMENT_STRING, REG_NOSUB|REG_EXTENDED); + if (0 != ret) { + goto exit; + } + + ret = regcomp(&keyvalue_regex, KEYVALUE_STRING, REG_EXTENDED); + if (0 != ret) { + goto exit; + } + + ret = 0; + +exit: + if (ret) { + iniparser_destroy(); + } + + return ret; +} diff --git a/src/iniparser_test.c b/src/iniparser_test.c new file mode 100644 index 0000000..ffa59e3 --- /dev/null +++ b/src/iniparser_test.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2015 Kyle Isom + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include +#include +#include + +#include "kst/iniparser.h" + + +int +main(int argc, char *argv[]) +{ + iniparser_file_s *file = NULL; + iniparser_line_s line; + int i; + int ret; + + ret = iniparser_init(); + if (0 != ret) { + fprintf(stderr, "init failed: %d\n", ret); + goto exit; + } + + argc--; + argv++; + + for (i = 0; i < argc; i++) { + printf("Processing %s\n", argv[i]); + ret = iniparser_open(argv[i], &file); + if (0 != ret) { + perror("_open"); + fprintf(stderr, "retval: %d\n", ret); + goto exit; + } + iniparser_line_init(&line); + + while (1) { + ret = iniparser_readline(file, &line); + /* -1 is returned on error. */ + if (-1 == ret) { + perror("_readline"); + fprintf(stderr, "retval: %d\n", ret); + goto exit; + } + /* 1 means EOF. */ + else if (1 == ret) { + ret = 0; + break; + } + + if (line.is_section) { + printf("Now in section '%s'\n", line.name); + } + else { + printf("Read key '%s' with value '%s'\n", + line.name, line.value); + } + + iniparser_line_destroy(&line); + } + + iniparser_close(file); + free(file); + file = NULL; + iniparser_line_destroy(&line); + } + +exit: + iniparser_line_destroy(&line); + if (argc > 0) { + iniparser_destroy(); + } + + return ret==0; +} diff --git a/src/srm.c b/src/srm.c new file mode 100644 index 0000000..ac0fb1f --- /dev/null +++ b/src/srm.c @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2011, 2012 Kyle Isom + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DEFAULT_PASSES 3 +#define DEV_RANDOM "/dev/urandom" +#define MAX_CHUNK 4096 +#define SRM_VERSION "1.3.1" + + +static int do_wipe(char *, size_t, int); +static int wipe(char *); +static int rmdirs(const char *, size_t); +static void usage(void); +static void version(void); + +extern char *__progname; + + +/* + * overwrite a file with one or more passes of random data, then unlink it. + */ +int +main(int argc, char **argv) +{ + size_t passes, tmp_passes, wiped, failed, i; + char **file_ptr, **wipe_success, **wipe_fail; + int retval, opt, verbose, recur; + + passes = DEFAULT_PASSES; + retval = EX_DATAERR; + wiped = 0; + failed = 0; + verbose = 0; + recur = 0; + + if (argc == 1) + usage(); + + while (-1 != (opt = getopt(argc, argv, "hn:rvV"))) { + switch( opt ) { + case 'h': + usage(); + break; /* don't technically need it but meh */ + case 'n': + tmp_passes = atoi(optarg); + passes = tmp_passes > 0 ? tmp_passes : passes; + break; + case 'r': + recur = 1; + break; + case 'v': + verbose = 1; + break; + case 'V': + version(); + exit(EX_OK); + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + file_ptr = argv; + wipe_success = calloc(argc, sizeof(char *)); + wipe_fail = calloc(argc, sizeof(char *)); + + + while (NULL != *file_ptr) { + printf("%s: ", *file_ptr); + fflush(stdout); + + if (EX_OK != do_wipe(*file_ptr, passes, recur)) { + wipe_fail[failed] = strdup(*file_ptr); + failed++; + } else { + wipe_success[wiped] = strdup(*file_ptr); + wiped++; + printf(" OK!"); + } + file_ptr++; + printf("\n"); + } + + if (verbose) { + if (0 < wiped) { + printf("success: "); + for( i = 0; i < wiped; ++i ) { + printf("%s ", wipe_success[i]); + } + printf("\n"); + } + + if (0 < failed) { + printf("failures: "); + for( i = 0; i < failed; ++i ) { + printf("%s ", wipe_fail[i]); + } + printf("\n"); + } + } + + /* free allocated memory */ + for (i = 0; i < failed; ++i) { + free(wipe_fail[i]); + wipe_fail[i] = NULL; + } + free(wipe_fail); + wipe_fail = NULL; + + for (i = 0; i < wiped; ++i) { + free(wipe_success[i]); + wipe_success[i] = NULL; + } + free(wipe_success); + wipe_success = NULL; + + return retval; +} + + +/* + * takes a filename and the number of passes to wipe the file + */ +int +do_wipe(char *filename, size_t passes, int recur) +{ + struct stat sb; + size_t filesize, i; + int retval; + + retval = EX_IOERR; + if (-1 == stat(filename, &sb)) { + warn("%s", filename); + return retval; + } + + if (recur && sb.st_mode & S_IFDIR) { + if (EX_OK != rmdirs(filename, passes)) { + printf("!"); + return retval; + } else { + printf("."); + fflush(stdout); + retval = EX_OK; + } + + } else { + filesize = sb.st_size; + + for (i = 0; i < passes; ++i) { + if (EX_OK != wipe(filename)) { + printf("!"); + return retval; + } else if (-1 == stat(filename, &sb)) { + printf("!"); + return retval; + } else if (filesize != (size_t)sb.st_size) { + printf("!"); + return retval; + } + } + + if (-1 == truncate(filename, (off_t)0)) + warn("%s", filename); + + if (-1 == unlink(filename)) { + warn("%s", filename); + } else { + printf("."); + fflush(stdout); + retval = EX_OK; + } + } + + return retval; +} + + +/* + * takes a filename and attempts to overwrite it with random data + */ +int +wipe(char *filename) +{ + struct stat sb; + + size_t chunk, filesize, rdsz, wiped, wrsz; + FILE *devrandom, *target; + int retval; + char *rdata; + + retval = EX_IOERR; + wiped = 0; + + if (-1 == stat(filename, &sb)) + return retval; + + filesize = sb.st_size; + + /* + * open devrandom first: if this fails, we don't want to touch the + * target file. + */ + devrandom = fopen(DEV_RANDOM, "r"); + if (NULL == devrandom || -1 == ferror(devrandom)) { + warn("failed to open PRNG %s!", DEV_RANDOM); + return retval; + } + + /* + * for security purposes, we want to make sure to actually overwrite + * the file. r+ gives us read/write but more importantly, sets the + * write stream at the beginning of the file. a side note is that when + * overwriting a file, its size will never seem to change. + */ + target = fopen(filename, "r+"); + if (NULL == target || -1 == ferror(target)) { + warn("failed to open %s", filename); + fclose(devrandom); + return retval; + } + + rewind(target); + + /* + * wait to calloc until we really need the data - makes cleaning up less + * tricky. + */ + rdata = calloc(MAX_CHUNK, sizeof(char)); + if (NULL == rdata) { + warn("could not allocate random data memory"); + fclose(devrandom); + fclose(target); + return retval; + } + + while (wiped < filesize) { + chunk = filesize - wiped; + chunk = chunk > MAX_CHUNK ? MAX_CHUNK : chunk; + + rdsz = fread( rdata, sizeof(char), chunk, devrandom ); + wrsz = fwrite( rdata, sizeof(char), chunk, target ); + + if (-1 == stat(filename, &sb)) { + warn(" stat on %s failed", filename); + break; + } + if ((rdsz != wrsz) || (filesize != (unsigned int)sb.st_size)) { + warn("invalid read/write size"); + break; + } + + wiped += chunk; + } + + if ((0 != fclose(devrandom)) || (0 != fclose(target))) + warn("%s", filename); + else + retval = EX_OK; + + free(rdata); + rdata = NULL; + + return retval; +} + + +/* + * remove a directory, all files in it, and all subdirectories. + */ +int +rmdirs(const char *path, size_t passes) +{ + char child[FILENAME_MAX + 1]; + struct dirent *dp; + DIR *dirp; + int fail = 0; + + if (NULL == (dirp = opendir(path))) + return EX_IOERR; + while (NULL != (dp = readdir(dirp))) { + if (0 == strncmp("..", dp->d_name, 3)) + continue; + if (0 == strncmp(".", dp->d_name, 2)) + continue; + snprintf(child, FILENAME_MAX, "%s/%s", path, dp->d_name); + if (DT_DIR == dp->d_type) { + fail = rmdirs(child, passes); + if (EX_IOERR == fail) + break; + } else { + fail = do_wipe(child, passes, 1); + if (EX_IOERR == fail) + break; + } + } + if (-1 == closedir(dirp)) + return EX_IOERR; + if (EX_IOERR == fail) + return EX_IOERR; + + if (-1 == rmdir(path)) + return EX_IOERR; + else + return EX_OK; +} + + +/* + * print a quick usage message + */ +void +usage() +{ + version(); + printf("usage: %s [-v] [-n number] files\n", __progname); + printf(" -n passes specify number of passes\n"); + printf(" (default is %d passes)\n", DEFAULT_PASSES); + printf(" -r recursive delete\n"); + printf(" -v display list of failures and wiped files" + " after wiping\n" + " (verbose mode).\n"); + printf(" -V print version information."); + + printf("\n"); + exit(EX_USAGE); +} + + +/* + * print program version information + */ +void +version() +{ + printf("%s\n", SRM_VERSION); +} + +