diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..906f189 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,53 @@ +./conf.c +./conf.h +./daemon.c +./daemon.h +./err.c +./err.h +./fifo.c +./fifo.h +./getopt.c +./getopt.h +./hdr.h +./hsort.c +./hsort.h +./lim.c +./lim.h +./list.c +./list.h +./log.c +./log.h +./mem.c +./mem.h +./msg.c +./msg.h +./prog.c +./prog.h +./sig.c +./sig.h +./snprintf.c +./snprintf.h +./opt.c +./opt.h +./rules.mk +./macros.mk +./Makefile +./map.c +./map.h +./prop.c +./prop.h +./net.h +./net.c +./std.h +./conf +./conf/linux +./conf/solaris +./conf/test +./str.h +./str.c +./lib.h +./vsscanf.h +./vsscanf.c +./socks.h +./README +./MANIFEST diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3ded02f --- /dev/null +++ b/Makefile @@ -0,0 +1,141 @@ +# +# libslack - http://libslack.org/ +# +# Copyright (C) 1999, 2000 raf +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# or visit http://www.gnu.org/copyleft/gpl.html +# + +# 20000902 raf + +CC := gcc +TEST := /usr/bin/test +PREFIX := /usr/local +APP_INSDIR := $(PREFIX)/bin +LIB_INSDIR := $(PREFIX)/lib +MAN_INSDIR := $(PREFIX)/man +HDR_INSDIR := $(PREFIX)/include +APP_MANSECT := 1 +LIB_MANSECT := 3 +APP_MANDIR := $(MAN_INSDIR)/man$(APP_MANSECT) +APP_CATDIR := $(MAN_INSDIR)/cat$(APP_MANSECT) +LIB_MANDIR := $(MAN_INSDIR)/man$(LIB_MANSECT) +LIB_CATDIR := $(MAN_INSDIR)/cat$(LIB_MANSECT) +APP_MANSECTNAME := User Commands +LIB_MANSECTNAME := C Library Functions - libslack + +CCFLAGS += -O2 -Wall -pedantic + +CLEAN_FILES += tags core Makefile.bak .makefile.bak MANIFEST pod2html-* + +SLACK_IS_ROOT := 1 +SLACK_SRCDIR := . +SLACK_INCDIRS := . +SLACK_LIBDIRS := . +include $(SLACK_SRCDIR)/macros.mk + +.PHONY: all ready test man html install uninstall + +all: ready $(ALL_TARGETS) +ready: $(READY_TARGETS) +test: ready $(TEST_TARGETS) +man: $(MAN_TARGETS) +html: $(HTML_TARGETS) +install: $(INSTALL_TARGETS) +uninstall: $(UNINSTALL_TARGETS) + +.PHONY: help help-macros depend dep clean clobber distclean dist + +help:: + @echo "This makefile provides the following targets." + @echo + @echo "make help -- shows this list of targets" + @echo "make help-macros -- shows the values of all make macros" + @echo "make ready -- prepares the source directory for make" + @echo "make all -- makes $(SLACK_TARGET) (default)" + @echo "make test -- generates and performs library unit tests" + @echo "make man -- generates all manpages" + @echo "make html -- generates all manpages in html" + @echo "make install -- installs everything under $(PREFIX)" + @echo "make uninstall -- uninstalls everything" + @echo "make depend -- generates source dependencies using makedepend" + @echo "make tags -- generates a tags file using ctags" + @echo "make clean -- removes object files, tags, core and Makefile.bak" + @echo "make clobber -- same as clean but also removes $(SLACK_TARGET) and tests" + @echo "make distclean -- same as clobber but also removes source dependencies" + @echo "make MANIFEST -- creates the MANIFEST file" + @echo "make dist -- creates the distribution: ../$(SLACK_DIST)" + @echo + +help-macros:: + @echo "CC = $(CC)" + @echo "TEST = $(TEST)" + @echo "PREFIX = $(PREFIX)" + @echo "APP_INSDIR = $(APP_INSDIR)" + @echo "LIB_INSDIR = $(LIB_INSDIR)" + @echo "MAN_INSDIR = $(MAN_INSDIR)" + @echo "HDR_INSDIR = $(HDR_INSDIR)" + @echo "APP_MANSECT = $(APP_MANSECT)" + @echo "LIB_MANSECT = $(LIB_MANSECT)" + @echo "APP_MANDIR = $(APP_MANDIR)" + @echo "APP_CATDIR = $(APP_CATDIR)" + @echo "LIB_MANDIR = $(LIB_MANDIR)" + @echo "LIB_CATDIR = $(LIB_CATDIR)" + @echo "TAG_FILES = $(TAG_FILES)" + @echo "DEPEND_CFILES = $(DEPEND_CFILES)" + @echo "DEPEND_HFILES = $(DEPEND_HFILES)" + @echo "CCFLAGS = $(CCFLAGS)" + @echo "READY_TARGETS = $(READY_TARGETS)" + @echo "ALL_TARGETS = $(ALL_TARGETS)" + @echo "TEST_TARGETS = $(TEST_TARGETS)" + @echo "MAN_TARGETS = $(MAN_TARGETS)" + @echo "HTML_TARGETS = $(HTML_TARGETS)" + @echo "INSTALL_TARGETS = $(INSTALL_TARGETS)" + @echo "UNINSTALL_TARGETS = $(UNINSTALL_TARGETS)" + @echo "CLEAN_FILES = $(CLEAN_FILES)" + @echo "CLOBBER_FILES = $(CLOBBER_FILES)" + @echo + +MANIFEST: + @find . -name \* -print > MANIFEST + +tags: $(TAG_FILES) + @ctags $(TAG_FILES) + +depend: ready $(DEPEND_CFILES) $(DEPEND_HFILES) + @makedepend $(SLACK_CPPFLAGS) $(DEPEND_CFILES) + +clean:: + @rm -rf $(CLEAN_FILES) + +clobber:: + @rm -rf $(CLEAN_FILES) $(CLOBBER_FILES) + +distclean:: clobber + @perl -pi -e 'last if /[D]O NOT DELETE/;' Makefile + +dist: distclean MANIFEST + @src=`basename \`pwd\``; \ + dst=$(SLACK_ID); \ + cd ..; \ + $(TEST) "$$src" != "$$dst" -a ! -e "$$dst" && ln -s $$src $$dst; \ + tar chzf $(SLACK_DIST) $$dst; \ + $(TEST) -L "$$dst" && rm -f $$dst; \ + rm $$src/MANIFEST; \ + tar tzf $$dst.tar.gz + +include $(SLACK_SRCDIR)/rules.mk + diff --git a/README b/README new file mode 100644 index 0000000..5e2a314 --- /dev/null +++ b/README @@ -0,0 +1,164 @@ +README +~~~~~~ +libslack - a UNIX/C library of general utilities for programmers with slack + +DESCRIPTION +~~~~~~~~~~~ +Libslack is a library of general utilities designed to make UNIX/C programming +a bit easier on the eye. It is a seemingly random collection of modules and +functions that I find commonly useful. + +It's a small library with lots of functionality, accurately documented and +thoroughly tested. Good library naming conventions are not rigorously observed +on the principle that common operations should always be easy to write and code +should always be easy to read. + +Libslack contains the following modules: + + conf - simple configuration file parsing + daemon - becoming a daemon + err - message/error/debug/verbosity messaging + fifo - fifo and file control + getopt - GNU getopt_long() for systems that don't have it + hsort - heap sort (by Stephen Russell) + lim - POSIX.1 limits convenience functions + list - list data type + log - syslog helper functions + map - map (hash table) data type + mem - memory helper functions + msg - message handling + net - network functions (clients/servers, expect/send, pack/unpack, mail) + opt - command line option handling + prog - program framework + prop - program properties files + sig - ANSI C compliant signal handling + snprintf - safe sprintf() for systems that don't have it (by Theo de Raadt) + str - string data type (tr, regex, regsub, fmt, trim, lc, uc, ...) + vsscanf - sscanf() with va_list argument for systems that don't have it + +INSTALL +~~~~~~~ +Currently this is only known to work on Linux Redhat 6.0 and Solaris 2.6. +Compiling on Solaris 2.6 requires running conf/solaris in the source +directory. It is ready to compile on Linux as distributed so it is not +necessary to run conf/linux. There isn't a real configure script so you will +no doubt encounter problems on other systems. An ANSI C and POSIX +environment will help greatly. + +If your system doesn't have snprintf(3), GNU getopt_long(3) or vsscanf(3), +uncomment the relevant lines in the macros.mk file to include them in libslack. + +If your system doesn't have POSIX 1003.2 compliant regex functions, either: +install the GNU implementation, ftp://ftp.gnu.org/gnu/regex/regex-0.12.tar.gz +[290K] (doesn't support internationalisation); or install Henry Spencer's +implementation, ftp://ftp.zoo.toronto.edu/pub/regex.shar [157K]. + +If you really, really, really don't want the regular expression functions, +uncomment REGEX_MISSING in macros.mk to enable the rest of the library to +be compiled. + +To build and test: + + tar xzf libslack-0.3.tar.gz + cd libslack-0.3 + conf/ # if applicable (i.e. solaris) + make depend + make + make test # note: compiling the tests can take ages + +To install the slack library and its manpages: + + make install + +For more details: + + make help + +There is one manpage for each module in libslack (rather than one for each +function). They are conf(3), daemon(3), err(3), fifo(3), hsort(3), lim(3), +list(3), log(3), map(3), mem(3), msg(3), net(3), opt(3), prog(3), prop(3), +sig(3) and str(3). If necessary, the manpages getopt(3), snprintf(3) and +vsscanf(3) are created as well. + +REQUIREMENTS +~~~~~~~~~~~~ +Requires perl to run the scripts in the conf directory. +Requires gcc to compile the source. +Requires pod2man (comes with perl) to make the manpages. +Requires pod2html (comes with perl) to make the html manpages. +Requires perl and GNU tar to make the distribution. +Requires POSIX 1003.2 compliant regex functions. See INSTALL. + +COPYING +~~~~~~~ +libslack - general utilities including the ability to become a daemon +Copyright (C) 1999, 2000 raf + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This library 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + or visit http://www.gnu.org/copyleft/gpl.html + +HISTORY +~~~~~~~ +0.1 + - Initial version + +0.2 + - Decoupled core file prevention from daemon_init() + into its own function, daemon_prevent_core() + - Decoupled signal handling from daemon_init() + - Cached daemon_started_by_init() and daemon_started_by_inetd() results + - Added some modules to libprog: conf, list, hsort, map, prop + - Added timestamps to msg_out_file() + - Included source to GNU getopt_long_only() (if neccessary) + - Added hdr.h to allow non-ANSI compilers to parse libprog's headers + - Moved libprog to a subdirectory using a "Whole Project" Makefile + - Converted "Whole Project" Makefile into "Scalable" Makefiles + - Added verbosity functions to libprog(prog) + - Fixed bug when constructing data for GNU getopt_long_only() + - Fixed bugs in the options table for libprog(prog) + - Changed help message format: separated option chunks by a blank line + - Fixed bug when obtaining names associated with syslog constants + - Added pathetic conf/linux and conf/solaris scripts + - Added manpages + +0.3 + - Started using GNU getopt_long() instead of getopt_long_only() + - Added -DSVR4 in conf/solaris (doh!) + - Added conditional compilation of debug functions + - Added assert macro that calls dump() + - Fixed bug: SIG_IGN, SIG_DFL and nasty signals weren't treated specially + - Made lists grow/shrink exponentially rather than linearly + - Made maps grow as needed and use arbitrary hash functions and key types + - Added multi-dimensional array allocator to the mem module + - Added net module: clients/servers, expect/send, pack/unpack, mail + - Added internal iterators and some more functions to list and map + - Added examples sections to some libprog manpages + - Added str module: decent strings + tr, regex, regsub, fmt, trim, lc, uc ... + - Added vsscanf(3) implementation for systems that don't have it (e.g. solaris) + - Renamed libprog to libslack (thanks, fred) + - Added socks.h + - Added daemon_revoke_privileges(), daemon_file_is_safe() to daemon module + +TODO +~~~~ + - Add modules: tty/pty, timo, thr + - Add pool, gc(?) to mem module + - Use autoconf + - Use libtool + +~~~~~~~~~~~~~~~~~~~~~~~~~~ +URL: http://libslack.org/ +Date: 20000902 +Author: raf diff --git a/conf.c b/conf.c new file mode 100644 index 0000000..abb8813 --- /dev/null +++ b/conf.c @@ -0,0 +1,469 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - simple configuration file parsing module + +=head1 SYNOPSIS + + #include + + typedef void conf_parse_t(void *obj, const char *conf_path, char *line, size_t lineno); + + void conf_skip_spaces(char **in); + void conf_skip_spaces_backwards(char **in, const char *start); + int conf_expect_word(char **in, const char *expect); + int conf_get_word(char **in, char *word, int length); + void *conf_parse(const char *conf_path, void *obj, conf_parse_t *parse_line); + +=head1 DESCRIPTION + +This module provides functions for parsing simple configuration files like +the ones you find in C. The client need only write a function that +parses a single line. This function is passed to I which will +call it for every line in the configuration file after stripping out blank +lines, leading and trailing spaces and comments. Comments run from a C<`#'> +character to the end of the line. Line continuation is performed when a line +ends with a C<`\'> character (there may also be whitespace and a comment +after the C<`\'>). + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include "conf.h" + +#ifdef NEEDS_SNPRINTF +#include "snprintf.h" +#endif + +/* + +=item C + +Moves C<*in> forwards past whitespace. + +=cut + +*/ + +void conf_skip_spaces(char **in) +{ + while (isspace((int)**in)) + ++*in; +} + +/* + +=item C + +Moves C<*in> backwards past whitespace but not past C. + +=cut + +*/ + +void conf_skip_spaces_backwards(char **in, const char *start) +{ + while (*in > start && isspace((int)(*in)[-1])) + --*in; +} + +/* + +=item C + +Moves C<*in> forwards past C if C is found at C<*in> (after +skipping whitespace). If C is found, returns 0. Otherwise returns -1. + +=cut + +*/ + +int conf_expect_word(char **in, const char *expect) +{ + size_t length = strlen(expect); + + conf_skip_spaces(in); + + if (strncmp(*in, expect, length)) + return -1; + + *in += length; + + return 0; +} + +/* + +=item C + +Copies the word at C<*in> (after skipping whitespace) into C. No more +than C bytes are copied including the C byte. If a word was +found that fit into C, returns 0. Otherwise returns -1. + +=cut + +*/ + +int conf_get_word(char **in, char *word, int length) +{ + char *initial = word; + + conf_skip_spaces(in); + + while (**in && !isspace((int)**in) && --length) + *word++ = *(*in)++; + + if ((word == initial) || (!length && in[1] && !isspace((int)in[1]))) + return -1; + + *word = '\0'; + + return 0; +} + +/* + +=item C + +Parses the text configuration file named C. Blank lines are +ignored. Comments (C<`#'> to end of line) are ignored. Lines that end with +C<`\'> are joined with the following line. There may be whitespace and even +a comment after the C<`\'> character but nothing else. The C +function is called with the client supplied C, the file name, the line +and the line number as arguments. On success, returns C. On errors, +returns C (i.e. if the configuration file could not be read). + +=cut + +*/ + +void *conf_parse(const char *conf_path, void *obj, conf_parse_t *parse_line) +{ + FILE *conf; + char line[BUFSIZ]; + char buf[BUFSIZ]; + int lineno; + int rc; + + if (!(conf = fopen(conf_path, "r"))) + return NULL; + + line[0] = '\0'; + for (lineno = 1; fgets(buf, BUFSIZ, conf); ++lineno) + { + char *start = buf; + char *end; + size_t length; + int cont_line; + + /* Skip leading spaces */ + + if (line[0] == '\0') + conf_skip_spaces(&start); + + /* Strip trailing comments */ + + end = strchr(start, '#'); + if (end) + *end = '\0'; + else + end = start + strlen(start); + + /* Skip trailing spaces (allows comments after line continuation) */ + + conf_skip_spaces_backwards(&end, start); + + /* Skip empty lines */ + + if (*start == '\0' || start == end) + continue; + + /* Perform line continuation */ + + cont_line = (end[-1] == '\\'); + if (cont_line) + --end; + + length = strlen(line); + rc = snprintf(line + length, BUFSIZ - length, "%*.*s", end - start, end - start, start); + if (rc == -1 || rc >= BUFSIZ - length) + return NULL; + + if (cont_line) + continue; + + /* Parse the resulting line */ + + parse_line(obj, conf_path, line, lineno); + line[0] = '\0'; + } + + fclose(conf); + + return obj; +} + +/* + +=back + +=head1 EXAMPLE + + #include + #include + #include + + void fstab_parser(void *obj, const char *conf_path, char *line, size_t lineno) + { + char device[64], mount[64], fstype[64], opts[64]; + int freq, passno; + + if (sscanf(line, "%s %s %s %s %d %d", device, mount, fstype, opts, &freq, &passno) != 6) + fprintf(stderr, "Syntax error in %s (line %d): %s\n", conf_path, lineno, line); + else + printf("%s %s %s %s %d %d\n", device, mount, fstype, opts, freq, passno); + } + + int main(int ac, char **av) + { + conf_parse("/etc/fstab", NULL, fstab_parser); + return 0; + } + +=head1 BUGS + +This module is crap and may eventually be replaced by a real lexical analyser. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +typedef struct Data1 Data1; +typedef struct Pair1 Pair1; + +struct Data1 +{ + int i; + Pair1 *pair; +}; + +struct Pair1 +{ + const char *service; + const char *port; +}; + +static Pair1 pairs[] = +{ + { "echo", "7/tcp" }, + { "echo", "7/udp" }, + { "ftp", "21/tcp" }, + { "ssh", "22/tcp" }, + { "smtp", "25/tcp" }, + { NULL, NULL } +}; + +static const int final_pair = 5; + +static Data1 data1[1] = {{ 0, pairs }}; + +typedef struct Data2 Data2; + +struct Data2 +{ + int i; + int j; + const char *text; + const char *results[3][8]; +}; + +static Data2 data2[1] = +{ + { + 0, + 0, + "\n" + "# This is a comment\n" + "\n" + "line1 = word1 word2\n" + "line2 = word3 \\\n" + "\tword4 word5 \\ # a comment in a funny place\n" + "\tword6 word7\n" + "\n" + "line3 = \\\n" + "\tword8\n" + "\n", + { + { "line1", "=", "word1", "word2", NULL, NULL, NULL, NULL }, + { "line2", "=", "word3", "word4", "word5", "word6", "word7", NULL }, + { "line3", "=", "word8", NULL, NULL, NULL, NULL, NULL } + } + } +}; + +static const int final_line = 3; +static const int final_word = 3; + +static int errors = 0; + +static int create_test1(const char *name) +{ + FILE *out = fopen(name, "w"); + int i; + + if (!out) + { + ++errors, printf("Test1: failed to create file: '%s'\n", name); + return 0; + } + + for (i = 0; data1->pair[i].service; ++i) + fprintf(out, "%s %s\n", data1->pair[i].service, data1->pair[i].port); + + fclose(out); + return 1; +} + +static void parse_test1(void *obj, const char *conf_path, char *line, size_t lineno) +{ + Data1 *data1 = (Data1 *)obj; + char *p = line; + char service[BUFSIZ]; + char port[BUFSIZ]; + + if (conf_get_word(&p, service, BUFSIZ) == -1) + ++errors, printf("Test1: failed to get a word: '%s' (file %s line %d)\n", p, conf_path, lineno); + else if (conf_get_word(&p, port, BUFSIZ) == -1) + ++errors, printf("Test1: failed to get a word: '%s' (files %s line %d)\n", p, conf_path, lineno); + else if (strcmp(service, data1->pair[data1->i].service)) + ++errors, printf("Test1: expected service '%s', received '%s' (file %s line %d)\n", data1->pair[data1->i].service, service, conf_path, lineno); + else if (strcmp(port, data1->pair[data1->i].port)) + ++errors, printf("Test1: expected port '%s', received '%s' (file %s line %d)\n", data1->pair[data1->i].port, port, conf_path, lineno); + ++data1->i; +} + +static int create_test2(const char *name) +{ + FILE *out = fopen(name, "w"); + + if (!out) + { + ++errors, printf("Test2: failed to create file: '%s'\n", name); + return 0; + } + + fprintf(out, "%s", data2->text); + fclose(out); + return 1; +} + +static void parse_test2(void *obj, const char *conf_path, char *line, size_t lineno) +{ + Data2 *data2 = (Data2 *)obj; + char *p = line; + char word[BUFSIZ]; + + for (data2->j = 0; conf_get_word(&p, word, BUFSIZ) != -1; ++data2->j) + { + if (data2->results[data2->i][data2->j] && strcmp(word, data2->results[data2->i][data2->j])) + { + ++errors; + printf("Test2: expected '%s', received '%s' (file %s line %d)\n", data2->results[data2->i][data2->j], word, conf_path, lineno); + break; + } + } + + ++data2->i; +} + +int main(int ac, char **av) +{ + const char * const name = "conf.testfile"; + + int errors_save; + + printf("Testing: conf\n"); + + if (create_test1(name)) + { + errors_save = errors; + conf_parse(name, data1, parse_test1); + if (errors == errors_save && data1->i != final_pair) + ++errors, printf("Test1: failed to parse entire conf file\n"); + unlink(name); + } + + if (create_test2(name)) + { + errors_save = errors; + conf_parse(name, data2, parse_test2); + if (errors == errors_save && (data2->i != final_line || data2->j != final_word)) + ++errors, printf("Test2: failed to parse entire conf file\n"); + unlink(name); + } + + if (errors) + printf("%d/2 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/conf.h b/conf.h new file mode 100644 index 0000000..afc738d --- /dev/null +++ b/conf.h @@ -0,0 +1,41 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_CONF_H +#define LIBSLACK_CONF_H + +#include + +typedef void conf_parse_t(void *obj, const char *conf_path, char *line, size_t lineno); + +__START_DECLS +void conf_skip_spaces __PROTO ((char **in)); +void conf_skip_spaces_backwards __PROTO ((char **in, const char *start)); +int conf_expect_word __PROTO ((char **in, const char *expect)); +int conf_get_word __PROTO ((char **in, char *word, int length)); +void *conf_parse __PROTO ((const char *conf_path, void *obj, conf_parse_t *parse_line)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/conf/linux b/conf/linux new file mode 100755 index 0000000..39c228a --- /dev/null +++ b/conf/linux @@ -0,0 +1,37 @@ +#!/bin/sh +# +# libslack - http://libslack.org/ +# +# Copyright (C) 1999, 2000 raf +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# or visit http://www.gnu.org/copyleft/gpl.html +# + +# Modify the macros.mk make include files to compile on linux +# +# 20000902 raf + +exec perl -pi \ + -e 's/^(\S+ \+= xnet)$/# $1/;' \ + -e 's/^(\S+ \+= -DSVR4)$/# $1/;' \ + -e 's/^(GETOPT := getopt)$/# $1/;' \ + -e 's/^(\S+ \+= -DNEEDS_GETOPT=1)$/# $1/;' \ + -e 's/^(SNPRINTF := snprintf)$/# $1/;' \ + -e 's/^(\S+ \+= -DNEEDS_SNPRINTF=1)$/# $1/;' \ + -e 's/^(VSSCANF := vsscanf)$/# $1/;' \ + -e 's/^(\S+ \+= -DNEEDS_VSSCANF=1)$/# $1/;' \ + -e 's/^(\S+ \+= -DPID_DIR)=.*$/$1=\\"\/var\/run\\"/;' \ + `find . -name macros.mk` diff --git a/conf/solaris b/conf/solaris new file mode 100755 index 0000000..450f70a --- /dev/null +++ b/conf/solaris @@ -0,0 +1,37 @@ +#!/bin/sh +# +# libslack - http://libslack.org/ +# +# Copyright (C) 1999, 2000 raf +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# or visit http://www.gnu.org/copyleft/gpl.html +# + +# Modify the macros.mk make include files to compile on solaris +# +# 20000902 raf + +exec perl -pi \ + -e 's/^# (\S+ \+= xnet)$/$1/;' \ + -e 's/^# (\S+ \+= -DSVR4)$/$1/;' \ + -e 's/^# (GETOPT := getopt)$/$1/;' \ + -e 's/^# (\S+ \+= -DNEEDS_GETOPT=1)$/$1/;' \ + -e 's/^(SNPRINTF := snprintf)$/# $1/;' \ + -e 's/^(\S+ \+= -DNEEDS_SNPRINTF=1)$/# $1/;' \ + -e 's/^# (VSSCANF := vsscanf)$/$1/;' \ + -e 's/^# (\S+ \+= -DNEEDS_VSSCANF=1)$/$1/;' \ + -e 's/^(\S+ \+= -DPID_DIR)=.*$/$1=\\"\/var\/pid\\"/;' \ + `find . -name macros.mk` diff --git a/conf/test b/conf/test new file mode 100755 index 0000000..c505d24 --- /dev/null +++ b/conf/test @@ -0,0 +1,34 @@ +#!/bin/sh +# +# libslack - http://libslack.org/ +# +# Copyright (C) 1999, 2000 raf +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# or visit http://www.gnu.org/copyleft/gpl.html +# + +# Modify the macros.mk make include files to compile on solaris +# +# 20000902 raf + +exec perl -pi \ + -e 's/^# (GETOPT := getopt)$/$1/;' \ + -e 's/^# (\S+ \+= -DNEEDS_GETOPT=1)$/$1/;' \ + -e 's/^# (SNPRINTF := snprintf)$/$1/;' \ + -e 's/^# (\S+ \+= -DNEEDS_SNPRINTF=1)$/$1/;' \ + -e 's/^# (VSSCANF := vsscanf)$/$1/;' \ + -e 's/^# (\S+ \+= -DNEEDS_VSSCANF=1)$/$1/;' \ + `find . -name macros.mk` diff --git a/daemon.c b/daemon.c new file mode 100644 index 0000000..dddbaea --- /dev/null +++ b/daemon.c @@ -0,0 +1,715 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - daemon module + +=head1 SYNOPSIS + + #include + + int daemon_started_by_init(void); + int daemon_started_by_inetd(void); + int daemon_prevent_core(void); + int daemon_revoke_privileges(void); + int daemon_file_is_safe(const char *path); + int daemon_init(const char *name); + int deamon_close(void); + +=head1 DESCRIPTION + +This module provides functions for writing daemons. There are many tasks +that need to be performed to correctly set up a daemon process. This can be +tedious. These functions perform these tasks for you. + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include +#include +#define __USE_BSD +#include + +#include +#include +#include + +#include "daemon.h" +#include "mem.h" +#include "err.h" +#include "lim.h" +#include "fifo.h" + +#ifdef NEEDS_SNPRINTF +#include "snprintf.h" +#endif + +static struct +{ + char *lock; /* Name of the locked pid file */ +} +g; + +/* + +C + +Creates a pid file for a daemon and locks it. The file has one line +containing the process id of the daemon. The well-known location for the +file is defined in C (C<"/var/run"> by default). The name of the +file is the name of the daemon (given by the name argument) followed by +C<".pid">. The presence of this file will prevent two deamons with the same +name from running at the same time. On success, returns 0. On error, returns +-1 with C set appropriately. + +*/ + +static int daemon_pidfile(const char *name) +{ + mode_t mode; + char pid[32]; + int pid_fd; + long path_len; + char *suffix = ".pid"; + int rc; + + path_len = limit_path(); + + if (sizeof(PID_DIR) + sizeof(PATH_SEP) + strlen(name) + strlen(suffix) + 1 > path_len) + return set_errno(ENAMETOOLONG); + + if (!g.lock && !(g.lock = mem_create(path_len, char))) + return set_errno(ENOMEM); + + rc = snprintf(g.lock, path_len, "%s%c%s%s", PID_DIR, PATH_SEP, name, suffix); + if (rc == -1 || rc >= path_len) + { + mem_destroy(g.lock); + return set_errno(ENOSPC); + } + + /* This is broken over NFS (Linux). */ + + mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + if ((pid_fd = open(g.lock, O_RDWR | O_CREAT | O_EXCL, mode)) == -1) + { + if (errno != EEXIST) + { + mem_destroy(g.lock); + return -1; + } + + /* + ** The pidfile already exists. Is it locked? + ** If so, another invocation is still alive. + ** If not, the invocation that created it has died. + ** Open the pidfile to attempt a lock. + */ + + if ((pid_fd = open(g.lock, O_RDWR)) == -1) + { + mem_destroy(g.lock); + return -1; + } + } + + if (fcntl_lock(pid_fd, F_SETLK, F_WRLCK, SEEK_SET, 0, 0) == -1) + { + mem_destroy(g.lock); + return -1; + } + + /* It wasn't locked. Now we have it locked, store our pid. */ + + rc = snprintf(pid, 32, "%d\n", getpid()); + if (rc == -1 || rc >= 32) + { + mem_destroy(g.lock); + return set_errno(ENOSPC); + } + + if (write(pid_fd, pid, strlen(pid)) != strlen(pid)) + { + daemon_close(); + return -1; + } + + /* + ** Flaw: If someone unceremoniously unlinks the pidfile, + ** we won't know about it and nothing will stop another + ** invocation from starting up. + */ + + return 0; +} + +/* + +=item C + +If this process was started by I, returns 1. If not, returns 0. If +it was, we might be getting respawned so I and I would be +a big mistake (and unnecessary anyway since there is no controlling +terminal). The return value is cached so any subsequent calls are faster. + +=cut + +*/ + +int daemon_started_by_init(void) +{ + static int rc = -1; + + if (rc == -1) + rc = (getppid() == 1); + + return rc; +} + +/* + +=item C + +If this process was started by I, returns 1. If not, returns 0. If +it was, C, C and C would be opened to a socket. +Closing them would be a big mistake. We also would not need to I +and I because there is no controlling terminal. The return value is +cached so any subsequent calls are faster. + +=cut + +*/ + +int daemon_started_by_inetd(void) +{ + static int rc = -1; + + if (rc == -1) + { + size_t optlen = sizeof(int); + int optval; + + rc = (getsockopt(STDIN_FILENO, SOL_SOCKET, SO_TYPE, &optval, &optlen) == 0); + } + + return rc; +} + +/* + +=item C + +Prevents core files from being generated. This is used to prevent security +holes in daemons run by root. On success, returns 0. On error, returns -1 +with C set appropriately. + +=cut + +*/ + +int daemon_prevent_core(void) +{ + struct rlimit limit[1] = {{ 0, 0 }}; + + if (getrlimit(RLIMIT_CORE, limit) == -1) + return -1; + + limit->rlim_cur = 0; + + return setrlimit(RLIMIT_CORE, limit); +} + +/* + +=item C + +Sets the effective gid to the real gid if they differ. Sets the effective +uid to the real uid if they differ. Checks that they no longer differ. On +success, returns 0. On error, returns -1. + +=cut + +*/ + +int daemon_revoke_privileges(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + uid_t euid = geteuid(); + gid_t egid = getegid(); + +#ifdef NGROUPS_MAX + + gid_t gidset[1]; + gidset[0] = gid; + + if (euid == 0 && euid != uid && (setgroups(1, gidset) == -1 || getgroups(1, gidset) != 1 || gidset[0] != gid)) + return -1; + +#endif + + if (egid != gid && (setgid(gid) == -1 || getegid() != getgid())) + return -1; + + if (uid != euid && (setuid(uid) == -1 || getuid() != geteuid())) + return -1; + + return 0; +} + +/* + +=item C + +Checks that the file referred to by C is not group or world writable. +Also checks that the containing directories are not group or world writable. +On success, returns 1 if C is safe or 0 if it is not. On error, +returns -1. + +=cut + +*/ + +int daemon_file_is_safe(const char *path) +{ + struct stat status[1]; + char *tmp, *slash; + + if (!path) + return set_errno(EINVAL); + + if (stat(path, status) == -1) + return -1; + + if (status->st_mode & (S_IWGRP | S_IWOTH)) + return 0; + + tmp = mem_strdup(path); + if (!tmp) + return -1; + + for (slash = strrchr(tmp, PATH_SEP); slash; slash = strrchr(tmp, PATH_SEP)) + { + slash[(slash == tmp) ? 1 : 0] = '\0'; + + if (stat(tmp, status) == -1) + { + mem_release(tmp); + return -1; + } + + if (status->st_mode & (S_IWGRP | S_IWOTH)) + { + mem_release(tmp); + return 0; + } + + if (slash == tmp) + break; + } + + mem_release(tmp); + return 1; +} + +/* + +=item C + +Initialises a daemon by performing the following tasks: + +=over 4 + +=item * + +If the process was not invoked by I or I: + +=over 4 + +=item * + +Background the process to lose process group leadership. + +=item * + +Start a new process session. + +=item * + +Under I, background the process again to lose process session +leadership. This prevents the process from ever gaining a controlling +terminal. This only happens when C is defined and +C is not defined when I is compiled. + +=back + +=item * + +Change to the root directory so as not to hamper umounts. + +=item * + +Clear the umask to enable explicit file creation modes. + +=item * + +Close all open file descriptors. If the process was invoked by I, +C, C and C are left open since they are open to a +socket. + +=item * + +Open C, C and C to C in case something +requires them to be open. Of course, this is not done if the process was +invoked by I. + +=item * + +If C is non-null, create and lock a file containing the process id of +the process. The presence of this locked file prevents two instances of a +daemon with the same name from running at the same time. + +=back + +On success, returns 0. On error, returns -1 with C set appropriately. + +=cut + +*/ + +int daemon_init(const char *name) +{ + pid_t pid; + long nopen; + int fd; + + /* + ** Don't setup a daemon-friendly process context + ** if started by init(8) or inetd(8). + */ + + if (!(daemon_started_by_init() || daemon_started_by_inetd())) + { + /* + ** Background the process. + ** Lose process group leadership. + */ + + if ((pid = fork()) == -1) + return -1; + + if (pid) + exit(0); + + /* Become a process session leader. */ + + setsid(); + +#ifndef NO_EXTRA_SVR4_FORK +#ifdef SVR4 + /* + ** Lose process session leadership + ** to prevent gaining a controlling + ** terminal in SVR4. + */ + + if ((pid = fork()) == -1) + return -1; + + if (pid) + exit(0); +#endif +#endif + } + + /* Enter the root directory to prevent hampering umounts. */ + + if (chdir(ROOT_DIR) == -1) + return -1; + + /* Clear umask to enable explicit file modes. */ + + umask(0); + + /* + ** We need to close all open file descriptors. Check how + ** many file descriptors we have (If indefinite, a usable + ** number (1024) will be returned). + */ + + if ((nopen = limit_open()) == -1) + return -1; + + /* + ** Close all open file descriptors. If started by inetd, + ** we don't close stdin, stdout and stderr. + ** Don't forget to open any future tty devices with O_NOCTTY + ** so as to prevent gaining a controlling terminal + ** (not necessary with SVR4). + */ + + if (daemon_started_by_inetd()) + { + for (fd = 0; fd < nopen; ++fd) + { + switch (fd) + { + case STDIN_FILENO: + case STDOUT_FILENO: + case STDERR_FILENO: + break; + default: + close(fd); + } + } + } + else + { + for (fd = 0; fd < nopen; ++fd) + close(fd); + + /* + ** Open stdin, stdout and stderr to /dev/null just in case + ** some code buried in a library somewhere expects them to be open. + */ + + if ((fd = open("/dev/null", O_RDWR)) == -1) + return -1; + + /* + ** This is only needed for very strange (hypothetical) + ** posix implementations where STDIN_FILENO != 0 or + ** STDOUT_FILE != 1 or STERR_FILENO != 2 (yeah, right). + */ + + if (fd != STDIN_FILENO) + { + if (dup2(fd, STDIN_FILENO) == -1) + return -1; + + close(fd); + } + + if (dup2(STDIN_FILENO, STDOUT_FILENO) == -1) + return -1; + + if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) + return -1; + } + + /* Place our process id in the file system and lock it. */ + + if (name) + return daemon_pidfile(name); + + return 0; +} + +/* + +=item C + +Unlinks the locked pid file, if any. Returns 0. + +=cut + +*/ + +int daemon_close(void) +{ + if (g.lock) + { + unlink(g.lock); + mem_destroy(g.lock); + } + + return 0; +} + +/* + +=back + +=head1 ERRORS + + +Additional errors may be generated and returned from the underlying system +calls. See their manual pages. + +=over 4 + +=item ENAMETOOLONG + +The C passed to I resulted in a path name that is +too long for the intended filesystem. + +=item ENOMEM + +I failed to allocate memory for the the pid file's path. + +=back + +=head1 EXAMPLE + + #include + #include + #include + + #include + #include + #include + + void hup(int signo) + { + // reload config file... + } + + void term(int signo) + { + daemon_close(); + exit(0); + } + + void do_stuff() + { + // do stuff... + kill(getpid(), SIGTERM); + signal_handle_all(); + } + + int main(int ac, char **av) + { + if (daemon_prevent_core() == -1 || + daemon_init(prog_basename(*av)) == -1 || + signal_set_handler(SIGHUP, 0, hup) == -1 || + signal_set_handler(SIGTERM, 0, term) == -1) + return 1; + + do_stuff(); + return 0; // unreached + } + +=head1 BUGS + +It is possible to obtain a controlling terminal under I (and even under +I if C was not defined or C was defined when +I is compiled). If anything calls I on a terminal device +without the C flag, the process doing so will obtain a controlling +terminal. + +It's very likely that only I can name daemon's clients since the pid +file is created in a directory that is probably only writable by I. If +this is a problem, override the C macro to specify a directory that +is writable by everyone and then recompile I. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +#include + +#include "msg.h" +#include "prog.h" +#include "sig.h" + +void term(int signo) +{ + daemon_close(); + exit(0); +} + +int main(int ac, char **av) +{ + const char *name = geteuid() ? NULL : prog_basename(*av); + int facility = LOG_DAEMON | LOG_ERR; + + printf("Testing: daemon\n"); + printf("All tests passed\n"); + printf("\n"); + printf(" Note: can't verify syslog daemon.err output.\n"); + printf(" Look for: \"%s succeeded\" (not \"%s failed\").\n", *av, *av); + + if (daemon_prevent_core() == -1) + { + syslog(facility, "%s failed: daemon_prevent_core(): %s", *av, strerror(errno)); + return 1; + } + + if (daemon_init(name) == -1) + { + syslog(facility, "%s failed: daemon_init(): %s", *av, strerror(errno)); + return 1; + } + + if (signal_set_handler(SIGTERM, 0, term) == -1) + { + syslog(facility, "%s failed: signal_set_handler(): %s", *av, strerror(errno)); + return 1; + } + + syslog(facility, "%s succeeded", *av); + kill(getpid(), SIGTERM); + signal_handle_all(); + return 0; /* unreached */ +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/daemon.h b/daemon.h new file mode 100644 index 0000000..56cc14f --- /dev/null +++ b/daemon.h @@ -0,0 +1,57 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_DAEMON_H +#define LIBSLACK_DAEMON_H + +#include + +#ifndef PID_DIR +#define PID_DIR "/var/run" +#endif + +#ifndef ROOT_DIR +#define ROOT_DIR "/" +#endif + +#ifndef ETC_DIR +#define ETC_DIR "/etc" +#endif + +#ifndef PATH_SEP +#define PATH_SEP '/' +#endif + +__START_DECLS +int daemon_started_by_init __PROTO ((void)); +int daemon_started_by_inetd __PROTO ((void)); +int daemon_prevent_core __PROTO ((void)); +int daemon_revoke_privileges __PROTO ((void)); +int daemon_file_is_safe __PROTO ((const char *path)); +int daemon_init __PROTO ((const char *name)); +int daemon_close __PROTO ((void)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/err.c b/err.c new file mode 100644 index 0000000..f74d2d5 --- /dev/null +++ b/err.c @@ -0,0 +1,903 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - message/error/debug/verbosity messaging module + +=head1 SYNOPSIS + + #include + + void msg(const char *fmt, ...); + void vmsg(const char *fmt, va_list args); + void verbose(size_t level, const char *fmt, ...); + void vverbose(size_t level, const char *fmt, va_list args); + void _debug(size_t level, const char *fmt, ...); + void _vdebug(size_t level, const char *fmt, va_list args); + int error(const char *fmt, ...); + int verror(const char *fmt, va_list args); + void fatal(const char *fmt, ...); + void vfatal(const char *fmt, va_list args); + void dump(const char *fmt, ...); + void vdump(const char *fmt, va_list args); + void _debugsys(size_t level, const char *fmt, ...); + void _vdebugsys(size_t level, const char *fmt, va_list args); + int errorsys(const char *fmt, ...); + int verrorsys(const char *fmt, va_list args); + void fatalsys(const char *fmt, ...); + void vfatalsys(const char *fmt, va_list args); + void dumpsys(const char *fmt, ...); + void vdumpsys(const char *fmt, va_list args); + int set_errno(int errnum); + + #define debug(args) + #define vdebug(args) + #define debugsys(args) + #define vdebugsys(args) + #define assert(test, msg) + +=head1 DESCRIPTION + +This module works with the I and I modules to provide functions +for emitting various types of message with simple call syntax and flexible +behaviour. The message types catered for are: normal, verbose, debug, error, +fatal error and dump messages. All messages are created and sent with +I-like syntax. The destinations for these messages are configurable +by the client. Calling I causes normal and verbose messages to +be sent to standard output; debug, error, fatal error and dump messages to +be sent to standard error. + +Calls to I, I, I, +I, I cause normal and verbose messages +to be sent to the specified destination. Calls to I, +I, I, I, +I cause error, fatal error and dump messages to be sent to +the specified destination. Calls to I, I, +I, I, I, +I cause debug messages to be sent to the specified +destination. Calls to the generic functions I, +I and I cause their respective message types +to be sent to the specified destination or destinations (multiplexing +messages is only possible via these functions). See I for more +details. + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include "msg.h" +#include "prog.h" +#include "err.h" + +#ifdef NEEDS_SNPRINTF +#include "snprintf.h" +#endif + +/* + +=item C + +Outputs a message to the program's normal message destination. C is a +I-like format string and processes any remaining arguments in the +same way as I. + +=cut + +*/ + +void msg(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vmsg(fmt, args); + va_end(args); +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +void vmsg(const char *fmt, va_list args) +{ + vmsg_out(prog_out(), fmt, args); +} + +/* + +=item C + +Outputs a verbose message to the program's normal message destination if +C is less than or equal to the program's current verbosity level. If +the program's name has been supplied using I, the message +will be preceeded by the name, a colon and a space. The message is also +preceeded by as many spaces as the message level. This indents messages +according to their verbosity. C is a I-like format string and +processes any remaining arguments in the same way as I. The +message is followed by a newline. + +=cut + +*/ + +void verbose(size_t level, const char *fmt, ...) +{ + if (prog_verbosity_level() >= level) + { + va_list args; + va_start(args, fmt); + vverbose(level, fmt, args); + va_end(args); + } +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +void vverbose(size_t level, const char *fmt, va_list args) +{ + if (prog_verbosity_level() >= level) + { + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + + if (prog_name()) + msg_out(prog_out(), "%s: %*s%s\n", prog_name(), level, "", msg); + else + msg_out(prog_out(), "%*s%s\n", level, "", msg); + } +} + +/* + +=item C + +Outputs a debug message to the program's debug message destination if +C is less than or equal to the current program debug level. If the +program's name has been supplied using I, the message will +be preceeded by the name, a colon and a space. The message is also preceeded +by as many spaces as the message level. This indents debug messages +according to their debug level. C is a I-like format string +and processes any remaining arguments in the same way as I. The +message is followed by a newline. + +=cut + +*/ + +void _debug(size_t level, const char *fmt, ...) +{ + if (prog_debug_level() >= level) + { + va_list args; + va_start(args, fmt); + _vdebug(level, fmt, args); + va_end(args); + } +} + +/* + +=item C + +Equivalent to I<_debug()> with the variable argument list specified +directly as for I. + +=cut + +*/ + +void _vdebug(size_t level, const char *fmt, va_list args) +{ + if (prog_debug_level() >= level) + { + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + + if (prog_name()) + msg_out(prog_dbg(), "%s: debug: %*s%s\n", prog_name(), level, "", msg); + else + msg_out(prog_dbg(), "debug: %*s%s\n", level, "", msg); + } +} + +/* + +=item C + +Outputs an error message to the program's error message destination. If the +program's name has been supplied using I, the message will +be preceeded by the name, a colon and a space. C is a I-like +format string and processes any remaining arguments in the same way as +I. The message is followed by a newline. Returns -1. + +=cut + +*/ + +int error(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + verror(fmt, args); + va_end(args); + + return -1; +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +int verror(const char *fmt, va_list args) +{ + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + + if (prog_name()) + msg_out(prog_err(), "%s: %s\n", prog_name(), msg); + else + msg_out(prog_err(), "%s\n", msg); + + return -1; +} + +/* + +=item C + +Outputs an error message to the program's error message destination and then +calls I with a return code of 1. If the program's name was supplied +using I, the message will be preceeded by the name, a colon +and a space. This is followed by the string C<"fatal: ">. C is a +I-like format string and processes any remaining arguments in the +same way as I. The message is followed by a newline. + +=cut + +*/ + +void fatal(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfatal(fmt, args); + va_end(args); /* unreached */ +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +void vfatal(const char *fmt, va_list args) +{ + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + error("fatal: %s", msg); + exit(1); +} + +/* + +=item C + +Outputs an error message to the program's error message destination and then +calls I. If the program's name was supplied using +I, the message will be preceeded by the name, a colon and a +space. This is followed by the string C<"dump: ">. C is a +I-like format string and processes any remaining arguments in the +same way as I. The message is followed by a newline. + +=cut + +*/ + +void dump(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vdump(fmt, args); + va_end(args); /* unreached */ +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +void vdump(const char *fmt, va_list args) +{ + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + error("dump: %s", msg); + abort(); +} + +/* + +=item C + +Outputs a debug message to the program's debug message destination if +C is less than or equal to the current program debug level. If the +program's name has been supplied using I, the message will +be preceeded by the name, a colon and a space. The message is also preceeded +by as many spaces as the message level. This indents debug messages +according to their debug level. C is a I-like format string +and processes any remaining arguments in the same way as I. The +message is followed by a colon, a space, the string representation of +C and a newline. + +=cut + +*/ + +void _debugsys(size_t level, const char *fmt, ...) +{ + if (prog_debug_level() >= level) + { + va_list args; + va_start(args, fmt); + _vdebugsys(level, fmt, args); + va_end(args); + } +} + +/* + +=item C + +Equivalent to I<_debugsys()> with the variable argument list specified +directly as for I. + +=cut + +*/ + +void _vdebugsys(size_t level, const char *fmt, va_list args) +{ + if (prog_debug_level() >= level) + { + char msg[MSG_SIZE]; + int errno_saved = errno; + vsnprintf(msg, MSG_SIZE, fmt, args); + _debug(level, "%s: %s", msg, strerror(errno_saved)); + } +} + +/* + +=item C + +Outputs an error message to the program's error message destination. If the +program's name has been supplied using I, the message will +be preceeded by the name, a colon and a space. C is a I-like +format string and processes any remaining arguments in the same way as +I. The message is followed by a colon, a space, the string +representation of C and a newline. Returns -1. + +=cut + +*/ + +int errorsys(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + verrorsys(fmt, args); + va_end(args); + return -1; +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +int verrorsys(const char *fmt, va_list args) +{ + char msg[MSG_SIZE]; + int errno_saved = errno; + vsnprintf(msg, MSG_SIZE, fmt, args); + return error("%s: %s", msg, strerror(errno_saved)); +} + +/* + +=item C + +Outputs an error message to the program's error message destination and then +calls I with a return code of 1. If the program's name was supplied +using I, the message will be preceeded by the name, a colon +and a space. This is followed by the string C<"fatal: ">. C is a +I-like format string and processes any remaining arguments in the +same way as I. The message is followed by a colon, a space, the +string representation of C and a newline. + +=cut + +*/ + +void fatalsys(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfatalsys(fmt, args); + va_end(args); /* unreached */ +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +void vfatalsys(const char *fmt, va_list args) +{ + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + fatal("%s: %s", msg, strerror(errno)); +} + +/* + +=item C + +Outputs an error message to the program's error message destination and then +calls I. If the program's name was supplied using +I, the message will be preceeded by the name, a colon and a +space. This is followed by the string C<"dump: ">. C is a +I-like format string and processes any remaining arguments in the +same way as I. The message is followed by a colon, a space, the +string representation of C and a newline. + +=cut + +*/ + +void dumpsys(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vdumpsys(fmt, args); + va_end(args); /* unreached */ +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +void vdumpsys(const char *fmt, va_list args) +{ + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + dump("%s: %s", msg, strerror(errno)); +} + +/* + +=item C + +Sets C to C and returns -1. + +=cut + +*/ + +int set_errno(int errnum) +{ + errno = errnum; + return -1; +} + +/* + +=item C< #define debug(args)> + +Calls I<_debug()> unless C is defined. C must be supplied with +extra parentheses. (e.g. C). + +=item C< #define vdebug(args)> + +Calls I<_vdebug()> unless C is defined. C must be supplied +with extra parentheses. (e.g. C). + +=item C< #define debugsys(args)> + +Calls I<_debugsys()> unless C is defined. C must be supplied +with extra parentheses. (e.g. C). + +=item C< #define vdebugsys(args)> + +Calls I<_vdebugsys()> unless C is defined. C must be supplied +with extra parentheses. (e.g. C). + +=item C< #define assert(test, msg)> + +Like the standard I but includes a C argument for including an +explanation of the test and it calls C to terminate the program so +that the output of the assertion failure message will be sent to the right +place. + +=back + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +#include +#include +#include + +#include +#include + +static int verify(int test, const char *name, const char *result) +{ + char buf[BUFSIZ]; + int fd; + ssize_t bytes; + + if ((fd = open(name, O_RDONLY)) == -1) + { + printf("Test%d: failed to create err file: %s (%s)\n", test, name, strerror(errno)); + return 1; + } + + memset(buf, '\0', BUFSIZ); + bytes = read(fd, buf, BUFSIZ); + close(fd); + unlink(name); + + if (bytes == -1) + { + printf("Test%d: failed to read err file: %s (%s)\n", test, name, strerror(errno)); + return 1; + } + + if (!strstr(buf, result)) + { + printf("Test%d: err file produced incorrect input:\nshould contain:\n%s\nwas:\n%s\n", test, result, buf); + return 1; + } + + return 0; +} + +static int verifysys(int test, const char *name, const char *result, int err) +{ + char buf[BUFSIZ]; + + snprintf(buf, BUFSIZ, result, strerror(err)); + + return verify(test, name, buf); +} + +int main(int ac, char **av) +{ + const char * const out = "err.out"; + const char * const err = "err.err"; + const char * const dbg = "err.dbg"; + const char * const core = "core"; + + const char *results[10] = + { + "msg\n", + "verbose\n", + "debug: debug\n", + "error\n", + "fatal: fatal\n", + "dump: dump\n", + "debug: debugsys: %s\n", + "errorsys: %s\n", + "fatal: fatalsys: %s\n", + "dump: dumpsys: %s\n" + }; + + pid_t pid; + int errors = 0; + + printf("Testing: err\n"); + + prog_set_debug_level(1); + prog_set_verbosity_level(1); + + prog_out_file(out); + msg("msg\n"); + errors += verify(1, out, results[0]); + + prog_out_file(out); + verbose(0, "verbose"); + errors += verify(2, out, results[1]); + + prog_dbg_file(dbg); + _debug(0, "debug"); + errors += verify(3, dbg, results[2]); + + prog_err_file(err); + error("error"); + errors += verify(4, err, results[3]); + + switch (pid = fork()) + { + case 0: + { + prog_err_file(err); + fatal("fatal"); + } + + case -1: + { + ++errors; + printf("Test5: failed to perform test - fork() failed (%s)\n", strerror(errno)); + break; + } + + default: + { + int status[1]; + + if (waitpid(pid, status, 0) == -1) + { + ++errors, printf("Test5: failed to wait for test - waitpid(%d) failed (%s)\n", (int)pid, strerror(errno)); + break; + } + + if (WIFSIGNALED(*status) && WTERMSIG(*status) != SIGABRT) + ++errors, printf("Test5: failed: received signal %d\n", WTERMSIG(*status)); + + if (WIFEXITED(*status) && WEXITSTATUS(*status) != 1) + ++errors, printf("Test5: failed: exit status %d\n", WEXITSTATUS(*status)); + } + } + + errors += verify(5, err, results[4]); + + switch (pid = fork()) + { + case 0: + { + prog_err_file(err); + unlink(core); + dump("dump"); + } + + case -1: + { + ++errors; + printf("Test6: failed to perform test - fork() failed (%s)\n", strerror(errno)); + break; + } + + default: + { + struct stat statbuf[1]; + int status[1]; + + if (waitpid(pid, status, 0) == -1) + { + ++errors, printf("Test6: failed to wait for test - waitpid(%d) failed (%s)\n", (int)pid, strerror(errno)); + break; + } + + if (WIFSIGNALED(*status) && WTERMSIG(*status) != SIGABRT) + ++errors, printf("Test6: failed: received signal %d\n", WTERMSIG(*status)); + + if (WIFEXITED(*status) && WEXITSTATUS(*status) != 1) + ++errors, printf("Test6: failed: exit status %d\n", WEXITSTATUS(*status)); + + if (stat(core, statbuf) == -1 && errno == ENOENT) + ++errors, printf("Test6: failed: no core file produced\n"); + else + unlink(core); + } + } + + errors += verify(6, err, results[5]); + + prog_dbg_file(dbg); + set_errno(EPERM); + _debugsys(0, "debugsys"); + errors += verifysys(7, dbg, results[6], EPERM); + + prog_err_file(err); + set_errno(ENOENT); + errorsys("errorsys"); + errors += verifysys(8, err, results[7], ENOENT); + + switch (pid = fork()) + { + case 0: + { + prog_err_file(err); + set_errno(EPERM); + fatalsys("fatalsys"); + } + + case -1: + { + ++errors; + printf("Test9: failed to perform test - fork() failed (%s)\n", strerror(errno)); + break; + } + + default: + { + int status[1]; + + if (waitpid(pid, status, 0) == -1) + { + ++errors; + printf("Test9: failed to wait for test - waitpid(%d) failed (%s)\n", (int)pid, strerror(errno)); + break; + } + + if (WIFSIGNALED(*status) && WTERMSIG(*status) != SIGABRT) + ++errors, printf("Test9: failed: received signal %d\n", WTERMSIG(*status)); + + if (WIFEXITED(*status) && WEXITSTATUS(*status) != 1) + ++errors, printf("Test9: failed: exit status %d\n", WEXITSTATUS(*status)); + } + } + + errors += verifysys(9, err, results[8], EPERM); + + switch (pid = fork()) + { + case 0: + { + prog_err_file(err); + unlink(core); + set_errno(ENOENT); + dumpsys("dumpsys"); + } + + case -1: + { + ++errors; + printf("Test10: failed to perform test - fork() failed (%s)\n", strerror(errno)); + break; + } + + default: + { + struct stat statbuf[1]; + int status[1]; + + if (waitpid(pid, status, 0) == -1) + { + ++errors; + printf("Test10: failed to wait for test - waitpid(%d) failed (%s)\n", (int)pid, strerror(errno)); + break; + } + + if (WIFSIGNALED(*status) && WTERMSIG(*status) != SIGABRT) + ++errors, printf("Test10: failed: received signal %d\n", WTERMSIG(*status)); + + if (WIFEXITED(*status) && WEXITSTATUS(*status) != 1) + ++errors, printf("Test10: failed: exit status %d\n", WEXITSTATUS(*status)); + + if (stat(core, statbuf) == -1 && errno == ENOENT) + ++errors, printf("Test10: failed: no core file produced\n"); + else + unlink(core); + } + } + + errors += verifysys(10, err, results[9], ENOENT); + + if (errors) + printf("%d/10 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/err.h b/err.h new file mode 100644 index 0000000..f89290b --- /dev/null +++ b/err.h @@ -0,0 +1,78 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_ERR_H +#define LIBSLACK_ERR_H + +#include +#include + +#include + +#undef debug +#undef vdebug +#undef debugsys +#undef vdebugsys +#undef assert + +#ifdef NDEBUG +#define debug(args) +#define vdebug(args) +#define debugsys(args) +#define vdebugsys(args) +#define assert(cond, msg) +#else +#define debug(args) _debug args +#define vdebug(args) _vdebug args +#define debugsys(args) _debugsys args +#define vdebugsys(args) _vdebugsys args +#define assert(test, msg) dump("Internal Error: %s: %s [\"%s\":%d]", (#test), (msg), __FILE__, __LINE__) +#endif + +__START_DECLS +void msg __PROTO ((const char *fmt, ...)); +void vmsg __PROTO ((const char *fmt, va_list args)); +void verbose __PROTO ((size_t level, const char *fmt, ...)); +void vverbose __PROTO ((size_t level, const char *fmt, va_list args)); +void _debug __PROTO ((size_t level, const char *fmt, ...)); +void _vdebug __PROTO ((size_t level, const char *fmt, va_list args)); +int error __PROTO ((const char *fmt, ...)); +int verror __PROTO ((const char *fmt, va_list args)); +void fatal __PROTO ((const char *fmt, ...)); +void vfatal __PROTO ((const char *fmt, va_list args)); +void dump __PROTO ((const char *fmt, ...)); +void vdump __PROTO ((const char *fmt, va_list args)); +void _debugsys __PROTO ((size_t level, const char *fmt, ...)); +void _vdebugsys __PROTO ((size_t level, const char *fmt, va_list args)); +int errorsys __PROTO ((const char *fmt, ...)); +int verrorsys __PROTO ((const char *fmt, va_list args)); +void fatalsys __PROTO ((const char *fmt, ...)); +void vfatalsys __PROTO ((const char *fmt, va_list args)); +void dumpsys __PROTO ((const char *fmt, ...)); +void vdumpsys __PROTO ((const char *fmt, va_list args)); +int set_errno __PROTO ((int errnum)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/fifo.c b/fifo.c new file mode 100644 index 0000000..a8e3c5c --- /dev/null +++ b/fifo.c @@ -0,0 +1,462 @@ +/* +# libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - fifo and file control module + +=head1 SYNOPSIS + + #include + + int fcntl_set_flag (int fd, int flag); + int fcntl_clear_flag (int fd, int flag); + int fcntl_lock (int fd, int cmd, int type, int whence, int start, int len); + int nonblock_set (int fd, int arg); + int nonblock_on (int fd); + int nonblock_off (int fd); + int fifo_exists (const char *path, int prepare); + int fifo_has_reader (const char *path, int prepare); + int fifo_open (const char *path, mode_t mode, int lock); + +=head1 DESCRIPTION + +This module provides functions for exclusively opening a fifo for reading as +well as random shorthand functions for manipulating file flags and locks. + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include + +#include +#ifndef BSD_COMP +#define BSD_COMP /* for Solaris::FIONBIO */ +#endif +#include +#undef BSD_COMP + +#include "err.h" + +/* + +=item C + +Shorthand for setting the file flag C on file descriptor C using +I. All other flags are unaffected. Returns the same as I +with C or C as the command. + +=cut + +*/ + +int fcntl_set_flag(int fd, int flag) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + return -1; + + return fcntl(fd, F_SETFL, flags | flag); +} + +/* + +=item C + +Shorthand for clearing the file flag C from file descriptor C +using I. All other flags are unaffected. Returns the same as +I with C or C as the command. + +=cut + +*/ + +int fcntl_clear_flag(int fd, int flag) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + return -1; + + return fcntl(fd, F_SETFL, flags & ~flag); +} + +/* + +=item C + +Shorthand for performing discretionary file locking operations on file +descriptor C. C is the locking command and is passed to +I. C, C, C and C are used to fill a +I structure which is passed to I. Returns the same as +I with C as the command. + +=cut + +*/ + +int fcntl_lock(int fd, int cmd, int type, int whence, int start, int len) +{ + struct flock lock[1]; + + lock->l_type = type; + lock->l_whence = whence; + lock->l_start = start; + lock->l_len = len; + + return fcntl(fd, cmd, lock); +} + +/* + +=item C + +Sets non-blocking mode for file descriptor C if C is non-zero. +Clears non-blocking mode if C is zero. Returns the same as I +with C as the command. + +=cut + +*/ + +int nonblock_set(int fd, int arg) +{ + return ioctl(fd, FIONBIO, &arg); +} + +/* + +=item C + +Sets non-blocking mode for file descriptor C. Returns the same as +I with C as the command. + +=cut + +*/ + +int nonblock_on(int fd) +{ + return nonblock_set(fd, 1); +} + +/* + +=item C + +Clears non-blocking mode for the file descriptor C. Returns the same as +I with C as the command. + +=cut + +*/ + +int nonblock_off(int fd) +{ + return nonblock_set(fd, 0); +} + +/* + +=item C + +Determines whether or not C refers to a fifo. Returns 0 if C +doesn't exist or doesn't refer to a fifo. If C refers to a fifo, +returns 1. If C is non-zero, and C refers to a non-fifo, it +will be unlinked. On error, returns -1 with C set by I. + +=cut + +*/ + +int fifo_exists(const char *path, int prepare) +{ + struct stat status[1]; + + if (stat(path, status) == -1) + return (errno == ENOENT) ? 0 : -1; + + if (S_ISFIFO(status->st_mode) == 0) + { + if (prepare) + unlink(path); + + return 0; + } + + return 1; +} + +/* + +=item C + +Determines whether or not C refers to a fifo that is being read by +another process. If C does not exist or does not refer to a fifo or if +the fifo can't be opened for non-blocking I, returns 0. If +C is non-zero, and path refers to a non-fifo, it will be unlinked. +On error, returns -1 with C set by I or I. + +=cut + +*/ + +int fifo_has_reader(const char *path, int prepare) +{ + int fd; + + /* + ** Check that fifo exists and is a fifo. + ** If not, there can be no reader process. + */ + + switch (fifo_exists(path, prepare)) + { + case 0: return 0; + case -1: return -1; + } + + /* + ** Open the fifo for non-blocking write. + ** If there is no reader process, open() + ** will fail with errno == ENXIO. + */ + + if ((fd = open(path, O_WRONLY | O_NONBLOCK)) == -1) + return (errno == ENXIO) ? 0 : -1; + + close(fd); + + return 1; +} + +/* + +=item C + +Creates a fifo named C with creation mode C for reading. If +C already exists, is a fifo and has a reader process, return -1 with +C set to C. If the fifo is created (or an existing one +can be reused), two file descriptors are opened to the fifo. A read +descriptor and a write descriptor. The read descriptor is returned by this +function on success. The write descriptor only exists to ensure that there +is always at least one writer process for the fifo. This allows a I +on the read descriptor to block until another process writes to the fifo +rather than returning an C condition. This is done in a POSIX compliant +way. If C is non-zero, the fifo is exclusively locked. On error, +returnd -1 with C set by I, I, I, +I, I or I. + +=cut + +*/ + +int fifo_open(const char *path, mode_t mode, int lock) +{ + struct stat status[1]; + int rfd, wfd; + + /* Don't open the fifo for reading twice. */ + + switch (fifo_has_reader(path, 1)) + { + case 1: return set_errno(EADDRINUSE); + case -1: return -1; + } + + /* Create the fifo. */ + + if (mkfifo(path, S_IFIFO | mode) == -1 && errno != EEXIST) + return -1; + + /* + ** Open the fifo for non-blocking read only. + ** This prevents blocking while waiting for a + ** writer process. We are about to supply our + ** own writer. + */ + + if ((rfd = open(path, O_RDONLY | O_NONBLOCK)) == -1) + return -1; + + /* + ** A sanity check to make sure that what we have just + ** opened is really a fifo. Someone may have just replacd + ** the fifo with a file between fifo_has_reader and here. + */ + + if (fstat(rfd, status) == -1 || S_ISFIFO(status->st_mode) == 0) + { + close(rfd); + return -1; + } + + /* + ** Open the fifo for write only and leave this fd open. + ** This guarantees that there is always at least one + ** writer process. This prevents EOF indications being + ** returned from read() when there are no other writer + ** processes. + ** + ** Just opening the fifo "rw" works but it's undefined + ** by posix. + */ + + if ((wfd = open(path, O_WRONLY)) == -1) + { + close(rfd); + return -1; + } + + /* A sanity test on what we have just opened. */ + + if (fstat(wfd, status) == -1 || S_ISFIFO(status->st_mode) == 0) + { + close(rfd); + close(wfd); + return -1; + } + + /* + ** Exclusively lock the fifo to prevent two invocations + ** deciding that there's no reader and opening this fifo + ** at the same time. + */ + + if (lock && fcntl_lock(wfd, F_SETLK, F_WRLCK, SEEK_SET, 0, 0) == -1) + { + close(rfd); + close(wfd); + return -1; + } + + /* Now put the reader into blocking mode. */ + + if (nonblock_off(rfd) == -1) + { + close(rfd); + close(wfd); + return -1; + } + + /* + ** Flaw: If someone unceremoniously unlinks our fifo, we won't know about + ** it and nothing will stop another invocation from creating a new fifo and + ** handling it. This process would sleep forever in select(). + */ + + return rfd; +} + +/* + +=back + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +int main(int ac, char **av) +{ + const char * const name = "./fifo.fifo"; + const mode_t mode = S_IRUSR | S_IWUSR | S_IWGRP | S_IWOTH; + const int lock = 1; + int errors = 0; + int fd; + + printf("Testing: fifo\n"); + + if ((fd = fifo_open(name, mode, lock)) == -1) + ++errors, printf("Test1: fifo_open(\"%s\", %d, %d) failed (%s)\n", name, (int)mode, lock, strerror(errno)); + + if ((fcntl_lock(fd, F_SETLK, F_WRLCK, SEEK_SET, 0, 0)) != -1) + ++errors, printf("Test2: fcntl_lock() failed\n"); + + /* Should really test that the following non-blocking changes do occur */ + + if (nonblock_on(fd) == -1) + ++errors, printf("Test3: nonblock_on() failed (%s)\n", strerror(errno)); + + if (nonblock_off(fd) == -1) + ++errors, printf("Test4: nonblock_off() failed (%s)\n", strerror(errno)); + + if (fcntl_set_flag(fd, O_NONBLOCK) == -1) + ++errors, printf("Test5: fcntl_set_flag() failed (%s)\n", strerror(errno)); + + if (fcntl_clear_flag(fd, O_NONBLOCK) == -1) + ++errors, printf("Test6: fcntl_clear_flag() failed (%s)\n", strerror(errno)); + + unlink(name); + + if (errors) + printf("%d/6 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/fifo.h b/fifo.h new file mode 100644 index 0000000..bc74269 --- /dev/null +++ b/fifo.h @@ -0,0 +1,45 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_FIFO_H +#define LIBSLACK_FIFO_H + +#include + +#include + +__START_DECLS +int fcntl_set_flag __PROTO ((int fd, int flag)); +int fcntl_clear_flag __PROTO ((int fd, int flag)); +int fcntl_lock __PROTO ((int fd, int cmd, int type, int whence, int start, int len)); +int nonblock_set __PROTO ((int fd, int arg)); +int nonblock_on __PROTO ((int fd)); +int nonblock_off __PROTO ((int fd)); +int fifo_exists __PROTO ((const char *path, int prepare)); +int fifo_has_reader __PROTO ((const char *path, int prepare)); +int fifo_open __PROTO ((const char *path, mode_t mode, int lock)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/getopt.c b/getopt.c new file mode 100644 index 0000000..cfe6bac --- /dev/null +++ b/getopt.c @@ -0,0 +1,1597 @@ +/* Perform additional initialization for getopt functions in GNU libc. + Copyright (C) 1997, 1998 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Contributed by Ulrich Drepper , 1997. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* Attention: this file is *not* necessary when the GNU getopt functions + are used outside the GNU libc. Some additional functionality of the + getopt functions in GNU libc require this additional work. */ + +/* + +(c) 1993 by Thomas Koenig (ig25@rz.uni-karlsruhe.de) + +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one + +Since the Linux kernel and libraries are constantly changing, this +manual page may be incorrect or out-of-date. The author(s) assume no +responsibility for errors or omissions, or for damages resulting from +the use of the information contained herein. The author(s) may not +have taken the same level of care in the production of this manual, +which is licensed free of charge, as they might when working +professionally. + +Formatted or processed versions of this manual, if unaccompanied by +the source, must acknowledge the copyright and authors of this work. +License. +Modified Sat Jul 24 19:27:50 1993 by Rik Faith (faith@cs.unc.edu) +Modified Mon Aug 30 22:02:34 1995 by Jim Van Zandt + longindex is a pointer, has_arg can take 3 values, using consistent + names for optstring and longindex, \n in formats fixed. Documenting + opterr and getopt_long_only. Clarified explanations (borrowing heavily + from the source code). +Modified 8 May 1998 by Joseph S. Myers (jsm28@cam.ac.uk) + +=head1 NAME + +I - Parse command line options + +=head1 SYNOPSIS + + #include + + int getopt(int argc, char * const argv[], + const char *optstring); + + extern char *optarg; + extern int optind, opterr, optopt; + + #include + + int getopt_long(int argc, char * const argv[], + const char *optstring, const struct option *longopts, + int *longindex); + + int getopt_long_only(int argc, char * const argv[], + const char *optstring, const struct option *longopts, + int *longindex); + +=head1 DESCRIPTION + +The I function parses the command line arguments. Its arguments +C and C are the argument count and array as passed to the +I function on program invocation. +An element of C that starts with `-' (and is not exactly "-" or "--") +is an option element. The characters of this element +(aside from the initial `-') are option characters. If I +is called repeatedly, it returns successively each of the option characters +from each of the option elements. + +If I finds another option character, it returns that +character, updating the external variable C and a static +variable C so that the next call to I can +resume the scan with the following option character or +C-element. + +If there are no more option characters, I returns +C. Then C is the index in C of the first +C-element that is not an option. + +C is a string containing the legitimate option characters. +If such a character is followed by a colon, the option requires an argument, +so I places a pointer to the following text in the same +C-element, or the text of the following C-element, in +C. Two colons mean an option takes an optional arg; if there +is text in the current C-element, it is returned in C, +otherwise C is set to zero. This is a GNU extension. If +C contains C followed by a semicolon, then +C<-W foo> is treated as the long option C<--foo>. +(The C<-W> option is reserved by POSIX.2 for implementation extensions.) +This behaviour is a GNU extension, not available with libraries before +GNU libc 2. + +By default, I permutes the contents of C as it +scans, so that eventually all the non-options are at the end. Two +other modes are also implemented. If the first character of +C is `+' or the environment variable POSIXLY_CORRECT is +set, then option processing stops as soon as a non-option argument is +encountered. If the first character of C is `-', then +each non-option C-element is handled as if it were the argument of +an option with character code 1. (This is used by programs that were +written to expect options and other C-elements in any order +and that care about the ordering of the two.) +The special argument `--' forces an end of option-scanning regardless +of the scanning mode. + +If I does not recognize an option character, it prints an +error message to stderr, stores the character in C, and +returns `?'. The calling program may prevent the error message by +setting C to 0. + +The I function works like I +except that it also accepts long options, started out by two dashes. +Long option names may be abbreviated if the abbreviation is +unique or is an exact match for some defined option. A long option +may take a parameter, of the form C<--arg=param> or C<--arg param>. + +C is a pointer to the first element of an array of +C declared in C as + + struct option { + const char *name; + int has_arg; + int *flag; + int val; + }; + +The meanings of the different fields are: + +=over 4 + +=item C + +is the name of the long option. + +=item C + +is: +C (or 0) if the option does not take an argument, +C (or 1) if the option requires an argument, or +C (or 2) if the option takes an optional argument. + +=item C + +specifies how results are returned for a long option. If C +is C, then I returns C. (For example, +the calling program may set C to the equivalent short +option character.) Otherwise, I returns 0, and +C points to a variable which is set to C if the +option is found, but left unchanged if the option is not found. + +=item I + +is the value to return, or to load into the variable pointed +to by C. + +=back + +The last element of the array has to be filled with zeroes. + +If C is not C, it points to a variable which is +set to the index of the long option relative to C. + +I is like C, but `-' as well +as `--' can indicate a long option. If an option that starts with `-' +(not `--') doesn't match a long option, but does match a short option, +it is parsed as a short option instead. + +=head1 RETURN VALUE + +The I function returns the option character if the option was found +successfully, `:' if there was a missing parameter for one of the options, +`?' for an unknown option character, or C for the end of the option list. + +I and I also return the option +character when a short option is recognized. For a long option, they +return C if C is C, and 0 otherwise. Error and C +returns are the same as for I, plus `?' for an ambiguous match +or an extraneous parameter. + +=head1 ENVIRONMENT VARIABLES + +=over 4 + +=item C + +If this is set, then option processing stops as soon as a non-option +argument is encountered. + +=item C<_>IC<_GNU_nonoption_argv_flags_> + +This variable was used by I 2.0 to communicate to GNU libc which +arguments are the results of wildcard expansion and so should not be +considered as options. This behaviour was removed in I version 2.01, +but the support remains in GNU libc. + +=back + +=head1 EXAMPLE + +The following example program, from the source code, illustrates the +use of I with most of its features. + + #include + + int + main (argc, argv) + int argc; + char **argv; + { + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 1, 0, 'c'}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:012", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\\n"); + break; + + case '0': + case '1': + case '2': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\\n"); + digit_optind = this_option_optind; + printf ("option %c\\n", c); + break; + + case 'a': + printf ("option a\\n"); + break; + + case 'b': + printf ("option b\\n"); + break; + + case 'c': + printf ("option c with value `%s'\\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\\n"); + } + + exit (0); + } + +=head1 BUGS + +This manpage is confusing. + +The POSIX.2 specification of I has a technical +error described in POSIX.2 Interpretation 150. The GNU +implementation (and probably all other implementations) +implements the correct behaviour rather than that speci- +fied. + +=head1 CONFORMING TO + +=over 4 + +=item I: + +POSIX.2, provided the environment variable POSIXLY_CORRECT is set. +Otherwise, the elements of C aren't really const, because we +permute them. We pretend they`re const in the prototype to be +compatible with other systems. + +=back + +=cut + +*/ + +#include "getopt.h" +#include +#include +#include + +#if 0 +#include +#endif + +/* Variable to synchronize work. */ +char *__getopt_nonoption_flags; + +/* Lower-case digits. */ +const char _itoa_lower_digits[] + = "0123456789abcdefghijklmnopqrstuvwxyz"; +/* Upper-case digits. */ +const char _itoa_upper_digits[] + = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +/* Convert VALUE into ASCII in base BASE (2..36). + Write backwards starting the character just before BUFLIM. + Return the address of the first (left-to-right) character in the number. + Use upper case letters iff UPPER_CASE is nonzero. */ + +extern char *_itoa (unsigned long int value, char *buflim, + unsigned int base, int upper_case); + +static char * +_itoa_word (unsigned long value, char *buflim, + unsigned int base, int upper_case) +{ + extern const char _itoa_upper_digits[], _itoa_lower_digits[]; + const char *digits = upper_case ? _itoa_upper_digits : _itoa_lower_digits; + char *bp = buflim; + + switch (base) + { +#define SPECIAL(Base) \ + case Base: \ + do \ + *--bp = digits[value % Base]; \ + while ((value /= Base) != 0); \ + break + + SPECIAL (10); + SPECIAL (16); + SPECIAL (8); + default: + do + *--bp = digits[value % base]; + while ((value /= base) != 0); + } + return bp; +} + +/* Remove the environment variable "__GNU_nonoption_argv_flags_" if + it is still available. If the getopt functions are also used in the + application it does not exist anymore since it was saved for the use + in getopt. */ +void +__getopt_clean_environment (char **env) +{ + /* Bash 2.0 puts a special variable in the environment for each + command it runs, specifying which ARGV elements are the results + of file name wildcard expansion and therefore should not be + considered as options. */ + static const char envvar_tail[] = "_GNU_nonoption_argv_flags_="; + char var[100]; + char *cp, **ep; + size_t len; + + /* Construct the "__GNU_nonoption_argv_flags_=" string. We must + not use `sprintf'. */ + cp = memcpy (&var[sizeof (var) - sizeof (envvar_tail)], envvar_tail, + sizeof (envvar_tail)); + cp = _itoa_word (getpid (), cp, 10, 0); + *--cp = '_'; + len = (var + sizeof (var) - 1) - cp; + + for (ep = env; *ep != NULL; ++ep) + if (!strncmp (*ep, cp, len)) + { + /* Found it. Store this pointer and move later ones back. */ + char **dp = ep; + __getopt_nonoption_flags = &(*ep)[len]; + do + dp[0] = dp[1]; + while (*dp++); + /* Continue the loop in case the name appears again. */ + } +} + +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to drepper@gnu.org + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99 + Free Software Foundation, Inc. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in . + Ditto for AIX 3.2 and . */ +#ifndef _NO_PROTO +# define _NO_PROTO +#endif + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +# ifndef const +# define const +# endif +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +# include +# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +# define ELIDE_CODE +# endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +# include +# include +#endif /* GNU C library. */ + +#ifdef VMS +# include +# if HAVE_STRING_H - 0 +# include +# endif +#endif + +#ifndef _ +/* This is for other GNU distributions with internationalized messages. + When compiling libc, the _ macro is predefined. */ +# ifdef HAVE_LIBINTL_H +# include +# define _(msgid) gettext (msgid) +# else +# define _(msgid) (msgid) +# endif +#endif + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int optind = 1; + +/* Formerly, initialization of getopt depended on optind==0, which + causes problems with re-calling getopt as programs generally don't + know that. */ + +int __getopt_initialized; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return -1 with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +# include +# define my_index strchr +#else + +# if HAVE_STRING_H +# include +# else +# include +# endif + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +#ifndef getenv +extern char *getenv (); +#endif + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +# if (!defined __STDC__ || !__STDC__) && !defined strlen +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +# endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +#ifdef _LIBC +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; + +static int nonoption_flags_max_len; +static int nonoption_flags_len; + +static int original_argc; +static char *const *original_argv; + +/* Make sure the environment variable bash 2.0 puts in the environment + is valid for the getopt call we must make sure that the ARGV passed + to getopt is that one passed to the process. */ +static void +__attribute__ ((unused)) +store_args_and_env (int argc, char *const *argv) +{ + /* XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ + original_argc = argc; + original_argv = argv; +} +# ifdef text_set_element +text_set_element (__libc_subinit, store_args_and_env); +# endif /* text_set_element */ + +# define SWAP_FLAGS(ch1, ch2) \ + if (nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +#if defined __STDC__ && __STDC__ +static void exchange (char **); +#endif + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + +#ifdef _LIBC + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = malloc (top + 1); + if (new_str == NULL) + nonoption_flags_len = nonoption_flags_max_len = 0; + else + { + memset (__mempcpy (new_str, __getopt_nonoption_flags, + nonoption_flags_max_len), + '\0', top + 1 - nonoption_flags_max_len); + nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +#if defined __STDC__ && __STDC__ +static const char *_getopt_initialize (int, char *const *, const char *); +#endif +static const char * +_getopt_initialize (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + +#ifdef _LIBC + if (posixly_correct == NULL + && argc == original_argc && argv == original_argv) + { + if (nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = nonoption_flags_max_len = strlen (orig_str); + if (nonoption_flags_max_len < argc) + nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) malloc (nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + nonoption_flags_max_len = -1; + else + memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', nonoption_flags_max_len - len); + } + } + nonoption_flags_len = nonoption_flags_max_len; + } + else + nonoption_flags_len = 0; +#endif + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns -1. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + optarg = NULL; + + if (optind == 0 || !__getopt_initialized) + { + if (optind == 0) + optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring); + __getopt_initialized = 1; + } + + /* Test whether ARGV[optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#ifdef _LIBC +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \ + || (optind < nonoption_flags_len \ + && __getopt_nonoption_flags[optind] == '1')) +#else +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') +#endif + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (last_nonopt > optind) + last_nonopt = optind; + if (first_nonopt > optind) + first_nonopt = optind; + + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (optind < argc && NONOPTION_P) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (ordering == REQUIRE_ORDER) + return -1; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + } + + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +#endif /* Not ELIDE_CODE. */ + + +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "getopt.h" + +#if !defined __STDC__ || !__STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + +#include + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#define GETOPT_INTERFACE_VERSION 2 +#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2 +#include +#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION +#define ELIDE_CODE +#endif +#endif + +#ifndef ELIDE_CODE + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* Not ELIDE_CODE. */ + +#ifdef TEST + +#include + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ + +/* vi:set ts=8 sw=2 sts=2: */ diff --git a/getopt.h b/getopt.h new file mode 100644 index 0000000..af08649 --- /dev/null +++ b/getopt.h @@ -0,0 +1,171 @@ +/* Declarations for getopt. + Copyright (C) 1989,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#ifndef _GETOPT_H + +#ifndef __need_getopt +# define _GETOPT_H 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +#ifndef __need_getopt +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +# if defined __STDC__ && __STDC__ + const char *name; +# else + char *name; +# endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 +#endif /* need getopt */ + + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `getopt' that there are no more + options. + + If OPTS begins with `--', then non-option arguments are treated as + arguments to the option '\0'. This behavior is specific to the GNU + `getopt'. */ + +#if defined __STDC__ && __STDC__ +# ifdef __GNU_LIBRARY__ +/* Many other libraries have conflicting prototypes for getopt, with + differences in the consts, in stdlib.h. To avoid compilation + errors, only prototype getopt for the GNU C library. */ +extern int getopt (int __argc, char *const *__argv, const char *__shortopts); +# else /* not __GNU_LIBRARY__ */ +extern int getopt (); +# endif /* __GNU_LIBRARY__ */ + +# ifndef __need_getopt +extern int getopt_long (int __argc, char *const *__argv, const char *__shortopts, + const struct option *__longopts, int *__longind); +extern int getopt_long_only (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only); +# endif +#else /* not __STDC__ */ +extern int getopt (); +# ifndef __need_getopt +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +# endif +#endif /* __STDC__ */ + +#ifdef __cplusplus +} +#endif + +/* Make sure we later can get all the definitions and declarations. */ +#undef __need_getopt + +#endif /* getopt.h */ + +/* vi:set ts=8 sw=2 sts=2: */ diff --git a/hdr.h b/hdr.h new file mode 100644 index 0000000..05e441a --- /dev/null +++ b/hdr.h @@ -0,0 +1,58 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_HDR_H +#define LIBSLACK_HDR_H + +#undef __hdr_start_decls +#undef __hdr_stop_decls + +#ifdef __cplusplus +#define __hdr_start_decls extern "C" { +#define __hdr_stop_decls } +#else +#define __hdr_start_decls +#define __hdr_stop_decls +#endif + +#undef __hdr_proto +#undef const + +#if defined __STDC__ || defined __cplusplus +#define __hdr_proto(args) args +#else +#define __hdr_proto(args) () +#define const +#endif + +#undef __PROTO +#undef __START_DECLS +#undef __STOP_DECLS + +#define __PROTO __hdr_proto +#define __START_DECLS __hdr_start_decls +#define __STOP_DECLS __hdr_stop_decls + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/hsort.c b/hsort.c new file mode 100644 index 0000000..ef5f43e --- /dev/null +++ b/hsort.c @@ -0,0 +1,287 @@ +/* + +=head1 NAME + +I - generic heap sort + +=head1 SYNOPSIS + + #include + + void hsort(void *base, size_t nelem, size_t size, int (*cmp)(const void *, const void *)) + +=head1 DESCRIPTION + +I is an implementation of the heap sort algorithm. It sorts a table +of data in place. C points to the element at the base of the table. +C is the number of elements in the table. C is the size of the +elements in bytes. C is the comparison function, which is called with +two arguments that point to the elements being compared. As the function +must return an integer less than, equal to, or greater than zero, so must +the first argument to be considered be less than, equal to, or greater than +the second. + +=head1 NOTES + +The comparison function need not compare every byte, so arbitrary data may +be contained in the elements in addition to the values being compared. + +The order in the output of two items which compare as equal is +unpredictable. + +=head1 SEE ALSO + +L + +=head1 AUTHOR + + Stephen Russell, Department of Computer Science, + University of Sydney, 2006. + Australia + 1988 April 28 + +=cut + +*/ + +/* + * Generic Heapsort. + * + * Synopsis: + * hsort(void *base, size_t n, size_t size, int (*fn)(void *, void *)) + * Description: + * Hsort sorts the array of `n' items which starts at address `base'. + * The size of each item is as given. Items are compared by the function + * `fn', which is passed pointers to two items as arguments. The function + * should return < 0 if item1 < item2, == 0 if item1 == item2, and > 0 + * if item1 > item2. + * Version: + * 1988 April 28 + * Author: + * Stephen Russell, Department of Computer Science, + * University of Sydney, 2006 + * Australia. + */ + +#include "std.h" + +#include "hsort.h" + +#ifdef INLINE + +#define swap(p1, p2, n) \ +{\ + register char *_p1, *_p2;\ + register size_t _n;\ + register char _tmp;\ +\ + for (_p1 = p1, _p2 = p2, _n = n; _n-- > 0; )\ + {\ + _tmp = *_p1; *_p1++ = *_p2; *_p2++ = _tmp;\ + }\ +}\ + +#else + +/* + * Support routine for swapping elements of the array. + */ + +static void swap +( + register char *p1, + register char *p2, + register size_t n +) +{ + register char ctmp; + + /* + * On machines with no alignment restrictions for int's, + * the following loop may improve performance if moving lots + * of data. It has been commented out for portability. + + register int itmp; + + for ( ; n > sizeof(int); n -= sizeof(int)) + { + itmp = *(int *)p1; + *(int *)p1 = *(int *)p2; + p1 += sizeof(int); + *(int *)p2 = itmp; + p2 += sizeof(int); + } + + */ + + while (n-- != 0) + { + ctmp = *p1; *p1++ = *p2; *p2++ = ctmp; + } +} + +#endif + +/* + * To avoid function calls in the inner loops, the code responsible for + * constructing a heap from (part of) the array has been expanded inline. + * It is possible to convert this common code to a function. The three + * parameters base0, cmp and size are invariant - only the size of the + * gap and the high bound of the array change. In phase 1, gap decreases + * while hi is fixed. In phase 2, gap == size, and hi decreases. The + * variables p, q, and g are only used in this common code. + */ + +void hsort(void *base, size_t n, size_t size, hsort_cmp_t *cmp) +{ + register char *p, *q, *base0, *hi; + register unsigned int gap, g; + + if (n < 2) + return; + + base0 = (char *)base - size; /* set up address of h[0] */ + + /* + * The gap is the distance, in bytes, between h[0] and h[i], + * for some i. It is also the distance between h[i] and h[2*i]; + * that is, the distance between a node and its left child. + * The initial node of interest is h[n/2] (the rightmost + * interior node), so gap is set accordingly. The following is + * the only multiplication needed. + */ + + gap = (n >> 1) * size; /* initial gap is n/2*size */ + hi = base0 + gap + gap; /* calculate address of h[n] */ + if (n & 1) + hi += size; /* watch out for odd arrays */ + + /* + * Phase 1: Construct heap from random data. + * + * For i = n/2 downto 2, ensure h[i] is greater than its + * children h[2*i] and h[2*i+1]. By decreasing 'gap' at each + * iteration, we move down the heap towards h[2]. The final step + * of making h[1] the maximum value is done in the next phase. + */ + + for ( ; gap != size; gap -= size) + { + /* fixheap(base0, size, cmp, gap, hi) */ + + for (p = base0 + (g = gap); (q = p + g) <= hi; p = q) + { + g += g; /* double gap for next level */ + + /* + * Find greater of left and right children. + */ + + if (q != hi && (*cmp)(q + size, q) > 0) + { + q += size; /* choose right child */ + g += size; /* follow right subtree */ + } + + /* + * Compare with parent. + */ + + if ((*cmp)(p, q) >= 0) + break; /* order is correct */ + + swap(p, q, size); /* swap parent and child */ + } + } + + /* + * Phase 2: Each iteration makes the first item in the + * array the maximum, then swaps it with the last item, which + * is its correct position. The size of the heap is decreased + * each iteration. The gap is always "size", as we are interested + * in the heap starting at h[1]. + */ + + for ( ; hi != base; hi -= size) + { + /* fixheap(base0, size, cmp, gap (== size), hi) */ + + p = (char *)base; /* == base0 + size */ + for (g = size; (q = p + g) <= hi; p = q) + { + g += g; + if (q != hi && (*cmp)(q + size, q) > 0) + { + q += size; + g += size; + } + + if ((*cmp)(p, q) >= 0) + break; + + swap(p, q, size); + } + + swap((char *)base, hi, size); /* move largest item to end */ + } +} + +#ifdef TEST + +static char *string[4]; + +int cmp(const char **p1, const char **p2) +{ + return strcmp(*p1, *p2); +} + +static int errors = 0; + +static void verify(int test) +{ + if (strcmp(string[0], "abc")) + ++errors, printf("Test%d: 1st item is '%s' (not 'abc')\n", test, string[0]); + else if (strcmp(string[1], "def")) + ++errors, printf("Test%d: 2nd item is '%s' (not 'def')\n", test, string[1]); + else if (strcmp(string[2], "ghi")) + ++errors, printf("Test%d: 3rd item is '%s' (not 'ghi')\n", test, string[2]); + else if (strcmp(string[3], "jkl")) + ++errors, printf("Test%d: 4th item is '%s' (not 'jkl')\n", test, string[3]); +} + +int main(int ac, char **av) +{ + printf("Testing: hsort\n"); + + string[0] = "abc"; + string[1] = "def"; + string[2] = "ghi"; + string[3] = "jkl"; + hsort(string, 4, sizeof string[0], (hsort_cmp_t *)cmp); + verify(1); + + string[3] = "jkl"; + string[2] = "ghi"; + string[1] = "def"; + string[0] = "abc"; + hsort(string, 4, sizeof string[0], (hsort_cmp_t *)cmp); + verify(2); + + string[1] = "def"; + string[0] = "abc"; + string[3] = "jkl"; + string[2] = "ghi"; + hsort(string, 4, sizeof string[0], (hsort_cmp_t *)cmp); + verify(3); + + if (errors) + printf("%d/3 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/hsort.h b/hsort.h new file mode 100644 index 0000000..1a391c4 --- /dev/null +++ b/hsort.h @@ -0,0 +1,39 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_HSORT_H +#define LIBSLACK_HSORT_H + +#include + +#include + +typedef int hsort_cmp_t(const void *, const void *); + +__START_DECLS +void hsort __PROTO ((void *base, size_t n, size_t size, hsort_cmp_t *cmp)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/lib.h b/lib.h new file mode 100644 index 0000000..5cbfdb4 --- /dev/null +++ b/lib.h @@ -0,0 +1,55 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_LIB_H +#define LIBSLACK_LIB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef NEEDS_SPRINTF +#include +#endif + +#ifdef NEEDS_VSSCANF +#include +#endif + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/lim.c b/lim.c new file mode 100644 index 0000000..a415683 --- /dev/null +++ b/lim.c @@ -0,0 +1,1021 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - POSIX.1 limits module + +=head1 SYNOPSIS + + #include + + long limit_arg(void); + long limit_child(void); + long limit_tick(void); + long limit_group(void); + long limit_open(void); + long limit_stream(void); + long limit_tzname(void); + long limit_job(void); + long limit_save_ids(void); + long limit_version(void); + long limit_pcanon(const char *path); + long limit_fcanon(int fd); + long limit_canon(void); + long limit_pinput(const char *path); + long limit_finput(int fd); + long limit_input(void); + long limit_pvdisable(const char *path); + long limit_fvdisable(int fd); + long limit_vdisable(void); + long limit_plink(const char *path); + long limit_flink(int fd); + long limit_link(void); + long limit_pname(const char *path); + long limit_fname(int fd); + long limit_name(void); + long limit_ppath(const char *path); + long limit_fpath(int fd); + long limit_path(void); + long limit_ppipe(const char *path); + long limit_fpipe(int fd); + long limit_pnotrunc(const char *path); + long limit_fnotrunc(int fd); + long limit_notrunc(void); + long limit_pchown(const char *path); + long limit_fchown(int fd); + long limit_chown(void); + +=head1 DESCRIPTION + +This module provides functions for simply and reliably obtaining a POSIX.1 +limit for the current system or a usable default when a particular facility +is unlimited on the current system. These functions should always return a +usable value. If -1 is returned it means that this module is buggy :-) + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include + +#include + +enum name_t +{ + LIMIT_ARG, + LIMIT_CHILD, + LIMIT_TICK, + LIMIT_GROUP, + LIMIT_OPEN, + LIMIT_STREAM, + LIMIT_TZNAME, + LIMIT_JOB, + LIMIT_SAVE_IDS, + LIMIT_VERSION, + LIMIT_CANON, + LIMIT_INPUT, + LIMIT_VDISABLE, + LIMIT_LINK, + LIMIT_NAME, + LIMIT_PATH, + LIMIT_PIPE, + LIMIT_NOTRUNC, + LIMIT_CHOWN, + LIMIT_COUNT +}; + +typedef enum name_t name_t; +typedef struct conf_t conf_t; + +struct conf_t +{ + int init; /* has this value been initialised? */ + int idem; /* is this function idempotent? */ + int name; /* name argument for sysconf, [f]pathconf */ + long value; /* limit value */ + long guess; /* limit to use when indeterminate */ + off_t offset; /* offset to apply to value when not indeterminate */ +}; + +#ifndef STREAM_MAX +#define STREAM_MAX 0 +#endif + +#ifndef TZNAME_MAX +#define TZNAME_MAX 0 +#endif + +#ifndef NAME_MAX +#define NAME_MAX 0 +#endif + +static conf_t g_limit[] = +{ + { 0, 1, _SC_ARG_MAX, ARG_MAX, 1048576, 0 }, + { 0, 1, _SC_CHILD_MAX, CHILD_MAX, 32768, 0 }, + { 0, 1, _SC_CLK_TCK, 0, -1, 0 }, + { 0, 1, _SC_NGROUPS_MAX, NGROUPS_MAX, 1024, 0 }, + { 0, 1, _SC_OPEN_MAX, OPEN_MAX, 1024, 0 }, + { 0, 1, _SC_STREAM_MAX, STREAM_MAX, 1024, 0 }, + { 0, 1, _SC_TZNAME_MAX, TZNAME_MAX, 1024, 0 }, + { 0, 1, _SC_JOB_CONTROL, 0, 0, 0 }, + { 0, 1, _SC_SAVED_IDS, 0, 0, 0 }, + { 0, 1, _SC_VERSION, 0, 0, 0 }, + { 0, 0, _PC_MAX_CANON, MAX_CANON, 4096, 0 }, + { 0, 0, _PC_MAX_INPUT, MAX_INPUT, 4096, 0 }, + { 0, 0, _PC_VDISABLE, 0, 0, 0 }, + { 0, 0, _PC_LINK_MAX, LINK_MAX, 1024, 0 }, + { 0, 0, _PC_NAME_MAX, NAME_MAX, 1024, 0 }, + { 0, 0, _PC_PATH_MAX, PATH_MAX, 4096, 2 }, + { 0, 0, _PC_PIPE_BUF, PIPE_BUF, 4096, 0 }, + { 0, 0, _PC_NO_TRUNC, 0, 0, 0 }, + { 0, 0, _PC_CHOWN_RESTRICTED, 0, 0, 0 } +}; + +/* + +C + +Determines whether or not the given limit is known or it needs to be +obtained. C is one of C and represents the name argument to +I, I or I. If the given limit has +been initialised and is idempotent, returns 1. Otherwise, returns 0. If +C is defined, then limit values that are defined +in header files are taken into account. They shouldn't be used, however, +since the values defined in header files are the absolute minima allowed by +POSIX.1 which bears little resemblance to any actual system. + +*/ + +static int limit_needed(int limit) +{ + return +#ifdef LIMIT_USE_DEFINED_VALUES + !g_limit[limit].value && +#endif + !g_limit[limit].init || !g_limit[limit].idem; +} + +/* + +C + +Returns system limits using I. If the limit has been obtained in +the past and is idempotent, a cached value is returned rather than invoking +I again. If the limit is indeterminate, a predetermined +default/guess is returned. Whatever happens, a usable value is returned. + +*/ + +static long limit_sysconf(int limit) +{ + if (limit_needed(limit)) + { + if ((g_limit[limit].value = sysconf(g_limit[limit].name)) == -1) + g_limit[limit].value = g_limit[limit].guess; + else + g_limit[limit].value += g_limit[limit].offset; + + g_limit[limit].init = 1; + } + + return g_limit[limit].value; +} + +/* + +C + +Returns system limits using I. If the limit has been obtained +in the past and is idempotent, a cached value is returned rather than +invoking I again. If the limit is indeterminate, a +predetermined default/guess is returned. If the limit is determinate, a +predetermined amount may be added to its value. This is only needed for +C<_PC_PATH_MAX> which is the maximum length of a relative path. To be more +useful, 2 is added to this limit to account for the C<`/'> and C<`\0'> that +will be needed to form an absolute path. Whatever happens, a usable value is +returned. + +*/ + +static long limit_pathconf(int limit, const char *path) +{ + if (limit_needed(limit)) + { + if ((g_limit[limit].value = pathconf(path, g_limit[limit].name)) == -1) + g_limit[limit].value = g_limit[limit].guess; + else + g_limit[limit].value += g_limit[limit].offset; + + g_limit[limit].init = 1; + } + + return g_limit[limit].value; +} + +/* + +C + +Returns system limits using I. If the limit has been obtained +in the past and is idempotent, a cached value is returned rather than +invoking I again. If the limit is indeterminate, a +predetermined default/guess is returned. If the limit is determinate, a +predetermined amount may be added to its value. This is only needed for +C<_PC_PATH_MAX> which is the maximum length of a relative path. To be more +useful, 2 is added to this limit to account for the C<`/'> and C<`\0'> that +will be needed to form an absolute path. Whatever happens, a usable value is +returned. + +*/ + +static long limit_fpathconf(int limit, int fd) +{ + if (limit_needed(limit)) + { + if ((g_limit[limit].value = fpathconf(fd, g_limit[limit].name)) == -1) + g_limit[limit].value = g_limit[limit].guess; + else + g_limit[limit].value += g_limit[limit].offset; + + g_limit[limit].init = 1; + } + + return g_limit[limit].value; +} + +/* + +=item C + +Returns the (possibly cached) maximum length of arguments to the I +family of functions. If indeterminate, a usable guess (1048576) is returned. + +=cut + +*/ + +long limit_arg(void) +{ + return limit_sysconf(LIMIT_ARG); +} + +/* + +=item C + +Returns the (possibly cached) maximum number of simultaneous processes per +user id. If indeterminate, a usable guess (32768) is returned. + +=cut + +*/ + +long limit_child(void) +{ + return limit_sysconf(LIMIT_CHILD); +} + +/* + +=item C + +Returns the (possibly cached) number of clock ticks per second. If +indeterminate (which makes no sense), -1 is returned. + +=cut + +*/ + +long limit_tick(void) +{ + return limit_sysconf(LIMIT_TICK); +} + +/* + +=item C + +Returns the (possibly cached) maximum number of groups that a user may +belong to. If indeterminate, a usable guess (1024) is returned. + +=cut + +*/ + +long limit_group(void) +{ + return limit_sysconf(LIMIT_GROUP); +} + +/* + +=item C + +Returns the (possibly cached) maximum number of files that a process can +have open at any time. If indeterminate, a usable guess (1024) is returned. + +=cut + +*/ + +long limit_open(void) +{ + return limit_sysconf(LIMIT_OPEN); +} + +/* + +=item C + +Returns the (possibly cached) maximum number of streams that a process can +have open at any time. If indeterminate, a usable guess (1024) is returned. + +=cut + +*/ + +long limit_stream(void) +{ + return limit_sysconf(LIMIT_STREAM); +} + +/* + +=item C + +Returns the (possibly cached) maximum number of bytes in a timezone name. If +indeterminate, a usable guess (1024) is returned. + +=cut + +*/ + +long limit_tzname(void) +{ + return limit_sysconf(LIMIT_TZNAME); +} + +/* + +=item C + +Returns whether or not (possibly cached) job control is supported. + +=cut + +*/ + +long limit_job(void) +{ + return limit_sysconf(LIMIT_JOB); +} + +/* + +=item C + +Returns whether or not (possibly cached) a process has a saved set-user-id +and a saved set-group-id. + +=cut + +*/ + +long limit_save_ids(void) +{ + return limit_sysconf(LIMIT_SAVE_IDS); +} + +/* + +=item C + +Returns the (possibly cached) year and month the POSIX.1 standard was +approved in the format YYYYMML. + +=cut + +*/ + +long limit_version(void) +{ + return limit_sysconf(LIMIT_VERSION); +} + +/* + +=item C + +Returns the maximum length of a formatted input line for the terminal +referred to by C. If indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_pcanon(const char *path) +{ + return limit_pathconf(LIMIT_CANON, path); +} + +/* + +=item C + +Returns the maximum length of a formatted input line for the terminal +referred to by C. If indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_fcanon(int fd) +{ + return limit_fpathconf(LIMIT_CANON, fd); +} + +/* + +=item C + +Returns the maximum length of a formatted input line for the terminal +referred to by C<"/dev/tty">. If indeterminate, a usable guess (4096) is +returned. + +=cut + +*/ + +long limit_canon(void) +{ + return limit_pcanon("/dev/tty"); +} + +/* + +=item C + +Returns the maximum length of an input line for the terminal referred to by +C. If indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_pinput(const char *path) +{ + return limit_pathconf(LIMIT_INPUT, path); +} + +/* + +=item C + +Returns the maximum length of an input line for the terminal referred to by +C. If indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_finput(int fd) +{ + return limit_fpathconf(LIMIT_INPUT, fd); +} + +/* + +=item C + +Returns the maximum length of an input line for the terminal referred to by +C<"/dev/tty">. If indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_input(void) +{ + return limit_pinput("/dev/tty"); +} + +/* + +=item C + +Returns whether or not special character processing can be disabled for the +terminal referred to by C. + +=cut + +*/ + +long limit_pvdisable(const char *path) +{ + return limit_pathconf(LIMIT_VDISABLE, path); +} + +/* + +=item C + +Returns whether or not special character processing can be disabled for the +terminal referred to by C. + +=cut + +*/ + +long limit_fvdisable(int fd) +{ + return limit_fpathconf(LIMIT_VDISABLE, fd); +} + +/* + +=item C + +Returns whether or not special character processing can be disabled for the +terminal referred to by C<"/dev/tty">. + +=cut + +*/ + +long limit_vdisable(void) +{ + return limit_pvdisable("/dev/tty"); +} + +/* + +=item C + +Returns the maximum number of links to the file represented by C. If +indeterminate, a usable guess (1024) is returned. + +=cut + +*/ + +long limit_plink(const char *path) +{ + return limit_pathconf(LIMIT_LINK, path); +} + +/* + +=item C + +Returns the maximum number of links to the file represented by C. If +indeterminate, a usable guess (1024) is returned. + +=cut + +*/ + +long limit_flink(int fd) +{ + return limit_fpathconf(LIMIT_LINK, fd); +} + +/* + +=item C + +Returns the maximum number of links to the file represented by C<"/">. If +indeterminate, a usable guess (1024) is returned. + +=cut + +*/ + +long limit_link(void) +{ + return limit_plink("/"); +} + +/* + +=item C + +Returns the maximum length of a filename in the directory referred to by +C that the process can create. If indeterminate, a usable guess (1024) +is returned. + +=cut + +*/ + +long limit_pname(const char *path) +{ + return limit_pathconf(LIMIT_NAME, path); +} + +/* + +=item C + +Returns the maximum length of a filename in the directory referred to by +C that the process can create. If indeterminate, a usable guess (1024) +is returned. + +=cut + +*/ + +long limit_fname(int fd) +{ + return limit_fpathconf(LIMIT_NAME, fd); +} + +/* + +=item C + +Returns the maximum length of a filename in the directory referred to by +C<"/"> that the process can create. If indeterminate, a usable guess (1024) +is returned. + +=cut + +*/ + +long limit_name(void) +{ + return limit_pname("/"); +} + +/* + +=item C + +Returns the maximum length of an absolute pathname (including the C +character) when C is the current directory. If indeterminate, a usable +guess (4096) is returned. + +=cut + +*/ + +long limit_ppath(const char *path) +{ + return limit_pathconf(LIMIT_PATH, path); +} + +/* + +=item C + +Returns the maximum length of an absolute pathname (including the C +character) when C is the current directory. If indeterminate, a usable +guess (4096) is +returned. + +=cut + +*/ + +long limit_fpath(int fd) +{ + return limit_fpathconf(LIMIT_PATH, fd); +} + +/* + +=item C + +Returns the maximum length of an absolute pathname (including the C +character). If indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_path(void) +{ + return limit_ppath("/"); +} + +/* + +=item C + +Returns the size of the pipe buffer for the fifo referred to by C. If +indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_ppipe(const char *path) +{ + return limit_pathconf(LIMIT_PIPE, path); +} + +/* + +=item C + +Returns the size of the pipe buffer for the pipe or fifo referred to by +C. If indeterminate, a usable guess (4096) is returned. + +=cut + +*/ + +long limit_fpipe(int fd) +{ + return limit_fpathconf(LIMIT_PIPE, fd); +} + +/* + +=item C + +Returns whether or not an error is generated when accessing filenames longer +than the maximum filename length for the filesystem referred to by C. + +=cut + +*/ + +long limit_pnotrunc(const char *path) +{ + return limit_pathconf(LIMIT_NOTRUNC, path); +} + +/* + +=item C + +Returns whether or not an error is generated when accessing filenames longer +than the maximum filename length for the filesystem referred to by C. + +=cut + +*/ + +long limit_fnotrunc(int fd) +{ + return limit_fpathconf(LIMIT_NOTRUNC, fd); +} + +/* + +=item C + +Returns whether or not an error is generated when accessing filenames longer +than the maximum filename length for the root filesystem. + +=cut + +*/ + +long limit_notrunc(void) +{ + return limit_pnotrunc("/"); +} + +/* + +=item C + +Returns whether or not I may be called on the file referred to by +C or the files contained in the directory referred to by C. + +=cut + +*/ + +long limit_pchown(const char *path) +{ + return limit_pathconf(LIMIT_CHOWN, path); +} + +/* + +=item C + +Returns whether or not I may be called on the file referred to by +C or the files contained in the directory referred to by C. + +=cut + +*/ + +long limit_fchown(int fd) +{ + return limit_fpathconf(LIMIT_CHOWN, fd); +} + +/* + +=item C + +Returns whether or not I may be called on the files contained in +the root directory. + +=cut + +*/ + +long limit_chown(void) +{ + return limit_pchown("/"); +} + +/* + +=back + +=head1 RETURNS + +The functions that return a condition return 1 when the condition is true or +0 when it is false. All of the others either return the system limit +indicated or a predetermined, usable guess when the indicated limit is +indeterminate. These functions never return -1 (except possibly +I). + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +int main(int ac, char **av) +{ + int fds[2]; + long limit; + int errors = 0; + int verbose = (ac >= 2 && !strcmp(av[1], "-v")); + + printf("Testing: lim\n"); + + if ((limit = limit_arg()) == -1) + ++errors, printf("Test1: limit_arg() failed\n"); + else if (verbose) + printf("arg = %ld\n", limit); + + if ((limit = limit_child()) == -1) + ++errors, printf("Test2: limit_child() failed\n"); + else if (verbose) + printf("child = %ld\n", limit); + + if ((limit = limit_tick()) == -1) + ++errors, printf("Test3: limit_tick() failed\n"); + else if (verbose) + printf("tick = %ld\n", limit); + + if ((limit = limit_group()) == -1) + ++errors, printf("Test4: limit_group() failed\n"); + else if (verbose) + printf("group = %ld\n", limit); + + if ((limit = limit_open()) == -1) + ++errors, printf("Test5: limit_open() failed\n"); + else if (verbose) + printf("open = %ld\n", limit); + + if ((limit = limit_stream()) == -1) + ++errors, printf("Test6: limit_stream() failed\n"); + else if (verbose) + printf("stream = %ld\n", limit); + + if ((limit = limit_tzname()) == -1) + ++errors, printf("Test7: limit_tzname() failed\n"); + else if (verbose) + printf("tzname = %ld\n", limit); + + if ((limit = limit_job()) == -1) + ++errors, printf("Test8: limit_job() failed\n"); + else if (verbose) + printf("job = %ld\n", limit); + + if ((limit = limit_save_ids()) == -1) + ++errors, printf("Test9: limit_save_ids() failed\n"); + else if (verbose) + printf("save_ids = %ld\n", limit); + + if ((limit = limit_version()) == -1) + ++errors, printf("Test10: limit_version() failed\n"); + else if (verbose) + printf("version = %ld\n", limit); + + if ((limit = limit_canon()) == -1) + ++errors, printf("Test11: limit_canon() failed\n"); + else if (verbose) + printf("canon = %ld\n", limit); + + if ((limit = limit_input()) == -1) + ++errors, printf("Test12: limit_input() failed\n"); + else if (verbose) + printf("input = %ld\n", limit); + + if ((limit = limit_vdisable()) == -1) + ++errors, printf("Test13: limit_vdisable() failed\n"); + else if (verbose) + printf("vdisable = %ld\n", limit); + + if ((limit = limit_link()) == -1) + ++errors, printf("Test14: limit_link() failed\n"); + else if (verbose) + printf("link = %ld\n", limit); + + if ((limit = limit_name()) == -1) + ++errors, printf("Test15: limit_name() failed\n"); + else if (verbose) + printf("name = %ld\n", limit); + + if ((limit = limit_path()) == -1) + ++errors, printf("Test16: limit_path() failed\n"); + else if (verbose) + printf("path = %ld\n", limit); + + if (pipe(fds) == -1) + { + ++errors, printf("Test17: failed to test limit_fpipe() - pipe() failed (%s)\n", strerror(errno)); + } + else + { + if ((limit = limit_fpipe(fds[0])) == -1) + ++errors, printf("Test17: limit_fpipe() failed\n"); + else if (verbose) + printf("pipe = %ld\n", limit); + } + + if ((limit = limit_notrunc()) == -1) + ++errors, printf("Test18: limit_notrunc() failed\n"); + else if (verbose) + printf("notrunc = %ld\n", limit); + + if ((limit = limit_chown()) == -1) + ++errors, printf("Test19: limit_chown() failed\n"); + else if (verbose) + printf("chown = %ld\n", limit); + + if (errors) + printf("%d/19 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/lim.h b/lim.h new file mode 100644 index 0000000..f10d9a1 --- /dev/null +++ b/lim.h @@ -0,0 +1,70 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_LIM_H +#define LIBSLACK_LIM_H + +#include + +__START_DECLS +long limit_arg __PROTO ((void)); +long limit_child __PROTO ((void)); +long limit_tick __PROTO ((void)); +long limit_group __PROTO ((void)); +long limit_open __PROTO ((void)); +long limit_stream __PROTO ((void)); +long limit_tzname __PROTO ((void)); +long limit_job __PROTO ((void)); +long limit_save_ids __PROTO ((void)); +long limit_version __PROTO ((void)); +long limit_pcanon __PROTO ((const char *path)); +long limit_fcanon __PROTO ((int fd)); +long limit_canon __PROTO ((void)); +long limit_pinput __PROTO ((const char *path)); +long limit_finput __PROTO ((int fd)); +long limit_input __PROTO ((void)); +long limit_pvdisable __PROTO ((const char *path)); +long limit_fvdisable __PROTO ((int fd)); +long limit_vdisable __PROTO ((void)); +long limit_plink __PROTO ((const char *path)); +long limit_flink __PROTO ((int fd)); +long limit_link __PROTO ((void)); +long limit_pname __PROTO ((const char *path)); +long limit_fname __PROTO ((int fd)); +long limit_name __PROTO ((void)); +long limit_ppath __PROTO ((const char *path)); +long limit_fpath __PROTO ((int fd)); +long limit_path __PROTO ((void)); +long limit_ppipe __PROTO ((const char *path)); +long limit_fpipe __PROTO ((int fd)); +long limit_pnotrunc __PROTO ((const char *path)); +long limit_fnotrunc __PROTO ((int fd)); +long limit_notrunc __PROTO ((void)); +long limit_pchown __PROTO ((const char *path)); +long limit_fchown __PROTO ((int fd)); +long limit_chown __PROTO ((void)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/list.c b/list.c new file mode 100644 index 0000000..933a0c4 --- /dev/null +++ b/list.c @@ -0,0 +1,2226 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - list module + +=head1 SYNOPSIS + + #include + + typedef struct List List; + typedef struct Lister Lister; + typedef void list_destroy_t(void *item); + typedef void *list_copy_t(const void *item); + typedef int list_cmp_t(const void *a, const void *b); + typedef void list_action_t(void *item, size_t *index, void *data); + typedef void *list_map_t(void *item, size_t *index, void *data); + typedef int list_query_t(void *item, size_t *index, void *data); + + List *list_create(list_destroy_t *destroy); + List *list_make(list_destroy_t *destroy, ...); + List *list_vmake(list_destroy_t *destroy, va_list args); + List *list_copy(const List *src, list_copy_t *copy); + void list_release(List *list); + #define list_destroy(list) + void *list_destroy_func(List **list); + int list_own(List *list, list_destroy_t *destroy); + list_destroy_t *list_disown(List *list); + void *list_item(const List *list, size_t index); + int list_item_int(const List *list, size_t index); + int list_empty(const List *list); + size_t list_length(const List *list); + ssize_t list_last(const List *list); + List *list_remove(List *list, size_t index); + List *list_remove_range(List *list, size_t index, size_t range); + List *list_insert(List *list, size_t index, void *item); + List *list_insert_int(List *list, size_t index, int item); + List *list_insert_list(List *list, size_t index, const List *src, list_copy_t *copy); + List *list_append(List *list, void *item); + List *list_append_int(List *list, int item); + List *list_append_list(List *list, const List *src, list_copy_t *copy); + List *list_prepend(List *list, void *item); + List *list_prepend_int(List *list, int item); + List *list_prepend_list(List *list, const List *src, list_copy_t *copy); + List *list_replace(List *list, size_t index, size_t range, void *item); + List *list_replace_int(List *list, size_t index, size_t range, int item); + List *list_replace_list(List *list, size_t index, size_t range, const List *src, list_copy_t *copy); + List *list_extract(const List *list, size_t index, size_t range, list_copy_t *copy); + List *list_push(List *list, void *item); + List *list_push_int(List *list, int item); + void *list_pop(List *list); + int list_pop_int(List *list); + void *list_shift(List *list); + int list_shift_int(List *list); + List *list_unshift(List *list, void *item); + List *list_unshift_int(List *list, int item); + List *list_splice(List *list, size_t index, size_t range, list_copy_t *copy); + List *list_sort(List *list, list_cmp_t *cmp); + void list_apply(List *list, list_action_t *action, void *data); + List *list_map(List *list, list_destroy_t *destroy, list_map_t *map, void *data); + List *list_grep(List *list, list_query_t *grep, void *data); + ssize_t list_ask(List *list, ssize_t *index, list_query_t *query, void *data); + Lister *lister_create(List *list); + void lister_release(Lister * lister); + #define lister_destroy(lister) + void *lister_destroy_func(Lister **lister); + int lister_has_next(Lister *lister); + void *lister_next(Lister *lister); + int lister_next_int(Lister *lister); + void lister_remove(Lister *lister); + int list_has_next(List *list); + void *list_next(List *list); + int list_next_int(List *list); + void list_remove_current(List *list); + +=head1 DESCRIPTION + +This module provides functions for manipulating and iterating over lists of +homogeneous data (or heterogeneous data if it's polymorphic). I may +own their items. I created with a non-C destroy function use +that function to destroy an item when it is removed from the list and to +destroy each item when the list itself is destroyed. Be careful not to +insert items owned by one list into a list that doesn't own its own items +unless you know that the source list (and all of the shared items) will +outlive the destination list. + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include "list.h" +#include "mem.h" +#include "hsort.h" + +#define xor(a, b) (!(a) ^ !(b)) +#define iff(a, b) !xor(a, b) +#define implies(a, b) (!(a) || (b)) + +/* Minimum list length: must be a power of 2 */ + +static const size_t MIN_LIST_LENGTH = 4; + +struct List +{ + size_t size; /* number of item slots allocated */ + size_t length; /* number of items used */ + void **list; /* vector of items (void *) */ + list_destroy_t *destroy; /* item destructor, if any */ + Lister *lister; /* built-in iterator */ +}; + +struct Lister +{ + List *list; /* the list being iterated over */ + ssize_t index; /* the index of the current item */ +}; + +/* + +C + +Allocates enough memory to add C extra items to C if necessary. +On success, returns 0. On error, returns -1. + +*/ + +static int grow(List *list, size_t items) +{ + int grown = 0; + + while (list->length + items > list->size) + { + if (list->size) + list->size <<= 1; + else + list->size = MIN_LIST_LENGTH; + + grown = 1; + } + + if (grown) + return mem_resize(list->list, list->size) ? 0 : -1; + + return 0; +} + +/* + +C + +Allocates less memory for removing C items from C if necessary. +On success, returns 0. On error, returns -1. + +*/ + +static int shrink(List *list, size_t items) +{ + int shrunk = 0; + + while (list->length - items < list->size >> 1) + { + if (list->size == MIN_LIST_LENGTH) + break; + + list->size >>= 1; + shrunk = 1; + } + + if (shrunk) + return mem_resize(list->list, list->size) ? 0 : -1; + + return 0; +} + +/* + +C + +Slides C's items, starting at C, C slots to the right to +make room for more. On success, returns 0. On error, returns -1. + +*/ + +static int expand(List *list, ssize_t index, size_t range) +{ + if (grow(list, range) == -1) + return -1; + + memmove(list->list + index + range, list->list + index, (list->length - index) * sizeof(*list->list)); + list->length += range; + + return 0; +} + +/* + +C + +Slides C's items, starting at C, C slots to the left to +close a gap. On success, returns 0. On error, returns -1. + +*/ + +static int contract(List *list, ssize_t index, size_t range) +{ + memmove(list->list + index, list->list + index + range, (list->length - index) * sizeof(*list->list)); + + if (shrink(list, range) == -1) + return -1; + + list->length -= range; + + return 0; +} + +/* + +C + +Expands or contracts C as required so that I +occupies I. On success, returns 0. On error, +returns -1. + +*/ + +static int adjust(List *list, ssize_t index, size_t range, size_t length) +{ + if (range < length) + return expand(list, index + range, length - range); + + if (range > length) + return contract(list, index + length, range - length); + + return 0; +} + +/* + +C + +Destroys the items in I ranging from C to C. + +*/ + +static void killitems(List *list, size_t index, size_t range) +{ + while (range--) + { + if (list->destroy) + list->destroy(list->list[index]); + list->list[index++] = NULL; + } +} + +/* + +=item C + +Creates a I with C as its item destructor. On success, +returns the new list. On error, returns C. + +=cut + +*/ + +List *list_create(list_destroy_t *destroy) +{ + List *list; + + if (!(list = mem_new(List))) + return NULL; + + list->size = list->length = 0; + list->list = NULL; + list->destroy = destroy; + list->lister = NULL; + + return list; +} + +/* + +=item C + +Creates a I with C as its item destructor and the remaining +arguments as its initial items. On success, returns the new list. On error, +return C. + +=cut + +*/ + +List *list_make(list_destroy_t *destroy, ...) +{ + List *list; + va_list args; + va_start(args, destroy); + list = list_vmake(destroy, args); + va_end(args); + return list; +} + +/* + +=item C + +Equivalent to I with the variable argument list specified +directly as for I. + +=cut + +*/ + +List *list_vmake(list_destroy_t *destroy, va_list args) +{ + List *list; + void *item; + + if (!(list = list_create(destroy))) + return NULL; + + while ((item = va_arg(args, void *)) != NULL) + list_append(list, item); + + return list; +} + +/* + +=item C + +Creates a clone of C using C as the copy constructor (if not +C). On success, returns the clone. On error, returns C. + +=cut + +*/ + +List *list_copy(const List *src, list_copy_t *copy) +{ + if (!src) + return NULL; + + return list_extract(src, 0, src->length, copy); +} + +/* + +=item C + +Releases (deallocates) C, destroying its items if necessary. + +=cut + +*/ + +void list_release(List *list) +{ + if (!list) + return; + + if (list->list) + { + killitems(list, 0, list->length); + mem_release(list->list); + } + + lister_release(list->lister); + mem_release(list); +} + +/* + +=item C< #define list_destroy(list)> + +Destroys (deallocates and sets to C) C. Returns C. + +=item C + +Destroys (deallocates and sets to C) C. Returns C. This +function is exposed as an implementation side effect. Don't call it +directly. Call I instead. + +=cut + +*/ + +void *list_destroy_func(List **list) +{ + if (list && *list) + { + list_release(*list); + *list = NULL; + } + + return NULL; +} + +/* + +=item C + +Causes C to take ownership of its items. The items will be destroyed +using C when they are removed or when C is destroyed. +On success, returns 0. On error, returns -1. + +=cut + +*/ + +int list_own(List *list, list_destroy_t *destroy) +{ + if (!list || !destroy) + return -1; + + list->destroy = destroy; + return 0; +} + +/* + +=item C + +Causes C to relinquish ownership of its items. The items will not be +destroyed when they are removed from C or when C is destroyed. +On success, returns the previous destroy function, if any. On error, returns +C. + +=cut + +*/ + +list_destroy_t *list_disown(List *list) +{ + list_destroy_t *destroy; + + if (!list) + return NULL; + + destroy = list->destroy; + list->destroy = NULL; + return destroy; +} + +/* + +=item C + +Returns the C'th item in C. On error, returns C. + +=cut + +*/ + +void *list_item(const List *list, size_t index) +{ + if (!list) + return NULL; + + if (index >= list->length) + return NULL; + + return list->list[index]; +} + +/* + +=item C + +Returns the C'th item in C as an integer. On error, returns 0. + +=cut + +*/ + +int list_item_int(const List *list, size_t index) +{ + if (!list) + return 0; + + if (index >= list->length) + return 0; + + return (int)(list->list[index]); +} + +/* + +=item C + +Returns whether or not C is empty. + +=cut + +*/ + +int list_empty(const List *list) +{ + return !list || !list->length; +} + +/* + +=item C + +Returns the length of C. + +=cut + +*/ + +size_t list_length(const List *list) +{ + return (list) ? list->length : 0; +} + +/* + +=item C + +Returns the index of the last item in C, or -1 if there are no items. + +=cut + +*/ + +ssize_t list_last(const List *list) +{ + return (list) ? list->length - 1 : -1; +} + +/* + +=item C + +Removes the C'th item from C. On success, returns C. On +error, returns C. + +=cut + +*/ + +List *list_remove(List *list, size_t index) +{ + return list_remove_range(list, index, 1); +} + +/* + +=item C + +Removes C items from C starting at C. On success, returns +C. On error, returns C. + +=cut + +*/ + +List *list_remove_range(List *list, size_t index, size_t range) +{ + if (!list || list->length < index + range) + return NULL; + + killitems(list, index, range); + + if (contract(list, index, range) == -1) + return NULL; + + return list; +} + +/* + +=item C + +Adds C to C at position C. On success, returns C. +On error, returns C. + +=cut + +*/ + +List *list_insert(List *list, size_t index, void *item) +{ + if (!list || list->length < index) + return NULL; + + if (expand(list, index, 1) == -1) + return NULL; + + list->list[index] = item; + + return list; +} + +/* + +=item C + +Adds C to C at position C. On success, returns C. +On error, returns C. + +=cut + +*/ + +List *list_insert_int(List *list, size_t index, int item) +{ + return list_insert(list, index, (void *)item); +} + +/* + +=item C + +Inserts the items from C into C, starting at position C +using C as the copy constructor (if not C). On success, returns +C. On error, returns C. + +=cut + +*/ + +#define enlist(item, copy) ((copy) ? (copy)(item) : (item)) + +List *list_insert_list(List *list, size_t index, const List *src, list_copy_t *copy) +{ + size_t i; + + if (!src || !list || list->length < index || xor(list->destroy, copy)) + return NULL; + + if (expand(list, index, src->length) == -1) + return NULL; + + for (i = 0; i < src->length; ++i) + list->list[index + i] = enlist(src->list[i], copy); + + return list; +} + +/* + +=item C + +Appends C to C. On success, returns C. On error, returns +C. + +=cut + +*/ + +List *list_append(List *list, void *item) +{ + return list_insert(list, list_length(list), item); +} + +/* + +=item C + +Appends C to C. On success, returns C. On error, returns +C. + +=cut + +*/ + +List *list_append_int(List *list, int item) +{ + return list_insert_int(list, list_length(list), item); +} + +/* + +=item C + +Appends the items in C to C using C as the copy constructor +(if not C). On success, returns C. On error, returns C. + +=cut + +*/ + +List *list_append_list(List *list, const List *src, list_copy_t *copy) +{ + return list_insert_list(list, list_length(list), src, copy); +} + +/* + +=item C + +Prepends C to C. On success, returns C. On error, returns +C. + +=cut + +*/ + +List *list_prepend(List *list, void *item) +{ + return list_insert(list, 0, item); +} + +/* + +=item C + +Prepends C to C. On success, returns C. On error, returns +C. + +=cut + +*/ + +List *list_prepend_int(List *list, int item) +{ + return list_insert_int(list, 0, item); +} + +/* + +=item C + +Prepends the items in C to C using C as the copy constructor +(if not C). On success, returns C. On error, returns C. + +=cut + +*/ + +List *list_prepend_list(List *list, const List *src, list_copy_t *copy) +{ + return list_insert_list(list, 0, src, copy); +} + +/* + +=item C + +Replaces C items in C, starting at C, with C. +On success, returns C. On error, returns C. + +=cut + +*/ + +List *list_replace(List *list, size_t index, size_t range, void *item) +{ + if (!list || list->length < index + range) + return NULL; + + killitems(list, index, range); + + if (adjust(list, index, range, 1) == -1) + return NULL; + + list->list[index] = item; + + return list; +} + +/* + +=item C + +Replaces C items in C, starting at C, with C. +On success, returns C. On error, returns C. + +=cut + +*/ + +List *list_replace_int(List *list, size_t index, size_t range, int item) +{ + return list_replace(list, index, range, (void *)item); +} + +/* + +=item C + +Replaces C items in C, starting at C, with the items in +C using C as the copy constructor (if not C). On success, +return C. On error, returns C. + +=cut + +*/ + +List *list_replace_list(List *list, size_t index, size_t range, const List *src, list_copy_t *copy) +{ + size_t length; + + if (!src || !list || list->length < index + range || xor(list->destroy, copy)) + return NULL; + + killitems(list, index, range); + + if (adjust(list, index, range, length = list_length(src)) == -1) + return NULL; + + while (length--) + list->list[index + length] = enlist(src->list[length], copy); + + return list; +} + +/* + +=item C + +Creates a new list consisting of C items from C, starting at +C, using C as the copy constructor (if not C). On success, +returns the new list. On error, returns C. + +=cut + +*/ + +List *list_extract(const List *list, size_t index, size_t range, list_copy_t *copy) +{ + List *ret; + + if (!list || list->length < index + range || xor(list->destroy, copy)) + return NULL; + + if (!(ret = list_create(copy ? list->destroy : NULL))) + return NULL; + + while (range--) + if (list_append(ret, enlist(list->list[index++], copy)) == NULL) + return list_destroy(ret); + + return ret; +} + +#undef enlist + +/* + +=item C + +Pushes C onto the end of C. On success, returns I. Om +error, returns C. + +=cut + +*/ + +List *list_push(List *list, void *item) +{ + return list_append(list, item); +} + +/* + +=item C + +Pushes C onto the end of C. On success, returns I. Om +error, returns C. + +=cut + +*/ + +List *list_push_int(List *list, int item) +{ + return list_append_int(list, item); +} + +/* + +=item C + +Pops the last item off I. On success, returns the item popped. On +error, returns C. + +=cut + +*/ + +void *list_pop(List *list) +{ + void *item; + + if (!list || !list->length) + return NULL; + + item = list->list[list->length - 1]; + list->list[list->length - 1] = NULL; + + if (!list_remove(list, list->length - 1)) + return list->list[list->length - 1] = item, NULL; + + return item; +} + +/* + +=item C + +Pops the last item off I. On success, returns the item popped. On +error, returns C<0>. + +=cut + +*/ + +int list_pop_int(List *list) +{ + return (int)list_pop(list); +} + +/* + +=item C + +Removes and returns the first item in I. On success, returns the item +shifted. On error, returns C. + +=cut + +*/ + +void *list_shift(List *list) +{ + void *item; + + if (!list || !list->length) + return NULL; + + item = list->list[0]; + list->list[0] = NULL; + + if (!list_remove(list, 0)) + return list->list[0] = item, NULL; + + return item; +} + +/* + +=item C + +Removes and returns the first item in I. On success, returns the item +shifted. On error, returns C<0>. + +=cut + +*/ + +int list_shift_int(List *list) +{ + return (int)list_shift(list); +} + +/* + +=item C + +Inserts I at the start of I. On success, returns I. On +error, returns C. + +=cut + +*/ + +List *list_unshift(List *list, void *item) +{ + return list_prepend(list, item); +} + +/* + +=item C + +Inserts I at the start of I. On success, returns I. On +error, returns C. + +=cut + +*/ + +List *list_unshift_int(List *list, int item) +{ + return list_prepend_int(list, item); +} + +/* + +=item C + +Removes a sublist from C starting at C of length C items. +On success, returns the sublist. It is the caller's responsibility to +deallocate the new sublist with I or I. On +error, returns C. + +=cut + +*/ + +List *list_splice(List *list, size_t index, size_t range, list_copy_t *copy) +{ + List *ret; + + if (!list || list->length < index + range) + return NULL; + + ret = list_extract(list, index, range, copy); + if (!ret) + return NULL; + + if (!list_remove_range(list, index, range)) + { + list_release(ret); + return NULL; + } + + return ret; +} + +/* + +=item C + +Sorts the items in C using the item comparison function C and +I for lists of fewer than 1000 items and I for larger +lists. On success, returns C. On error, returns C. + +=cut + +*/ + +List *list_sort(List *list, list_cmp_t *cmp) +{ + if (!list || !list->list || !list->length) + return NULL; + + ((list->length >= 10000) ? hsort : qsort)(list->list, list->length, sizeof list->list[0], cmp); + + return list; +} + +/* + +=item C + +Invokes C for each of C's items. The arguments passed to +C are the item, a pointer to the loop variable containing the item's +position within C and C. + +=cut + +*/ + +void list_apply(List *list, list_action_t *action, void *data) +{ + size_t i; + + if (!list || !action) + return; + + for (i = 0; i < list->length; ++i) + action(list->list[i], &i, data); +} + +/* + +=item C + +Returns a new list containing the return values of C, invoked once for +each item in C. The arguments passed to C are the item, a pointer +to the loop variable containing the item's position within C and +C. C will be the destroy function for the returned list. +The user is responsible for deallocating the returned list with +I or I. On success, returns the new list. On +error, returns C. + +=cut + +*/ + +List *list_map(List *list, list_destroy_t *destroy, list_map_t *map, void *data) +{ + List *mapping; + size_t i; + + if (!list || !map) + return NULL; + + if (!(mapping = list_create(destroy))) + return NULL; + + for (i = 0; i < list->length; ++i) + { + if (!list_append(mapping, map(list->list[i], &i, data))) + { + list_release(mapping); + return NULL; + } + } + + return mapping; +} + +/* + +=item C + +Invokes C once for each item in C, building a new list +containing the items that resulted in C returning a non-zero value. +Returns this new list. The arguments passed to C are the item, a +pointer to the loop variable containing the item's position within C +and C. The user is responsible for deallocating the returned list with +I or I. Note that the list returned does not +own its items since it does not copy the items. On success, returns the new +list. On error, returns C. + +=cut + +*/ + +List *list_grep(List *list, list_query_t *grep, void *data) +{ + List *grepping; + size_t i; + + if (!list || !list) + return NULL; + + if (!(grepping = list_create(NULL))) + return NULL; + + for (i = 0; i < list->length; ++i) + { + if (grep(list->list[i], &i, data)) + if (!list_append(grepping, list->list[i])) + { + list_release(grepping); + return NULL; + } + } + + return grepping; +} + +/* + +=item C + +Invokes C on each item of C, starting at C until +C returns a non-zero value. The arguments passed to C are the +item, C and C. Returns the index of the item that satisfied +query, or -1 when query is not satisfied by any remaining items. The value +pointed to by C is set to the return value. + +=cut + +*/ + +ssize_t list_ask(List *list, ssize_t *index, list_query_t *query, void *data) +{ + size_t i; + + if (!list || !index || *index >= list->length || !query) + return -1; + + for (i = *index; i < list->length; ++i) + if (query(list->list[i], (size_t *)index, data)) + return *index = i; + + return *index = -1; +} + +/* + +=item C + +Creates an iterator for C. On success, returns the iterator. On error, +returns C. + +=cut + +*/ + +Lister *lister_create(List *list) +{ + Lister *lister; + + if (!list) + return NULL; + + if (!(lister = mem_new(Lister))) + return NULL; + + lister->list = list; + lister->index = -1; + + return lister; +} + +/* + +=item C + +Releases (deallocates) C. + +=cut + +*/ + +void lister_release(Lister *lister) +{ + mem_release(lister); +} + +/* + +=item C< #define lister_destroy(lister)> + +Destroys (deallocates and sets to C) C. + +=item C + +Destroys (deallocates and sets to C) C. Returns C. This +function is exposed as an implementation side effect. Don't call it +directly. Call I instead. + +=cut + +*/ + +void *lister_destroy_func(Lister **lister) +{ + if (lister && *lister) + { + lister_release(*lister); + *lister = NULL; + } + + return NULL; +} + +/* + +=item C + +Returns whether or not there is another item in the list being iterated over +by C. + +=cut + +*/ + +int lister_has_next(Lister *lister) +{ + return lister && lister->index + 1 < lister->list->length; +} + +/* + +=item C + +Returns the next item in the iteration C. On error, returns C. + +=cut + +*/ + +void *lister_next(Lister *lister) +{ + if (!lister) + return NULL; + + return list_item(lister->list, (size_t)++lister->index); +} + +/* + +=item C + +Returns the next item in the iteration C as an integer. On error, +returns C<-1>. + +=cut + +*/ + +int lister_next_int(Lister *lister) +{ + if (!lister) + return -1; + + return list_item_int(lister->list, (size_t)++lister->index); +} + +/* + +=item C + +Removes the current item in the iteration C. The next item in the +iteration is the item following the removed item, if any. This must be called +after I or I. + +=cut + +*/ + +void lister_remove(Lister *lister) +{ + if (!lister || lister->index == -1) + return; + + list_remove(lister->list, (size_t)lister->index--); +} + +/* + +=item C + +Returns whether or not there is another item in C. The first time this +is called, a new, internal I will be created (Note: there can be +only one). When there are no more items, returns zero and destroys the +internal iterator. When it returns a non-zero value, use I to +retrieve the next item. + +=cut + +*/ + +int list_has_next(List *list) +{ + int ret; + + if (!list) + return 0; + + if (!list->lister) + list->lister = lister_create(list); + + ret = lister_has_next(list->lister); + if (!ret) + lister_destroy(list->lister); + + return ret; +} + +/* + +=item C + +Returns the next item in C using it's internal iterator. On error, +returns C. + +=cut + +*/ + +void *list_next(List *list) +{ + if (!list || !list->lister) + return NULL; + + return lister_next(list->lister); +} + +/* + +=item C + +Returns the next item in C as an integer using it's internal iterator. +On error, returns C<-1>. + +=cut + +*/ + +int list_next_int(List *list) +{ + if (!list || !list->lister) + return -1; + + return lister_next_int(list->lister); +} + +/* + +=item C + +Removes the current item in C using it's internal iterator. The next +item in the iteration is the item following the removed item, if any. This +must be called after I. + +=cut + +*/ + +void list_remove_current(List *list) +{ + if (!list || !list->lister) + return; + + lister_remove(list->lister); +} + +/* + +=back + +=head1 BUGS + +Little attempt is made to protect the client from sharing items between +lists with differing ownership policies and getting it wrong. When copying +items from any list to an owning list, a copy function must be supplied. +When adding a single item to an owning list, it is assumed that the list may +take over ownership of the item. When an owning list is destroyed, all of +its items are destroyed. If any of these items had been shared with a +non-owning list that outlived the owning list, then the non-owning list will +contain items that point to deallocated memory. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +#if 0 +static void list_print(const char *name, List *list) +{ + int i; + + if (!list) + { + printf("%s = nil\n", name); + return; + } + + /* printf("%s = { destroy = %p, size = %d, length = %d, list = { ", name, list->destroy, list->size, list->length); */ + printf("%s = { ", name); + + for (i = 0; i < list_length(list); ++i) + { + if (i) + printf(", "); + + /* printf("\"%s\"(%p)", (char *)list_item(list, i), list_item(list, i)); */ + printf("\"%s\"", (char *)list_item(list, i)); + } + + printf(" }\n"); +} +#endif + +char action_data[1024]; + +void action(void *item, size_t *index) +{ + strcat(action_data, item); +} + +int query_data[] = { 2, 6, 8 , -1 }; + +int query(void *item, size_t *index) +{ + return !strcmp((const char *)item, "def"); +} + +int sort_cmp(const char **a, const char **b) +{ + return strcmp(*a, *b); +} + +int mapf(int item, size_t *index, int *sum) +{ + return (sum) ? *sum += item : item; +} + +int grepf(int item, size_t *index, int *data) +{ + return !(item & 1); +} + +int main(int ac, char **av) +{ + int errors = 0; + List *a, *b, *c, *d; + Lister *lister; + ssize_t index = 0; + int i; + + printf("Testing: list\n"); + + a = list_make(NULL, "abc", "def", "ghi", "jkl", NULL); + if (!a) + ++errors, printf("Test1: list_make() failed\n"); + else if (list_length(a) != 4) + ++errors, printf("Test1: list_make() created a list with %d items (not 4) or list_length() failed\n", list_length(a)); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "abc")) + ++errors, printf("Test1: list_make(): 1st item is '%s' not 'abc'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "def")) + ++errors, printf("Test1: list_make(): 2nd item is '%s' not 'def'\n", (char *)list_item(a, 1)); + else if (!list_item(a, 2) || strcmp(list_item(a, 2), "ghi")) + ++errors, printf("Test1: list_make(): 3rd item is '%s' not 'ghi'\n", (char *)list_item(a, 2)); + else if (!list_item(a, 3) || strcmp(list_item(a, 3), "jkl")) + ++errors, printf("Test1: list_make(): 4th item is '%s' not 'jkl'\n", (char *)list_item(a, 3)); + + if (!(b = list_create(NULL))) + ++errors, printf("Test2: list_create(NULL) failed\n"); + + if (!list_empty(b)) + ++errors, printf("Test3: list_empty() on empty list failed\n"); + + if (!list_append(b, "abc") || list_length(b) != 1 || strcmp(list_item(b, 0), "abc")) + ++errors, printf("Test4: list_append() failed\n"); + + if (list_empty(b)) + ++errors, printf("Test5: list_empty() on non-empty list failed\n"); + + if (!list_prepend(b, "def") || list_length(b) != 2 || strcmp(list_item(b, 0), "def") || strcmp(list_item(b, 1), "abc")) + ++errors, printf("Test6: list_prepend() failed\n"); + + if (!list_insert(b, 1, "ghi") || list_length(b) != 3 || strcmp(list_item(b, 0), "def") || strcmp(list_item(b, 1), "ghi") || strcmp(list_item(b, 2), "abc")) + ++errors, printf("Test7: list_insert() failed\n"); + + if (list_last(b) != 2) + ++errors, printf("Test8: list_last() failed (returned %d, not 2)\n", list_last(b)); + + c = list_copy(a, (list_copy_t *)free); + if (c) + ++errors, printf("Test9: list_copy() with copy() but no destroy() failed\n"); + + c = list_copy(a, NULL); + if (!c) + ++errors, printf("Test10: list_copy() without copy() or destroy() failed\n"); + else if (list_length(c) != 4) + ++errors, printf("Test10: list_copy() created a list with %d items (not 4) or list_length() failed\n", list_length(c)); + else if (!list_item(c, 0) || strcmp(list_item(c, 0), "abc")) + ++errors, printf("Test10: list_copy(): 1st item is '%s' not 'abc'\n", (char *)list_item(c, 0)); + else if (!list_item(c, 0) || strcmp(list_item(c, 1), "def")) + ++errors, printf("Test10: list_copy(): 2nd item is '%s' not 'def'\n", (char *)list_item(c, 1)); + else if (!list_item(c, 0) || strcmp(list_item(c, 2), "ghi")) + ++errors, printf("Test10: list_copy(): 3rd item is '%s' not 'ghi'\n", (char *)list_item(c, 2)); + else if (!list_item(c, 0) || strcmp(list_item(c, 3), "jkl")) + ++errors, printf("Test10: list_copy(): 4th item is '%s' not 'jkl'\n", (char *)list_item(c, 3)); + + list_destroy(c); + if (c) + ++errors, printf("Test 11: list_destroy() failed\n"); + + if (!(c = list_create((list_destroy_t *)free))) + ++errors, printf("Test12: list_create(free) failed\n"); + else + { + if (list_append(c, mem_strdup("abc")) == NULL) + ++errors, printf("Test13: list_append(\"abc\") failed\n"); + if (list_append(c, mem_strdup("def")) == NULL) + ++errors, printf("Test14: list_append(\"def\") failed\n"); + if (list_append(c, mem_strdup("ghi")) == NULL) + ++errors, printf("Test15: list_append(\"ghi\") failed\n"); + if (list_append(c, mem_strdup("jkl")) == NULL) + ++errors, printf("Test16: list_append(\"jkl\") failed\n"); + + d = list_copy(c, NULL); + if (d) + ++errors, printf("Test17: list_copy() with destroy() but no copy() failed\n"); + + d = list_copy(c, (list_copy_t *)mem_strdup); + if (!d) + ++errors, printf("Test18: list_copy() with copy() and destroy() failed\n"); + } + + if (!list_remove(a, 3)) + ++errors, printf("Test19: list_remove() failed\n"); + else if (list_length(a) != 3) + ++errors, printf("Test19: list_remove() from end of list failed: has %d items (not 3)\n", list_length(a)); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "abc")) + ++errors, printf("Test19: list_remove(): 1st item is '%s' not 'abc'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "def")) + ++errors, printf("Test19: list_remove(): 2nd item is '%s' not 'def'\n", (char *)list_item(a, 1)); + else if (!list_item(a, 2) || strcmp(list_item(a, 2), "ghi")) + ++errors, printf("Test19: list_remove(): 3rd item is '%s' not 'ghi'\n", (char *)list_item(a, 2)); + + if (!list_remove(a, 0)) + ++errors, printf("Test20: list_remove() failed\n"); + else if (list_length(a) != 2) + ++errors, printf("Test20: list_remove() from beginning of list failed: has %d items (not 2)\n", list_length(a)); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "def")) + ++errors, printf("Test20: list_remove(): 1st item is '%s' not 'def'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "ghi")) + ++errors, printf("Test20: list_remove(): 2nd item is '%s' not 'ghi'\n", (char *)list_item(a, 1)); + + if (!list_replace(a, 1, 1, "123")) + ++errors, printf("Test21: list_replace() failed\n"); + else if (list_length(a) != 2) + ++errors, printf("Test21: list_replace() failed: has %d items (not 2)\n", list_length(a)); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "def")) + ++errors, printf("Test21: list_replace(): 1st item is '%s' not 'def'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "123")) + ++errors, printf("Test21: list_replace(): 2nd item is '%s' not '123'\n", (char *)list_item(a, 1)); + + if (!list_append_list(a, b, NULL)) + ++errors, printf("Test22: list_append_list() failed\n"); + else if (list_length(a) != 5) + ++errors, printf("Test22: list_append_list() failed: has %d items (not 5)\n", list_length(a)); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "def")) + ++errors, printf("Test22: list_append_list(): 1st item is '%s' not 'def'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "123")) + ++errors, printf("Test22: list_append_list(): 2nd item is '%s' not '123'\n", (char *)list_item(a, 1)); + else if (!list_item(a, 2) || strcmp(list_item(a, 2), "def")) + ++errors, printf("Test22: list_append_list(): 3rd item is '%s' not 'def'\n", (char *)list_item(a, 2)); + else if (!list_item(a, 3) || strcmp(list_item(a, 3), "ghi")) + ++errors, printf("Test22: list_append_list(): 4th item is '%s' not 'ghi'\n", (char *)list_item(a, 3)); + else if (!list_item(a, 4) || strcmp(list_item(a, 4), "abc")) + ++errors, printf("Test22: list_append_list(): 5th item is '%s' not 'abc'\n", (char *)list_item(a, 4)); + + if (!list_prepend_list(a, c, NULL)) + ++errors, printf("Test23: list_prepend_list() failed\n"); + else if (list_length(a) != 9) + ++errors, printf("Test23: list_prepend_list() failed: has %d items (not 9)\n", list_length(a)); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "abc")) + ++errors, printf("Test23: list_prepend_list(): 1st item is '%s' not 'abc'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "def")) + ++errors, printf("Test23: list_prepend_list(): 2nd item is '%s' not 'def'\n", (char *)list_item(a, 1)); + else if (!list_item(a, 2) || strcmp(list_item(a, 2), "ghi")) + ++errors, printf("Test23: list_prepend_list(): 3rd item is '%s' not 'ghi'\n", (char *)list_item(a, 2)); + else if (!list_item(a, 3) || strcmp(list_item(a, 3), "jkl")) + ++errors, printf("Test23: list_prepend_list(): 4th item is '%s' not 'jkl'\n", (char *)list_item(a, 3)); + else if (!list_item(a, 4) || strcmp(list_item(a, 4), "def")) + ++errors, printf("Test23: list_prepend_list(): 5th item is '%s' not 'def'\n", (char *)list_item(a, 4)); + else if (!list_item(a, 5) || strcmp(list_item(a, 5), "123")) + ++errors, printf("Test23: list_prepend_list(): 6th item is '%s' not '123'\n", (char *)list_item(a, 5)); + else if (!list_item(a, 6) || strcmp(list_item(a, 6), "def")) + ++errors, printf("Test23: list_prepend_list(): 7th item is '%s' not 'def'\n", (char *)list_item(a, 6)); + else if (!list_item(a, 7) || strcmp(list_item(a, 7), "ghi")) + ++errors, printf("Test23: list_prepend_list(): 8th item is '%s' not 'ghi'\n", (char *)list_item(a, 7)); + else if (!list_item(a, 8) || strcmp(list_item(a, 8), "abc")) + ++errors, printf("Test23: list_prepend_list(): 9th item is '%s' not 'abc'\n", (char *)list_item(a, 8)); + + if (!list_insert_list(b, 1, c, NULL)) + ++errors, printf("Test24: list_insert_list() failed\n"); + else if (list_length(b) != 7) + ++errors, printf("Test24: list_insert_list() failed: has %d items (not 7)\n", list_length(b)); + else if (!list_item(b, 0) || strcmp(list_item(b, 0), "def")) + ++errors, printf("Test24: list_insert_list(): 1st item is '%s' not 'def'\n", (char *)list_item(b, 0)); + else if (!list_item(b, 1) || strcmp(list_item(b, 1), "abc")) + ++errors, printf("Test24: list_insert_list(): 2nd item is '%s' not 'abc'\n", (char *)list_item(b, 1)); + else if (!list_item(b, 2) || strcmp(list_item(b, 2), "def")) + ++errors, printf("Test24: list_insert_list(): 3rd item is '%s' not 'def'\n", (char *)list_item(b, 2)); + else if (!list_item(b, 3) || strcmp(list_item(b, 3), "ghi")) + ++errors, printf("Test24: list_insert_list(): 4th item is '%s' not 'ghi'\n", (char *)list_item(b, 3)); + else if (!list_item(b, 4) || strcmp(list_item(b, 4), "jkl")) + ++errors, printf("Test24: list_insert_list(): 5th item is '%s' not 'jkl'\n", (char *)list_item(b, 4)); + else if (!list_item(b, 5) || strcmp(list_item(b, 5), "ghi")) + ++errors, printf("Test24: list_insert_list(): 6th item is '%s' not 'ghi'\n", (char *)list_item(b, 5)); + else if (!list_item(b, 6) || strcmp(list_item(b, 6), "abc")) + ++errors, printf("Test24: list_insert_list(): 7th item is '%s' not 'abc'\n", (char *)list_item(b, 6)); + + if (list_replace_list(c, 1, 2, d, NULL)) + ++errors, printf("Test25: list_replace_list() with destroy() but not copy() failed\n"); + + if (list_replace_list(a, 1, 2, d, (list_copy_t *)mem_strdup)) + ++errors, printf("Test26: list_replace_list() with copy() but not destroy() failed\n"); + + if (!list_replace_list(a, 1, 2, d, NULL)) + ++errors, printf("Test27: list_replace_list() failed\n"); + else if (list_length(a) != 11) + ++errors, printf("Test27: list_insert_list() failed: has %d items (not 11)\n", list_length(a)); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "abc")) + ++errors, printf("Test27: list_insert_list(): 1st item is '%s' not 'abc'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "abc")) + ++errors, printf("Test27: list_insert_list(): 2nd item is '%s' not 'abc'\n", (char *)list_item(a, 1)); + else if (!list_item(a, 2) || strcmp(list_item(a, 2), "def")) + ++errors, printf("Test27: list_insert_list(): 3rd item is '%s' not 'def'\n", (char *)list_item(a, 2)); + else if (!list_item(a, 3) || strcmp(list_item(a, 3), "ghi")) + ++errors, printf("Test27: list_insert_list(): 4th item is '%s' not 'ghi'\n", (char *)list_item(a, 3)); + else if (!list_item(a, 4) || strcmp(list_item(a, 4), "jkl")) + ++errors, printf("Test27: list_insert_list(): 5th item is '%s' not 'jkl'\n", (char *)list_item(a, 4)); + else if (!list_item(a, 5) || strcmp(list_item(a, 5), "jkl")) + ++errors, printf("Test27: list_insert_list(): 6th item is '%s' not 'jkl'\n", (char *)list_item(a, 5)); + else if (!list_item(a, 6) || strcmp(list_item(a, 6), "def")) + ++errors, printf("Test27: list_insert_list(): 7th item is '%s' not 'def'\n", (char *)list_item(a, 6)); + else if (!list_item(a, 7) || strcmp(list_item(a, 7), "123")) + ++errors, printf("Test27: list_insert_list(): 8th item is '%s' not '123'\n", (char *)list_item(a, 7)); + else if (!list_item(a, 8) || strcmp(list_item(a, 8), "def")) + ++errors, printf("Test27: list_insert_list(): 9th item is '%s' not 'def'\n", (char *)list_item(a, 8)); + else if (!list_item(a, 9) || strcmp(list_item(a, 9), "ghi")) + ++errors, printf("Test27: list_insert_list(): 10th item is '%s' not 'ghi'\n", (char *)list_item(a, 9)); + else if (!list_item(a, 10) || strcmp(list_item(a, 10), "abc")) + ++errors, printf("Test27: list_insert_list(): 11th item is '%s' not 'abc'\n", (char *)list_item(a, 10)); + + if (!list_remove_range(b, 1, 3)) + ++errors, printf("Test28: list_remove_range() failed\n"); + else if (list_length(b) != 4) + ++errors, printf("Test28: list_insert_list() failed: has %d items (not 4)\n", list_length(b)); + else if (!list_item(b, 0) || strcmp(list_item(b, 0), "def")) + ++errors, printf("Test28: list_insert_list(): 1st item is '%s' not 'def'\n", (char *)list_item(b, 0)); + else if (!list_item(b, 1) || strcmp(list_item(b, 1), "jkl")) + ++errors, printf("Test28: list_insert_list(): 2nd item is '%s' not 'jkl'\n", (char *)list_item(b, 1)); + else if (!list_item(b, 2) || strcmp(list_item(b, 2), "ghi")) + ++errors, printf("Test28: list_insert_list(): 3rd item is '%s' not 'ghi'\n", (char *)list_item(b, 2)); + else if (!list_item(b, 3) || strcmp(list_item(b, 3), "abc")) + ++errors, printf("Test28: list_insert_list(): 4th item is '%s' not 'abc'\n", (char *)list_item(b, 3)); + + list_apply(a, (list_action_t *)action, NULL); + if (strcmp(action_data, "abcabcdefghijkljkldef123defghiabc")) + ++errors, printf("Test29: list_apply() failed\n"); + + for (i = 0; list_ask(a, &index, (list_query_t *)query, NULL) != -1; ++i, ++index) + { + if (index != query_data[i]) + { + ++errors, printf("Test30: list_ask returned %d (not %d)\n", index, query_data[i]); + break; + } + } + + if (!list_sort(a, (list_cmp_t *)sort_cmp)) + ++errors, printf("Test31: list_sort() failed\n"); + else if (!list_item(a, 0) || strcmp(list_item(a, 0), "123")) + ++errors, printf("Test31: list_sort(): 1st item is '%s' not '123'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "abc")) + ++errors, printf("Test31: list_sort(): 2nd item is '%s' not 'abc'\n", (char *)list_item(a, 1)); + else if (!list_item(a, 2) || strcmp(list_item(a, 2), "abc")) + ++errors, printf("Test31: list_sort(): 3rd item is '%s' not 'abc'\n", (char *)list_item(a, 2)); + else if (!list_item(a, 3) || strcmp(list_item(a, 3), "abc")) + ++errors, printf("Test31: list_sort(): 4th item is '%s' not 'abc'\n", (char *)list_item(a, 3)); + else if (!list_item(a, 4) || strcmp(list_item(a, 4), "def")) + ++errors, printf("Test31: list_sort(): 5th item is '%s' not 'def'\n", (char *)list_item(a, 4)); + else if (!list_item(a, 5) || strcmp(list_item(a, 5), "def")) + ++errors, printf("Test31: list_sort(): 6th item is '%s' not 'def'\n", (char *)list_item(a, 5)); + else if (!list_item(a, 6) || strcmp(list_item(a, 6), "def")) + ++errors, printf("Test31: list_sort(): 7th item is '%s' not 'def'\n", (char *)list_item(a, 6)); + else if (!list_item(a, 7) || strcmp(list_item(a, 7), "ghi")) + ++errors, printf("Test31: list_sort(): 8th item is '%s' not 'ghi'\n", (char *)list_item(a, 7)); + else if (!list_item(a, 8) || strcmp(list_item(a, 8), "ghi")) + ++errors, printf("Test31: list_sort(): 9th item is '%s' not 'ghi'\n", (char *)list_item(a, 8)); + else if (!list_item(a, 9) || strcmp(list_item(a, 9), "jkl")) + ++errors, printf("Test31: list_sort(): 10th item is '%s' not 'jkl'\n", (char *)list_item(a, 9)); + else if (!list_item(a, 10) || strcmp(list_item(a, 10), "jkl")) + ++errors, printf("Test31: list_sort(): 11th item is '%s' not 'jkl'\n", (char *)list_item(a, 10)); + + if (!(lister = lister_create(a))) + ++errors, printf("Test32: lister_create() failed\n"); + + for (i = 0; lister_has_next(lister); ++i) + { + void *item = lister_next(lister); + + if (item != list_item(a, i)) + { + ++errors, printf("Test33: iteration %d is '%s' not '%s'\n", i, (char *)item, (char *)list_item(a, i)); + break; + } + } + + lister_destroy(lister); + if (lister) + ++errors, printf("Test34: lister_destroy(lister) failed, lister is %p not null\n", lister); + + for (i = 0; list_has_next(a); ++i) + { + void *item = list_next(a); + + if (item != list_item(a, i)) + { + ++errors, printf("Test35: internal iteration %d is '%s' not '%s'\n", i, (char *)item, (char *)list_item(a, i)); + break; + } + } + + if (!(lister = lister_create(a))) + ++errors, printf("Test36: lister_create() failed\n"); + + for (i = 0; lister_has_next(lister); ++i) + { + lister_next(lister); + + if (i == 4) + lister_remove(lister); + } + + if (!list_item(a, 0) || strcmp(list_item(a, 0), "123")) + ++errors, printf("Test37: list_sort(): 1st item is '%s' not '123'\n", (char *)list_item(a, 0)); + else if (!list_item(a, 1) || strcmp(list_item(a, 1), "abc")) + ++errors, printf("Test37: list_sort(): 2nd item is '%s' not 'abc'\n", (char *)list_item(a, 1)); + else if (!list_item(a, 2) || strcmp(list_item(a, 2), "abc")) + ++errors, printf("Test37: list_sort(): 3rd item is '%s' not 'abc'\n", (char *)list_item(a, 2)); + else if (!list_item(a, 3) || strcmp(list_item(a, 3), "abc")) + ++errors, printf("Test37: list_sort(): 4th item is '%s' not 'abc'\n", (char *)list_item(a, 3)); + else if (!list_item(a, 4) || strcmp(list_item(a, 4), "def")) + ++errors, printf("Test37: list_sort(): 5th item is '%s' not 'def'\n", (char *)list_item(a, 4)); + else if (!list_item(a, 5) || strcmp(list_item(a, 5), "def")) + ++errors, printf("Test37: list_sort(): 6th item is '%s' not 'def'\n", (char *)list_item(a, 5)); + else if (!list_item(a, 6) || strcmp(list_item(a, 6), "ghi")) + ++errors, printf("Test37: list_sort(): 7th item is '%s' not 'ghi'\n", (char *)list_item(a, 6)); + else if (!list_item(a, 7) || strcmp(list_item(a, 7), "ghi")) + ++errors, printf("Test37: list_sort(): 8th item is '%s' not 'ghi'\n", (char *)list_item(a, 7)); + else if (!list_item(a, 8) || strcmp(list_item(a, 8), "jkl")) + ++errors, printf("Test37: list_sort(): 9th item is '%s' not 'jkl'\n", (char *)list_item(a, 8)); + else if (!list_item(a, 9) || strcmp(list_item(a, 9), "jkl")) + ++errors, printf("Test37: list_sort(): 10th item is '%s' not 'jkl'\n", (char *)list_item(a, 9)); + + lister_destroy(lister); + if (lister) + ++errors, printf("Test38: lister_destroy(lister) failed, lister is %p not null\n", lister); + + list_destroy(a); + if (a) + ++errors, printf("Test39: list_destroy(a) failed, a is %p not null\n", a); + + list_destroy(b); + if (b) + ++errors, printf("Test40: list_destroy(b) failed, b is %p not null\n", b); + + list_destroy(c); + if (c) + ++errors, printf("Test41: list_destroy(c) failed, c is %p not null\n", c); + + list_destroy(d); + if (d) + ++errors, printf("Test42: list_destroy(d) failed, d is %p not null\n", d); + + a = list_create(NULL); + if (!a) + ++errors, printf("Test43: list_create(NULL) failed\n"); + + if (!list_append_int(a, 2)) + ++errors, printf("Test44: list_append_int(a, 2) failed\n"); + if (list_item_int(a, 0) != 2) + ++errors, printf("Test45: list_item_int(a, 0) failed (%d, not %d)\n", list_item_int(a, 0), 2); + + if (!list_prepend_int(a, 0)) + ++errors, printf("Test46: list_prepend_int(a, 0) failed\n"); + if (list_item_int(a, 0) != 0) + ++errors, printf("Test47: list_item_int(a, 0) failed (%d, not %d)\n", list_item_int(a, 0), 0); + if (list_item_int(a, 1) != 2) + ++errors, printf("Test48: list_item_int(a, 1) failed (%d, not %d)\n", list_item_int(a, 1), 2); + + if (!list_insert_int(a, 1, 1)) + ++errors, printf("Test49: list_insert_int(a, 1, 1) failed\n"); + if (list_item_int(a, 0) != 0) + ++errors, printf("Test50: list_item_int(a, 0) failed (%d, not %d)\n", list_item_int(a, 0), 0); + if (list_item_int(a, 1) != 1) + ++errors, printf("Test51: list_item_int(a, 1) failed (%d, not %d)\n", list_item_int(a, 1), 1); + if (list_item_int(a, 2) != 2) + ++errors, printf("Test52: list_item_int(a, 2) failed (%d, not %d)\n", list_item_int(a, 2), 2); + + for (i = 0; list_has_next(a); ++i) + { + int item = list_next_int(a); + if (item != list_item_int(a, i)) + ++errors, printf("Test53: int list test failed (item %d = %d, not %d)\n", i, item, list_item_int(a, i)); + } + + if (i != 3) + ++errors, printf("Test54: list_has_next() failed (only %d items, not %d)\n", i, 3); + + if (!(lister = lister_create(a))) + ++errors, printf("Test55: lister_create(a) failed\n"); + else + { + for (i = 0; lister_has_next(lister); ++i) + { + int item = lister_next_int(lister); + if (item != list_item_int(a, i)) + ++errors, printf("Test56: int list test failed (item %d = %d, not %d)\n", i, item, i); + } + + if (i != 3) + ++errors, printf("Test57: lister_has_next() failed (only %d items, not %d)\n", i, 3); + } + + if (!list_replace_int(a, 2, 1, 4)) + ++errors, printf("Test58: list_replace_int(a, 2, 1, 4) failed\n"); + if (list_item_int(a, 0) != 0) + ++errors, printf("Test59: list_item_int(a, 0) failed (%d, not %d)\n", list_item_int(a, 0), 0); + if (list_item_int(a, 1) != 1) + ++errors, printf("Test60: list_item_int(a, 1) failed (%d, not %d)\n", list_item_int(a, 1), 1); + if (list_item_int(a, 2) != 4) + ++errors, printf("Test61: list_item_int(a, 2) failed (%d, not %d)\n", list_item_int(a, 2), 4); + + i = 0; + if (!(b = list_map(a, NULL, (list_map_t *)mapf, &i))) + ++errors, printf("Test62: list_map() failed\n"); + else + { + if (list_length(b) != 3) + ++errors, printf("Test63: list_map() failed (length %d, not %d)\n", list_length(b), 3); + else + { + if (list_item_int(b, 0) != 0) + ++errors, printf("Test64: list_map() failed (item %d = %d, not %d)\n", 0, list_item_int(b, 0), 0); + if (list_item_int(b, 1) != 1) + ++errors, printf("Test65: list_map() failed (item %d = %d, not %d)\n", 1, list_item_int(b, 1), 1); + if (list_item_int(b, 2) != 5) + ++errors, printf("Test66: list_map() failed (item %d = %d, not %d)\n", 2, list_item_int(b, 2), 5); + } + + list_destroy(b); + if (b) + ++errors, printf("Test67: list_destroy(b) failed\n"); + } + + if (!(b = list_grep(a, (list_query_t *)grepf, NULL))) + ++errors, printf("Test68: list_grep() failed\n"); + else + { + if (list_length(b) != 2) + ++errors, printf("Test69: list_grep() failed (length %d, not %d)\n", list_length(b), 2); + else + { + if (list_item_int(b, 0) != 0) + ++errors, printf("Test70: list_map() failed (item %d = %d, not %d)\n", 0, list_item_int(b, 0), 0); + if (list_item_int(b, 1) != 4) + ++errors, printf("Test71: list_map() failed (item %d = %d, not %d)\n", 1, list_item_int(b, 1), 4); + } + + list_destroy(b); + if (b) + ++errors, printf("Test72: list_destroy(b) failed\n"); + } + + if (!(a = list_create(NULL))) + ++errors, printf("Test73: a = list_create(NULL) failed\n"); + else + { + int item; + + if (!list_push_int(a, 1)) + ++errors, printf("Test74: list_push_int(a, %d) failed\n", 1); + if (!list_push_int(a, 2)) + ++errors, printf("Test75: list_push_int(a, %d) failed\n", 2); + if (!list_push_int(a, 3)) + ++errors, printf("Test76: list_push_int(a, %d) failed\n", 3); + if (!list_push_int(a, 0)) + ++errors, printf("Test77: list_push_int(a, %d) failed\n", 0); + if (!list_push_int(a, 5)) + ++errors, printf("Test78: list_push_int(a, %d) failed\n", 5); + if (!list_push_int(a, 6)) + ++errors, printf("Test79: list_push_int(a, %d) failed\n", 6); + if (!list_push_int(a, 7)) + ++errors, printf("Test80: list_push_int(a, %d) failed\n", 7); + if ((item = list_pop_int(a)) != 7) + ++errors, printf("Test81: list_pop_int(a) failed (%d, not %d)\n", item, 7); + if ((item = list_pop_int(a)) != 6) + ++errors, printf("Test82: list_pop_int(a) failed (%d, not %d)\n", item, 6); + if ((item = list_pop_int(a)) != 5) + ++errors, printf("Test83: list_pop_int(a) failed (%d, not %d)\n", item, 5); + if ((item = list_pop_int(a)) != 0) + ++errors, printf("Test84: list_pop_int(a) failed (%d, not %d)\n", item, 0); + if ((item = list_pop_int(a)) != 3) + ++errors, printf("Test85: list_pop_int(a) failed (%d, not %d)\n", item, 3); + if ((item = list_pop_int(a)) != 2) + ++errors, printf("Test86: list_pop_int(a) failed (%d, not %d)\n", item, 2); + if ((item = list_pop_int(a)) != 1) + ++errors, printf("Test87: list_pop_int(a) failed (%d, not %d)\n", item, 1); + if ((item = list_pop_int(a)) != 0) + ++errors, printf("Test88: list_pop_int(a) failed (%d, not %d)\n", item, 0); + + if (!list_unshift_int(a, 1)) + ++errors, printf("Test89: list_unshift_int(a, %d) failed\n", 1); + if (!list_unshift_int(a, 2)) + ++errors, printf("Test90: list_unshift_int(a, %d) failed\n", 2); + if (!list_unshift_int(a, 3)) + ++errors, printf("Test91: list_unshift_int(a, %d) failed\n", 3); + if (!list_unshift_int(a, 0)) + ++errors, printf("Test92: list_unshift_int(a, %d) failed\n", 0); + if (!list_unshift_int(a, 5)) + ++errors, printf("Test93: list_unshift_int(a, %d) failed\n", 5); + if (!list_unshift_int(a, 6)) + ++errors, printf("Test94: list_unshift_int(a, %d) failed\n", 6); + if (!list_unshift_int(a, 7)) + ++errors, printf("Test95: list_unshift_int(a, %d) failed\n", 7); + if ((item = list_shift_int(a)) != 7) + ++errors, printf("Test96: list_shift_int(a) failed (%d, not %d)\n", item, 7); + if ((item = list_shift_int(a)) != 6) + ++errors, printf("Test97: list_shift_int(a) failed (%d, not %d)\n", item, 6); + if ((item = list_shift_int(a)) != 5) + ++errors, printf("Test98: list_shift_int(a) failed (%d, not %d)\n", item, 5); + if ((item = list_shift_int(a)) != 0) + ++errors, printf("Test99: list_shift_int(a) failed (%d, not %d)\n", item, 0); + if ((item = list_shift_int(a)) != 3) + ++errors, printf("Test100: list_shift_int(a) failed (%d, not %d)\n", item, 3); + if ((item = list_shift_int(a)) != 2) + ++errors, printf("Test101: list_shift_int(a) failed (%d, not %d)\n", item, 2); + if ((item = list_shift_int(a)) != 1) + ++errors, printf("Test102: list_shift_int(a) failed (%d, not %d)\n", item, 1); + if ((item = list_shift_int(a)) != 0) + ++errors, printf("Test103: list_shift_int(a) failed (%d, not %d)\n", item, 0); + + list_destroy(a); + if (a) + ++errors, printf("Test104: list_destroy(a) failed\n"); + } + + if (!(a = list_create(free))) + ++errors, printf("Test105: a = list_create(free) failed\n"); + else + { + char *item; + + if (!list_push(a, mem_strdup("1"))) + ++errors, printf("Test106: list_push(a, \"%s\") failed\n", "1"); + if (!list_push(a, mem_strdup("2"))) + ++errors, printf("Test107: list_push(a, \"%s\") failed\n", "2"); + if (!list_push(a, mem_strdup("3"))) + ++errors, printf("Test108: list_push(a, \"%s\") failed\n", "3"); + if (!list_push(a, mem_strdup("4"))) + ++errors, printf("Test109: list_push(a, \"%s\") failed\n", "4"); + if (!list_push(a, mem_strdup("5"))) + ++errors, printf("Test110: list_push(a, \"%s\") failed\n", "5"); + if (!list_push(a, mem_strdup("6"))) + ++errors, printf("Test111: list_push(a, \"%s\") failed\n", "6"); + if (!list_push(a, mem_strdup("7"))) + ++errors, printf("Test112: list_push(a, \"%s\") failed\n", "7"); + if (!(item = list_pop(a)) || strcmp(item, "7")) + ++errors, printf("Test113: list_pop(a) failed (\"%s\", not \"%s\")\n", item, "7"); + free(item); + if (!(item = list_pop(a)) || strcmp(item, "6")) + ++errors, printf("Test114: list_pop(a) failed (\"%s\", not \"%s\")\n", item, "6"); + free(item); + if (!(item = list_pop(a)) || strcmp(item, "5")) + ++errors, printf("Test115: list_pop(a) failed (\"%s\", not \"%s\")\n", item, "5"); + free(item); + if (!(item = list_pop(a)) || strcmp(item, "4")) + ++errors, printf("Test116: list_pop(a) failed (\"%s\", not \"%s\")\n", item, "4"); + free(item); + if (!(item = list_pop(a)) || strcmp(item, "3")) + ++errors, printf("Test117: list_pop(a) failed (\"%s\", not \"%s\")\n", item, "3"); + free(item); + if (!(item = list_pop(a)) || strcmp(item, "2")) + ++errors, printf("Test118: list_pop(a) failed (\"%s\", not \"%s\")\n", item, "2"); + free(item); + if (!(item = list_pop(a)) || strcmp(item, "1")) + ++errors, printf("Test119: list_pop(a) failed (\"%s\", not \"%s\")\n", item, "1"); + free(item); + if ((item = list_pop(a))) + ++errors, printf("Test120: list_pop(empty a) failed (%p, not %p)\n", item, NULL); + + if (!list_unshift(a, mem_strdup("1"))) + ++errors, printf("Test121: list_unshift(a, \"%s\") failed\n", "1"); + if (!list_unshift(a, mem_strdup("2"))) + ++errors, printf("Test122: list_unshift(a, \"%s\") failed\n", "2"); + if (!list_unshift(a, mem_strdup("3"))) + ++errors, printf("Test123: list_unshift(a, \"%s\") failed\n", "3"); + if (!list_unshift(a, mem_strdup("4"))) + ++errors, printf("Test124: list_unshift(a, \"%s\") failed\n", "4"); + if (!list_unshift(a, mem_strdup("5"))) + ++errors, printf("Test125: list_unshift(a, \"%s\") failed\n", "5"); + if (!list_unshift(a, mem_strdup("6"))) + ++errors, printf("Test126: list_unshift(a, \"%s\") failed\n", "6"); + if (!list_unshift(a, mem_strdup("7"))) + ++errors, printf("Test127: list_unshift(a, \"%s\") failed\n", "7"); + + if (!(item = list_shift(a)) || strcmp(item, "7")) + ++errors, printf("Test128: list_shift(a) failed (\"%s\", not \"%s\")\n", item, "7"); + free(item); + if (!(item = list_shift(a)) || strcmp(item, "6")) + ++errors, printf("Test129: list_shift(a) failed (\"%s\", not \"%s\")\n", item, "6"); + free(item); + if (!(item = list_shift(a)) || strcmp(item, "5")) + ++errors, printf("Test130: list_shift(a) failed (\"%s\", not \"%s\")\n", item, "5"); + free(item); + if (!(item = list_shift(a)) || strcmp(item, "4")) + ++errors, printf("Test131: list_shift(a) failed (\"%s\", not \"%s\")\n", item, "4"); + free(item); + if (!(item = list_shift(a)) || strcmp(item, "3")) + ++errors, printf("Test132: list_shift(a) failed (\"%s\", not \"%s\")\n", item, "3"); + free(item); + if (!(item = list_shift(a)) || strcmp(item, "2")) + ++errors, printf("Test133: list_shift(a) failed (\"%s\", not \"%s\")\n", item, "2"); + free(item); + if (!(item = list_shift(a)) || strcmp(item, "1")) + ++errors, printf("Test134: list_shift(a) failed (\"%s\", not \"%s\")\n", item, "1"); + free(item); + if ((item = list_shift(a))) + ++errors, printf("Test135: list_shift(empty a) failed (%p, not %p)\n", item, NULL); + + list_destroy(a); + if (a) + ++errors, printf("Test136: list_destroy(a) failed\n"); + } + + if (!(a = list_make(NULL, "a", "b", "c", "d", "e", "f", NULL))) + ++errors, printf("Test137: a = list_make(NULL, \"a\", \"b\", \"c\", \"d\", \"e\", \"f\") failed\n"); + else + { + List *splice; + + if (!(splice = list_splice(a, 0, 1, NULL))) + ++errors, printf("Test138: list_splice(a, 0, 1) failed\n"); + else + { + if (list_length(splice) != 1) + ++errors, printf("Test139: list_splice(a, 0, 1) failed (splice length is %d, not %d)\n", list_length(splice), 1); + if (strcmp(list_item(splice, 0), "a")) + ++errors, printf("Test140: list_splice(a, 0, 1) failed (splice item %d is \"%s\", not \"%s\")\n", 0, (char *)list_item(splice, 0), "a"); + + if (strcmp(list_item(a, 0), "b")) + ++errors, printf("Test141: list_splice(a, 0, 1) failed (item %d is \"%s\", not \"%s\")\n", 0, (char *)list_item(a, 0), "b"); + if (strcmp(list_item(a, 1), "c")) + ++errors, printf("Test142: list_splice(a, 0, 1) failed (item %d is \"%s\", not \"%s\")\n", 1, (char *)list_item(a, 1), "c"); + if (strcmp(list_item(a, 2), "d")) + ++errors, printf("Test143: list_splice(a, 0, 1) failed (item %d is \"%s\", not \"%s\")\n", 2, (char *)list_item(a, 2), "d"); + if (strcmp(list_item(a, 3), "e")) + ++errors, printf("Test144: list_splice(a, 0, 1) failed (item %d is \"%s\", not \"%s\")\n", 3, (char *)list_item(a, 3), "e"); + if (strcmp(list_item(a, 4), "f")) + ++errors, printf("Test145: list_splice(a, 0, 1) failed (item %d is \"%s\", not \"%s\")\n", 4, (char *)list_item(a, 4), "f"); + + list_destroy(splice); + if (splice) + ++errors, printf("Test146: list_destroy(splice) failed\n"); + } + + if (!(splice = list_splice(a, 4, 1, NULL))) + ++errors, printf("Test147: list_splice(a, 4, 1) failed\n"); + else + { + if (list_length(splice) != 1) + ++errors, printf("Test148: list_splice(a, 4, 1) failed (splice length is %d, not %d)\n", list_length(splice), 1); + if (strcmp(list_item(splice, 0), "f")) + ++errors, printf("Test149: list_splice(a, 4, 1) failed (splice item %d is \"%s\", not \"%s\")\n", 0, (char *)list_item(splice, 0), "f"); + + if (strcmp(list_item(a, 0), "b")) + ++errors, printf("Test150: list_splice(a, 4, 1) failed (item %d is \"%s\", not \"%s\")\n", 0, (char *)list_item(a, 0), "b"); + if (strcmp(list_item(a, 1), "c")) + ++errors, printf("Test151: list_splice(a, 4, 1) failed (item %d is \"%s\", not \"%s\")\n", 1, (char *)list_item(a, 1), "c"); + if (strcmp(list_item(a, 2), "d")) + ++errors, printf("Test152: list_splice(a, 4, 1) failed (item %d is \"%s\", not \"%s\")\n", 2, (char *)list_item(a, 2), "d"); + if (strcmp(list_item(a, 3), "e")) + ++errors, printf("Test153: list_splice(a, 4, 1) failed (item %d is \"%s\", not \"%s\")\n", 3, (char *)list_item(a, 3), "e"); + + list_destroy(splice); + if (splice) + ++errors, printf("Test154: list_destroy(splice) failed\n"); + } + + if (!(splice = list_splice(a, 1, 2, NULL))) + ++errors, printf("Test155: list_splice(a, 1, 2) failed\n"); + else + { + if (list_length(splice) != 2) + ++errors, printf("Test156: list_splice(a, 1, 2) failed (splice length is %d, not %d)\n", list_length(splice), 2); + if (strcmp(list_item(splice, 0), "c")) + ++errors, printf("Test157: list_splice(a, 1, 2) failed (splice item %d is \"%s\", not \"%s\")\n", 0, (char *)list_item(splice, 0), "c"); + if (strcmp(list_item(splice, 1), "d")) + ++errors, printf("Test158: list_splice(a, 1, 2) failed (splice item %d is \"%s\", not \"%s\")\n", 1, (char *)list_item(splice, 1), "d"); + + if (strcmp(list_item(a, 0), "b")) + ++errors, printf("Test159: list_splice(a, 1, 2) failed (item %d is \"%s\", not \"%s\")\n", 0, (char *)list_item(a, 0), "b"); + if (strcmp(list_item(a, 1), "e")) + ++errors, printf("Test160: list_splice(a, 1, 2) failed (item %d is \"%s\", not \"%s\")\n", 1, (char *)list_item(a, 1), "e"); + + list_destroy(splice); + if (splice) + ++errors, printf("Test161: list_destroy(splice) failed\n"); + } + + list_destroy(a); + if (a) + ++errors, printf("Test162: list_destroy(a) failed\n"); + } + + if (errors) + printf("%d/162 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/list.h b/list.h new file mode 100644 index 0000000..fc51fb3 --- /dev/null +++ b/list.h @@ -0,0 +1,105 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_LIST_H +#define LIBSLACK_LIST_H + +#include + +#include + +#include + +typedef struct List List; +typedef struct Lister Lister; +typedef void list_destroy_t(void *item); +typedef void *list_copy_t(const void *item); +typedef int list_cmp_t(const void *a, const void *b); +typedef void list_action_t(void *item, size_t *index, void *data); +typedef void *list_map_t(void *item, size_t *index, void *data); +typedef int list_query_t(void *item, size_t *index, void *data); + +#undef list_destroy +#undef lister_destroy + +__START_DECLS +List *list_create __PROTO ((list_destroy_t *destroy)); +List *list_make __PROTO ((list_destroy_t *destroy, ...)); +List *list_vmake __PROTO ((list_destroy_t *destroy, va_list args)); +List *list_copy __PROTO ((const List *src, list_copy_t *copy)); +void list_release __PROTO ((List *list)); +#define list_destroy(list) list_destroy_func(&(list)) +void *list_destroy_func __PROTO ((List **list)); +int list_own __PROTO ((List *list, list_destroy_t *destroy)); +list_destroy_t *list_disown __PROTO ((List *list)); +void *list_item __PROTO ((const List *list, size_t index)); +int list_item_int __PROTO ((const List *list, size_t index)); +int list_empty __PROTO ((const List *list)); +size_t list_length __PROTO ((const List *list)); +ssize_t list_last __PROTO ((const List *list)); +List *list_remove __PROTO ((List *list, size_t index)); +List *list_remove_range __PROTO ((List *list, size_t index, size_t range)); +List *list_insert __PROTO ((List *list, size_t index, void *item)); +List *list_insert_int __PROTO ((List *list, size_t index, int item)); +List *list_insert_list __PROTO ((List *list, size_t index, const List *src, list_copy_t *copy)); +List *list_append __PROTO ((List *list, void *item)); +List *list_append_int __PROTO ((List *list, int item)); +List *list_append_list __PROTO ((List *list, const List *src, list_copy_t *copy)); +List *list_prepend __PROTO ((List *list, void *item)); +List *list_prepend_int __PROTO ((List *list, int item)); +List *list_prepend_list __PROTO ((List *list, const List *src, list_copy_t *copy)); +List *list_replace __PROTO ((List *list, size_t index, size_t range, void *item)); +List *list_replace_int __PROTO ((List *list, size_t index, size_t range, int item)); +List *list_replace_list __PROTO ((List *list, size_t index, size_t range, const List *src, list_copy_t *copy)); +List *list_extract __PROTO ((const List *list, size_t index, size_t range, list_copy_t *copy)); +List *list_push __PROTO ((List *list, void *item)); +List *list_push_int __PROTO ((List *list, int item)); +void *list_pop __PROTO ((List *list)); +int list_pop_int __PROTO ((List *list)); +void *list_shift __PROTO ((List *list)); +int list_shift_int __PROTO ((List *list)); +List *list_unshift __PROTO ((List *list, void *item)); +List *list_unshift_int __PROTO ((List *list, int item)); +List *list_splice __PROTO ((List *list, size_t index, size_t range, list_copy_t *copy)); +List *list_sort __PROTO ((List *list, list_cmp_t *cmp)); +void list_apply __PROTO ((List *list, list_action_t *action, void *data)); +List *list_map __PROTO ((List *list, list_destroy_t *destroy, list_map_t *map, void *data)); +List *list_grep __PROTO ((List *list, list_query_t *grep, void *data)); +ssize_t list_ask __PROTO ((List *list, ssize_t *index, list_query_t *query, void *data)); +Lister *lister_create __PROTO ((List *list)); +void lister_release __PROTO ((Lister * lister)); +#define lister_destroy(lister) lister_destroy_func(&(lister)) +void *lister_destroy_func __PROTO ((Lister **lister)); +int lister_has_next __PROTO ((Lister *lister)); +void *lister_next __PROTO ((Lister *lister)); +int lister_next_int __PROTO ((Lister *lister)); +void lister_remove __PROTO ((Lister *lister)); +int list_has_next __PROTO ((List *list)); +void *list_next __PROTO ((List *list)); +int list_next_int __PROTO ((List *list)); +void list_remove_current __PROTO ((List *list)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/log.c b/log.c new file mode 100644 index 0000000..c984b16 --- /dev/null +++ b/log.c @@ -0,0 +1,395 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - syslog helper module + +=head1 SYNOPSIS + + #include + + int syslog_lookup_facility(const char *facility); + int syslog_lookup_priority(const char *priority); + const char *syslog_facility_str(int spec); + const char *syslog_priority_str(int spec); + int syslog_parse(const char *spec, int *facility, int *priority); + +=head1 DESCRIPTION + +This module provides functions for parsing I targets, converting +between I facility names and codes, and converting between I +priority names and codes. + + syslog facilities syslog priorities + ---------------------- ----------------------- + kern LOG_KERN emerg LOG_EMERG + user LOG_USER alert LOG_ALERT + mail LOG_MAIL crit LOG_CRIT + daemon LOG_DAEMON err LOG_ERR + auth LOG_AUTH warning LOG_WARNIN + syslog LOG_SYSLOG info LOG_INFO + lpr LOG_LPR debug LOG_DEBUG + news LOG_NEWS + uucp LOG_UUCP + cron LOG_CRON + local0 LOG_LOCAL0 + local1 LOG_LOCAL1 + local2 LOG_LOCAL2 + local3 LOG_LOCAL3 + local4 LOG_LOCAL4 + local5 LOG_LOCAL5 + local6 LOG_LOCAL6 + local7 LOG_LOCAL7 + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include + +typedef struct syslog_map_t syslog_map_t; + +struct syslog_map_t +{ + char *name; + int val; +}; + +/* +** The following masks may be wrong on some systems. +*/ + +#ifndef LOG_PRIMASK +#define LOG_PRIMASK 0x0007 +#endif + +#ifndef LOG_FACMASK +#define LOG_FACMASK 0x03f8 +#endif + +static syslog_map_t syslog_facility_map[] = +{ + { "kern", LOG_KERN }, + { "user", LOG_USER }, + { "mail", LOG_MAIL }, + { "daemon", LOG_DAEMON }, + { "auth", LOG_AUTH }, + { "syslog", LOG_SYSLOG }, + { "lpr", LOG_LPR }, + { "news", LOG_NEWS }, + { "uucp", LOG_UUCP }, + { "cron", LOG_CRON }, + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 }, + { NULL, -1 } +}; + +static syslog_map_t syslog_priority_map[] = +{ + { "emerg", LOG_EMERG }, + { "alert", LOG_ALERT }, + { "crit", LOG_CRIT }, + { "err", LOG_ERR }, + { "warning", LOG_WARNING }, + { "info", LOG_INFO }, + { "debug", LOG_DEBUG }, + { NULL, -1 } +}; + +/* + +C + +Looks for C (a facility or priority name) in C. If found, returns +its corresponding code. If not found, returns -1. + +*/ + +static int syslog_lookup(const syslog_map_t *map, const char *name) +{ + int i; + + for (i = 0; map[i].name; ++i) + if (!strcmp(name, map[i].name)) + break; + + return map[i].val; +} + +/* + +C + +Looks for C (a facility or priority code) in C. If found, returns +its corresponding name. If not found, returns C. + +*/ + +static const char *syslog_lookup_str(const syslog_map_t *map, int spec, int mask) +{ + int i; + + for (i = 0; map[i].name; ++i) + if ((spec & mask) == map[i].val) + break; + + return map[i].name; +} + +/* + +=item C + +Returns the code corresponding to C. + +=cut + +*/ + +int syslog_lookup_facility(const char *facility) +{ + return syslog_lookup(syslog_facility_map, facility); +} + +/* + +=item C + +Returns the code corresponding to C. + +=cut + +*/ + +int syslog_lookup_priority(const char *priority) +{ + return syslog_lookup(syslog_priority_map, priority); +} + +/* + +=item C + +Returns the name corresponding to the facility part of C. + +=cut + +*/ + +const char *syslog_facility_str(int spec) +{ + return syslog_lookup_str(syslog_facility_map, spec, LOG_FACMASK); +} + +/* + +=item C + +Returns the name corresponding to the priority part of C. + +=cut + +*/ + +const char *syslog_priority_str(int spec) +{ + return syslog_lookup_str(syslog_priority_map, spec, LOG_PRIMASK); +} + +/* + +=item C + +Parses C as a I string. If C is +non-C, the parsed facility is stored in the location pointed to by +C. If C is non-C the parsed priority is stored in +the location pointed to by C. On success, returns 0. On error, +returns -1. + +=cut + +*/ + +int syslog_parse(const char *spec, int *facility, int *priority) +{ + char fac[256], *pri; + int f, p; + + if (!spec) + return -1; + + strncpy(fac, spec, 255); + fac[255] = '\0'; + + if (!(pri = strchr(fac, '.'))) + return -1; + + *pri++ = '\0'; + + if ((f = syslog_lookup_facility(fac)) == -1) + return -1; + + if ((p = syslog_lookup_priority(pri)) == -1) + return -1; + + if (facility) + *facility = f; + + if (priority) + *priority = p; + + return 0; +} + +/* + +=back + +=head1 EXAMPLE + + #include + #include + + int main(int ac, char **av) + { + int facility, priority; + if (syslog_parse(av[1], &facility, &priority) != -1) + syslog(facility | priority, "syslog(%s)", av[1]); + return 0; + } + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +#ifdef NEEDS_SNPRINTF +#include "snprintf.h" +#endif + +int main(int ac, char **av) +{ + int i; + int j; + int errors = 0; + int tests = 0; + int rc; + + printf("Testing: log\n"); + + for (i = 0; syslog_facility_map[i].name; ++i) + { + for (j = 0; syslog_priority_map[j].name; ++j) + { + char buf[64]; + int facility = 0; + int priority = 0; + + snprintf(buf, 64, "%s.%s", syslog_facility_map[i].name, syslog_priority_map[j].name); + ++tests; + + rc = syslog_parse(buf, &facility, &priority); + if (rc == -1) + ++errors, printf("Test%d: syslog_parse(%s) failed\n", tests, buf); + else if (facility != syslog_facility_map[i].val) + ++errors, printf("Test%d: syslog_parse(%s) failed: facility = %d (not %d)\n", tests, buf, facility, syslog_facility_map[i].val); + else if (priority != syslog_priority_map[j].val) + ++errors, printf("Test%d: syslog_parse(%s) failed: priority = %d (not %d)\n", tests, buf, priority, syslog_priority_map[j].val); + } + } + + for (i = 0; syslog_facility_map[i].name; ++i) + { + const char *fac = syslog_facility_str(syslog_facility_map[i].val); + + ++tests; + if (fac != syslog_facility_map[i].name) + ++errors, printf("Test%d: syslog_facility_str(%d) failed: %s (not %s)\n", tests, syslog_facility_map[i].val, fac, syslog_facility_map[i].name); + } + + for (i = 0; syslog_priority_map[i].name; ++i) + { + const char *pri = syslog_priority_str(syslog_priority_map[i].val); + + ++tests; + if (pri != syslog_priority_map[i].name) + ++errors, printf("Test%d: syslog_priority_str(%d) failed: %s (not %s)\n", tests, syslog_priority_map[i].val, pri, syslog_priority_map[i].name); + } + + ++tests; + if (syslog_parse(NULL, NULL, NULL) != -1) + ++errors, printf("Test%d: syslog_parse(null) failed\n", tests); + + ++tests; + if (syslog_parse("gibberish", NULL, NULL) != -1) + ++errors, printf("Test%d: syslog_parse(\"gibberish\") failed\n", tests); + + if (errors) + printf("%d/%d tests failed\n", errors, tests); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/log.h b/log.h new file mode 100644 index 0000000..2dc9922 --- /dev/null +++ b/log.h @@ -0,0 +1,40 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +* +*/ + +#ifndef LIBSLACK_LOG_H +#define LIBSLACK_LOG_H + +#include + +__START_DECLS +int syslog_lookup_facility __PROTO ((const char *facility)); +int syslog_lookup_priority __PROTO ((const char *priority)); +const char *syslog_facility_str __PROTO ((int spec)); +const char *syslog_priority_str __PROTO ((int spec)); +int syslog_parse __PROTO ((const char *spec, int *facility, int *priority)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/macros.mk b/macros.mk new file mode 100644 index 0000000..7bfb45b --- /dev/null +++ b/macros.mk @@ -0,0 +1,131 @@ +# +# libslack - http://libslack.org/ +# +# Copyright (C) 1999, 2000 raf +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# or visit http://www.gnu.org/copyleft/gpl.html +# +# 20000902 raf + +# Uncomment these to override the defines in daemon.h and prog.h +# +# SLACK_DEFINES += -DPATH_SEP=\'/\' +# SLACK_DEFINES += -DROOT_DIR=\"/\" +# SLACK_DEFINES += -DETC_DIR=\"/etc\" +# SLACK_DEFINES += -DPID_DIR=\"/var/run\" + +# Uncomment this if your system doesn't have snprintf() +# +# SNPRINTF := snprintf +# SLACK_DEFINES += -DNEEDS_SNPRINTF=1 + +# Uncomment this if your system doesn't have GNU getopt_long() +# +# GETOPT := getopt +# SLACK_DEFINES += -DNEEDS_GETOPT=1 + +# Uncomment this if your system doesn't have vsscanf() +# +# VSSCANF := vsscanf +# SLACK_DEFINES += -DNEEDS_VSSCANF=1 + +# Uncomment this if your system uses SOCKS +# +# SLACK_DEFINES += -DSOCKS=1 + +# Uncomment this if your system doesn't have POSIX 1003.2 compliant regular +# expression functions and you don't want to download them (see README). +# +# SLACK_DEFINES += -DREGEX_MISSING=1 + +# Uncomment one of these on SVR4 if required (see next) +# +# SLACK_DEFINES += -DSVR4 +# SLACK_DEFINES += -USVR4 + +# Uncomment this to prevent an extra fork on SVR4 which prevents +# the process from ever gaining a controlling terminal. If this is +# uncommented, the O_NOCTTY flag should be passed to all calls to +# open(2) made by the process (as is required on BSD anyway) +# +# SLACK_DEFINES += -DNO_EXTRA_SVR4_FORK=1 + +# Uncomment this to override the default value of 8192 bytes +# as the size for message buffers +# +# SLACK_DEFINES += -DMSG_SIZE=8192 + +# Uncomment this to override the default value of 32 as the maximum +# number of dimensions of an allocated space +# +# SLACK_DEFINES += -DMEM_MAX_DIM=32 + +# Uncomment this if your system has the "long long int" type (64 bit) +# and you want to include the long format ("l") in pack() and unpack(). +# +SLACK_DEFINES += -DHAS_LONG_LONG -Wno-long-long + +SLACK_NAME := slack +SLACK_VERSION := 0.3 +SLACK_ID := lib$(SLACK_NAME)-$(SLACK_VERSION) +SLACK_DIST := $(SLACK_ID).tar.gz +SLACK_HAS_SUBTARGETS := 0 + +SLACK_TARGET := $(SLACK_SRCDIR)/lib$(SLACK_NAME).a +SLACK_INSTALL := $(SLACK_ID).a +SLACK_INSTALL_LINK := lib$(SLACK_NAME).a +SLACK_MODULES := conf daemon err fifo $(GETOPT) hsort lim list log map mem msg net opt prog prop sig $(SNPRINTF) str $(VSSCANF) +SLACK_HEADERS := lib hdr socks + +SLACK_CFILES := $(patsubst %, $(SLACK_SRCDIR)/%.c, $(SLACK_MODULES)) +SLACK_OFILES := $(patsubst %, $(SLACK_SRCDIR)/%.o, $(SLACK_MODULES)) +SLACK_HFILES := $(patsubst %, $(SLACK_SRCDIR)/%.h, $(SLACK_MODULES) $(SLACK_HEADERS)) +SLACK_PODFILES := $(SLACK_CFILES) +SLACK_MANFILES := $(patsubst %.c, %.$(LIB_MANSECT), $(SLACK_PODFILES)) +SLACK_HTMLFILES := $(patsubst %.c, %.$(LIB_MANSECT).html, $(SLACK_PODFILES)) + +SLACK_TESTDIR := $(SLACK_SRCDIR)/test +SLACK_TESTS := $(patsubst %, $(SLACK_TESTDIR)/%.test, $(SLACK_MODULES)) + +SLACK_INCLINK := $(SLACK_SRCDIR)/$(SLACK_NAME) + +TAG_FILES += $(SLACK_HFILES) $(SLACK_CFILES) +DEPEND_HFILES += $(SLACK_HFILES) +DEPEND_CFILES += $(SLACK_CFILES) + +ALL_TARGETS += slack +READY_TARGETS += ready-slack +TEST_TARGETS += test-slack +MAN_TARGETS += man-slack +HTML_TARGETS += html-slack +INSTALL_TARGETS += install-slack +UNINSTALL_TARGETS += uninstall-slack + +CLEAN_FILES += $(SLACK_OFILES) $(SLACK_MANFILES) $(SLACK_HTMLFILES) $(SLACK_SRCDIR)/pod2html-* +CLOBBER_FILES += $(SLACK_TARGET) $(SLACK_TESTDIR) $(SLACK_SRCDIR)/tags + +SLACK_DEFINES += +SLACK_CPPFLAGS += $(SLACK_DEFINES) $(patsubst %, -I%, $(SLACK_INCDIRS)) +SLACK_CCFLAGS += $(CCFLAGS) +SLACK_CFLAGS += $(SLACK_CPPFLAGS) $(SLACK_CCFLAGS) +SLACK_LIBS += $(SLACK_NAME) + +# Uncomment this on Solaris for the daemon and net modules +# +# SLACK_LIBS += xnet + +SLACK_LDFLAGS += $(patsubst %, -L%, $(SLACK_LIBDIRS)) $(patsubst %, -l%, $(SLACK_LIBS)) + diff --git a/map.c b/map.c new file mode 100644 index 0000000..cbc7371 --- /dev/null +++ b/map.c @@ -0,0 +1,2236 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - map module + +=head1 SYNOPSIS + + #include + + typedef struct Map Map; + typedef struct Mapper Mapper; + typedef struct Mapping Mapping; + typedef list_destroy_t map_destroy_t; + typedef list_copy_t map_copy_t; + typedef list_cmp_t map_cmp_t; + typedef size_t map_hash_t(size_t table_size, const void *key); + typedef void map_action_t(void *key, void *item, void *data); + + Map *map_create(map_destroy_t *destroy); + Map *map_create_sized(size_t size, map_destroy_t *destroy); + Map *map_create_with_hash(map_hash_t *hash, map_destroy_t *destroy); + Map *map_create_with_hash_sized(size_t size, map_hash_t *hash, map_destroy_t *destroy); + Map *map_create_generic(map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_destroy_t *key_destroy, map_destroy_t *value_destroy); + Map *map_create_generic_sized(size_t size, map_copy *copy, map_cmp_t *cmp, map_hash_t *hash, map_destroy_t *key_destroy, map_destroy_t *value_destroy); + void map_release(Map *map); + #define map_destroy(map) + void *map_destroy_func(Map **map); + int map_own(Map *map, map_destroy_t *destroy); + map_destroy_t *map_disown(Map *map); + int map_add(Map *map, const void *key, void *value); + int map_put(Map *map, const void *key, void *value); + int map_insert(Map *map, const void *key, void *value, int replace); + int map_remove(Map *map, const void *key); + void *map_get(const Map *map, const void *key); + Mapper *mapper_create(Map *map); + void mapper_release(Mapper *mapper); + #define mapper_destroy(mapper) + void *mapper_destroy_func(Mapper **mapper); + int mapper_has_next(Mapper *mapper); + void *mapper_next(Mapper *mapper); + const Mapping *mapper_next_mapping(Mapper *mapper); + void mapper_remove(Mapper *mapper); + int map_has_next(Map *map); + void *map_next(Map *map); + const Mapping *map_next_mapping(Map *map); + void map_remove_current(Map *map); + const void *mapping_key(const Mapping *mapping); + const void *mapping_value(const Mapping *mapping); + List *map_keys(Map *map); + List *map_values(Map *map); + void map_apply(Map *, map_action_t *action, void *data); + ssize_t map_size(const Map *map); + +=head1 DESCRIPTION + +This module provides functions for manipulating and iterating over a set of +mappings from strings to homogeneous data (or heterogeneous data if it's +polymorphic), also known as associative arrays. I may own their items. +I created with a non-C destroy function use that function to +destroy an item when it is removed from the map and to destroy each item +when the map itself it destroyed. I are hash tables with 11 buckets +by default. They grow when necessary, approximately doubling in size each +time up to a maximum size of 26,214,401 buckets. + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include "map.h" +#include "mem.h" +#include "err.h" + +struct Map +{ + size_t size; /* number of buckets */ + size_t items; /* number of items */ + List **chain; /* array of hash buckets */ + map_hash_t *hash; /* hash function */ + map_copy_t *copy; /* key copy function */ + map_cmp_t *cmp; /* key comparison function */ + map_destroy_t *key_destroy; /* destructor function for keys */ + map_destroy_t *value_destroy; /* destructor function for items */ + Mapper *mapper; /* built-in iterator */ +}; + +struct Mapping +{ + void *key; /* a map key */ + void *value; /* a map value */ + map_destroy_t *key_destroy; /* destructor function for key */ + map_destroy_t *value_destroy; /* destructor function for value */ +}; + +struct Mapper +{ + Map *map; /* the map being iterated over */ + ssize_t chain_index; /* the index of the chain of the current item */ + ssize_t item_index; /* the index of the current item */ + ssize_t next_chain_index; /* the index of the chain of the next item */ + ssize_t next_item_index; /* the index of the next item */ +}; + +/* Increasing sequence of valid (i.e. prime) table sizes to choose from. */ + +static const size_t table_sizes[] = +{ + 11, 23, 47, 101, 199, 401, 797, 1601, 3203, 6397, 12799, 25601, + 51199, 102397, 204803, 409597, 819187, 1638431, 3276799, 6553621, + 13107197, 26214401 +}; + +static const size_t num_table_sizes = sizeof(table_sizes) / sizeof(table_sizes[0]); + +/* Average bucket length threshold that must be reached before a map grows */ + +static const double table_resize_factor = 2.0; + +#if 0 +/* + +C + +JPW hash function. Returns a hash value (in the range 0..size-1) for C. + +*/ + +static size_t hash(size_t size, const void *key) +{ + unsigned char *k = key; + size_t g, h = 0; + + while (*k) + if ((g = (h <<= 4, h += *k++) & 0xf0000000)) + h ^= (g >> 24) ^ g; + + return h % size; +} +#endif + +/* + +C + +Hash function from The Practice of Programming by Kernighan and Pike (p57). +Returns a hash value (in the range 0..size-1) for C. + +*/ + +static size_t hash(size_t size, const void *key) +{ + const unsigned char *k = key; + size_t h = 0; + const int multiplier = 31; + /* const int multiplier = 37; */ + + while (*k) + h *= multiplier, h += *k++; + + return h % size; +} + +/* + +C + +Creates a new mapping from C to C. C is the destructor +function for C. On success, returns the new mapping. On error, returns +C. + +*/ + +static Mapping *mapping_create(void *key, void *value, map_destroy_t *key_destroy, map_destroy_t *value_destroy) +{ + Mapping *mapping; + + if (!(mapping = mem_new(Mapping))) + return NULL; + + mapping->key = key; + mapping->value = value; + mapping->key_destroy = key_destroy; + mapping->value_destroy = value_destroy; + + return mapping; +} + +/* + +C + +Releases (deallocates) C, destroying its value if necessary. + +*/ + +static void mapping_release(Mapping *mapping) +{ + if (!mapping) + return; + + if (mapping->key_destroy) + mapping->key_destroy(mapping->key); + + if (mapping->value_destroy) + mapping->value_destroy(mapping->value); + + mem_release(mapping); +} + +/* + +=item C + +Creates a I with strings keys, 11 buckets and C as its item +destructor. On success, returns the new map. On error, returns C. + +=cut + +*/ + +Map *map_create(map_destroy_t *destroy) +{ + return map_create_with_hash_sized(table_sizes[0], (map_hash_t *)hash, destroy); +} + +/* + +=item C + +Creates a I with string keys, approximately C buckets and +C as its item destructor. The actual size will be the first prime +greater than or equal to C in a prebuilt sequence of primes between 11 +and 26,214,401 that double at each step. On success, returns the new map. On +error, returns C. + +=cut + +*/ + +Map *map_create_sized(size_t size, map_destroy_t *destroy) +{ + return map_create_with_hash_sized(size, (map_hash_t *)hash, destroy); +} + +/* + +=item C + +Creates a I with strings keys, 11 buckets, C as the hash function +and C as its item destructor. On success, returns the new map. On +error, returns C. The arguments to C are a C specifying +the number of buckets and a C specifying the key to hash. It +returns a C between zero and the table size - 1. + +=cut + +*/ + +Map *map_create_with_hash(map_hash_t *hash, map_destroy_t *destroy) +{ + return map_create_with_hash_sized(table_sizes[0], hash, destroy); +} + +/* + +=item C + +Creates a I with string keys, approximately C buckets, C as +its hash function and C as its item destructor. The actual size +will be the first prime greater than or equal to C in a built in +sequence of primes between 11 and 26,214,401 that double at each step. On +success, returns the new map. On error, returns C. The arguments to +C are a C specifying the number of buckets and a C specifying the key to hash. It must return a C between zero and +the table size - 1. + +=cut + +*/ + +Map *map_create_with_hash_sized(size_t size, map_hash_t *hash, map_destroy_t *destroy) +{ + return map_create_generic_sized(size, (map_copy_t *)mem_strdup, (map_cmp_t *)strcmp, hash, (map_destroy_t *)free, destroy); +} + +/* + +=item C + +Creates a I with arbitrary keys, 11 buckets, C as its key copy +function, C as its key comparison function, C as its hash +function, C as its key destructor and C as its +item destructor. On success, returns the new map. On error, returns C. +The argument to C is the key to be copied. It returns the copy. The +arguments to C are two keys to be compared. It returns < 0 if the first +compares less than the second, 0 if they compare equal and > 0 if the first +compares greater than the second. The arguments to C are a C +specifying the number of buckets and a C specifying the key to +hash. It must return a C between zero and the table size - 1. + +=cut + +*/ + +Map *map_create_generic(map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_destroy_t *key_destroy, map_destroy_t *value_destroy) +{ + return map_create_generic_sized(table_sizes[0], copy, cmp, hash, key_destroy, value_destroy); +} + +/* + +=item C + +Creates a I with arbitrary keys, approximately C buckets, C +as its key copy function, C as its key comparison function, C as +its hash function, C as its key destrouctor and +C as its item destructor. The actual size will be the first +prime greater than or equal to C in a built in sequence of primes +between 11 and 26,214,401 that double at each step. On success, returns the +new map. On error, returns C. The argument to C is the key to be +copied. It returns the copy. The arguments to C are two keys to be +compared. It returns < 0 if the first compares less than the second, 0 if +they compare equal and > 0 if the first compares greater than the second. +The arguments to C are a C specifying the number of buckets +and a C specifying the key to hash. It must return a C +between zero and the table size - 1. + +=cut + +*/ + +Map *map_create_generic_sized(size_t size, map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_destroy_t *key_destroy, map_destroy_t *value_destroy) +{ + Map *map; + size_t i; + + for (i = 0; i < num_table_sizes; ++i) + { + if (table_sizes[i] >= size) + { + size = table_sizes[i]; + break; + } + } + + if (i == num_table_sizes) + return NULL; + + if (!(map = mem_new(Map))) + return NULL; + + if (!(map->chain = mem_create(size, List *))) + { + mem_release(map); + return NULL; + } + + map->size = size; + map->items = 0; + memset(map->chain, '\0', map->size * sizeof(List *)); + map->hash = hash; + map->copy = copy; + map->cmp = cmp; + map->key_destroy = key_destroy; + map->value_destroy = value_destroy; + map->mapper = NULL; + + return map; +} + +/* + +=item C + +Releases (deallocates) C, destroying its items if necessary. + +=cut + +*/ + +void map_release(Map *map) +{ + size_t i; + + if (!map) + return; + + for (i = 0; i < map->size; ++i) + list_release(map->chain[i]); + + mem_release(map->chain); + mapper_release(map->mapper); + mem_release(map); +} + +/* + +=item C< #define map_destroy(map)> + +Destroys (deallocates and sets to C) C. Returns C. + +=item C + +Destroys (deallocates and sets to C) C. Returns C. This +function is exposed as an implementation side effect. Don't call it +directly. Call I instead. + +=cut + +*/ + +void *map_destroy_func(Map **map) +{ + if (map && *map) + { + map_release(*map); + *map = NULL; + } + + return NULL; +} + +/* + +=item C + +Causes C to take ownership of its items. The keys will be destroyed +using C. The items will be destroyed using C +when their mappings are removed from C or when C is destroyed. On +success, returns 0. On error, returns -1. + +=cut + +*/ + +int map_own(Map *map, map_destroy_t *destroy) +{ + size_t c, i, length; + + if (!map || !destroy) + return -1; + + if (destroy == map->value_destroy) + return 0; + + map->value_destroy = destroy; + + for (c = 0; c < map->size; ++c) + { + List *chain = map->chain[c]; + + if (!chain) + continue; + + length = list_length(chain); + + for (i = 0; i < length; ++i) + { + Mapping *mapping = (Mapping *)list_item(chain, i); + mapping->value_destroy = destroy; + } + } + + return 0; +} + +/* + +=item C + +Causes C to relinquish ownership of its items. The items will not be +destroyed when their mappings are removed from C or when C is +destroyed. On success, returns the previous destroy function, if any. +On error, returns C. + +=cut + +*/ + +map_destroy_t *map_disown(Map *map) +{ + size_t c, i, length; + map_destroy_t *destroy; + + if (!map || !map->value_destroy) + return NULL; + + destroy = map->value_destroy; + map->value_destroy = NULL; + + for (c = 0; c < map->size; ++c) + { + List *chain = map->chain[c]; + + if (!chain) + continue; + + length = list_length(chain); + + for (i = 0; i < length; ++i) + { + Mapping *mapping = (Mapping *)list_item(chain, i); + mapping->value_destroy = NULL; + } + } + + return destroy; +} + +/* + +C + +Resizes C to use the next prime in a built in sequence of primes between +11 and 26,214,401 that is greater than the current size. + +*/ + +static int map_resize(Map *map) +{ + size_t size = 0; + size_t i; + Mapper *mapper; + Map *new_map; + + if (!map) + return -1; + + for (i = 1; i < num_table_sizes; ++i) + if (table_sizes[i] > map->size) + { + size = table_sizes[i]; + break; + } + + if (i == num_table_sizes || size == 0) + return -1; + + if (!(mapper = mapper_create(map))) + return -1; + + if (!(new_map = map_create_generic_sized(size, map->copy, map->cmp, map->hash, map->key_destroy, NULL))) + { + mapper_release(mapper); + return -1; + } + + while (mapper_has_next(mapper)) + { + const Mapping *mapping = mapper_next_mapping(mapper); + + if (map_add(new_map, mapping->key, mapping->value) == -1) + { + mapper_destroy(mapper); + map_destroy(new_map); + return -1; + } + } + + mapper_destroy(mapper); + if (map->value_destroy) + map_own(new_map, map_disown(map)); + + for (i = 0; i < map->size; ++i) + list_release(map->chain[i]); + mem_release(map->chain); + + *map = *new_map; + mem_release(new_map); + + return 0; +} + +/* + +=item C + +Adds the C<(key, value)> mapping to C if C is not already present. +On success, returns 0. On error, returns -1. + +=cut + +*/ + +int map_add(Map *map, const void *key, void *value) +{ + return map_insert(map, key, value, 0); +} + +/* + +=item C + +Adds the C<(key, value)> mapping to C, replacing any existing +C<(key, value)> mapping. On success, returns 0. On error, returns -1. + +=cut + +*/ + +int map_put(Map *map, const void *key, void *value) +{ + return map_insert(map, key, value, 1); +} + +/* + +=item C + +Adds the C<(key, value)> mapping to C, replacing any existing C<(key, +value)> mapping if C is non-zero. On success, returns 0. on error, +or if C is already present and C is zero, returns -1. + +=cut + +*/ + +int map_insert(Map *map, const void *key, void *value, int replace) +{ + Mapping *mapping; + List *chain; + size_t h, c, length; + + if (!map || !key) + return -1; + + if ((double)map->items / (double)map->size >= table_resize_factor) + map_resize(map); + + h = map->hash(map->size, key); + + if (!map->chain[h]) + map->chain[h] = list_create((map_destroy_t *)mapping_release); + + if (!map->chain[h]) + return -1; + + chain = map->chain[h]; + length = list_length(chain); + + for (c = 0; c < length; ++c) + { + mapping = (Mapping *)list_item(chain, c); + + if (!map->cmp(mapping->key, key)) + { + if (!replace) + return -1; + + list_remove(chain, c); + break; + } + } + + if (!(mapping = mapping_create(map->copy(key), value, map->key_destroy, map->value_destroy))) + return -1; + + if (!list_append(chain, mapping)) + { + mapping_release(mapping); + return -1; + } + + ++map->items; + return 0; +} + +/* + +=item C + +Removes C<(key, value)> mapping from C if it is present. If C was +created with a destroy function, then the value will be destroyed. On +success, returns 0. On error, returns -1. + +=cut + +*/ + +int map_remove(Map *map, const void *key) +{ + List *chain; + size_t h, c, length; + + if (!map || !key) + return -1; + + h = map->hash(map->size, key); + chain = map->chain[h]; + + if (!chain) + return -1; + + length = list_length(chain); + + for (c = 0; c < length; ++c) + { + Mapping *mapping = (Mapping *)list_item(chain, c); + + if (!map->cmp(mapping->key, key)) + { + if (!list_remove(chain, c)) + return -1; + + --map->items; + return 0; + } + } + + return -1; +} + +/* + +=item C + +Returns the value associated with C in C, or C if there is +none. + +=cut + +*/ + +void *map_get(const Map *map, const void *key) +{ + List *chain; + size_t h, c, length; + + if (!map || !key) + return NULL; + + h = map->hash(map->size, key); + chain = map->chain[h]; + + if (!chain) + return NULL; + + length = list_length(chain); + + for (c = 0; c < length; ++c) + { + Mapping *mapping = (Mapping *)list_item(chain, c); + + if (!map->cmp(mapping->key, key)) + return mapping->value; + } + + return NULL; +} + +/* + +=item C + +Creates an iterator for C. On success, returns the iterator. On error, +returns C. + +=cut + +*/ + +Mapper *mapper_create(Map *map) +{ + Mapper *mapper; + + if (!map) + return NULL; + + if (!(mapper = mem_new(Mapper))) + return NULL; + + mapper->map = map; + mapper->chain_index = -1; + mapper->item_index = -1; + mapper->next_chain_index = -1; + mapper->next_item_index = -1; + + return mapper; +} + +/* + +=item C + +Releases (deallocates) C. + +=cut + +*/ + +void mapper_release(Mapper *mapper) +{ + mem_release(mapper); +} + +/* + +=item C< #define mapper_destroy(mapper)> + +Destroys (deallocates and sets to C) C. + +=item C + +Destroys (deallocates and sets to C) C. Returns C. This +function is exposed as an implementation side effect. Don't call it +directly. Call I instead. + +=cut + +*/ + +void *mapper_destroy_func(Mapper **mapper) +{ + if (mapper && *mapper) + { + mapper_release(*mapper); + *mapper = NULL; + } + + return NULL; +} + +/* + +=item C + +Returns whether or not there is another item in the map that C is +iterating over. + +=cut + +*/ + +int mapper_has_next(Mapper *mapper) +{ + List *chain; + + if (!mapper) + return 0; + + /* Find the current/first chain */ + + mapper->next_chain_index = mapper->chain_index; + mapper->next_item_index = mapper->item_index; + + if (mapper->next_chain_index == -1) + ++mapper->next_chain_index; + + while (mapper->next_chain_index < mapper->map->size && !mapper->map->chain[mapper->next_chain_index]) + ++mapper->next_chain_index; + + if (mapper->next_chain_index == mapper->map->size) + return 0; + + chain = mapper->map->chain[mapper->next_chain_index]; + + /* Find the next item */ + + if (++mapper->next_item_index < list_length(chain)) + return 1; + + do + { + ++mapper->next_chain_index; + + while (mapper->next_chain_index < mapper->map->size && !mapper->map->chain[mapper->next_chain_index]) + ++mapper->next_chain_index; + + if (mapper->next_chain_index == mapper->map->size) + return 0; + + chain = mapper->map->chain[mapper->next_chain_index]; + } + while (!list_length(chain)); + + mapper->next_item_index = 0; + + return 1; +} + +/* + +=item C + +Returns the next item in the map that C is iterating over. + +=cut + +*/ + +void *mapper_next(Mapper *mapper) +{ + if (!mapper) + return NULL; + + return mapper_next_mapping(mapper)->value; +} + +/* + +=item C + +Returns the next mapping (key, value pair) in the map over which C +is iterating. + +=cut + +*/ + +const Mapping *mapper_next_mapping(Mapper *mapper) +{ + mapper->chain_index = mapper->next_chain_index; + mapper->item_index = mapper->next_item_index; + + return (Mapping *)list_item(mapper->map->chain[mapper->chain_index], mapper->item_index); +} + +/* + +=item C + +Removes the current item in the iteration C. The next item in the +iteration is the item following the removed item, if any. This must be +called after I. + +=cut + +*/ + +void mapper_remove(Mapper *mapper) +{ + if (!mapper) + return; + + list_remove(mapper->map->chain[mapper->chain_index], (size_t)mapper->item_index--); + --mapper->map->items; +} + +/* + +=item C + +Returns whether or not there is another item in C. The first time this +is called, a new, internal I will be created (Note: there can be +only one). When there are no more items, returns zero and destroys the +internal iterator. When it returns a non-zero value, use I to +retrieve the next item. + +=cut + +*/ + +int map_has_next(Map *map) +{ + int ret; + + if (!map) + return 0; + + if (!map->mapper) + map->mapper = mapper_create(map); + + ret = mapper_has_next(map->mapper); + if (!ret) + mapper_destroy(map->mapper); + + return ret; +} + +/* + +=item C + +Returns the next item in C using it's internal iterator. + +=cut + +*/ + +void *map_next(Map *map) +{ + if (!map || !map->mapper) + return NULL; + + return mapper_next(map->mapper); +} + +/* + +=item C + +Returns the next mapping (key, value pair) in C using it's internal +iterator. + +=cut + +*/ + +const Mapping *map_next_mapping(Map *map) +{ + if (!map || !map->mapper) + return NULL; + + return mapper_next_mapping(map->mapper); +} + +/* + +=item C + +Removes the current item in C using it's internal iterator. The next +item in the iteration is the item following the removed item, if any. This +must be called after I. + +=cut + +*/ + +void map_remove_current(Map *map) +{ + mapper_remove(map->mapper); +} + +/* + +=item C + +Returns the key in C. On error, returns C. + +=cut + +*/ + +const void *mapping_key(const Mapping *mapping) +{ + if (!mapping) + return NULL; + + return mapping->key; +} + +/* + +=item C + +Returns the value in C. On error, returns C. + +=cut + +*/ + +const void *mapping_value(const Mapping *mapping) +{ + if (!mapping) + return NULL; + + return mapping->value; +} + +/* + +=item C + +Creates and returns a list of all of the keys contained in C. The +caller is required to destroy the list. Also, the keys in the list are owned +by C so the list returned must not outlive the map. On error, returns +C. + +=cut + +*/ + +List *map_keys(Map *map) +{ + Mapper *mapper; + List *keys; + + if (!map) + return NULL; + + if (!(mapper = mapper_create(map))) + return NULL; + + if (!(keys = list_create(NULL))) + { + mapper_destroy(mapper); + return NULL; + } + + while (mapper_has_next(mapper)) + { + const Mapping *mapping = mapper_next_mapping(mapper); + + if (!list_append(keys, mapping->key)) + { + mapper_destroy(mapper); + list_destroy(keys); + return NULL; + } + } + + mapper_destroy(mapper); + + return keys; +} + +/* + +=item C + +Creates and returns a list of all of the values contained in C. The +caller is required to destroy the list. Also, the values in the list are not +owned by the list so it must not outlive C if C owns them. On +error, returns C. + +=cut + +*/ + +List *map_values(Map *map) +{ + Mapper *mapper; + List *values; + + if (!map) + return NULL; + + if (!(mapper = mapper_create(map))) + return NULL; + + if (!(values = list_create(NULL))) + { + mapper_destroy(mapper); + return NULL; + } + + while (mapper_has_next(mapper)) + { + const Mapping *mapping = mapper_next_mapping(mapper); + + if (!list_append(values, mapping->value)) + { + mapper_destroy(mapper); + list_destroy(values); + return NULL; + } + } + + mapper_destroy(mapper); + + return values; +} + + +/* + +=item C + +Invokes C for each of C's items. The arguments passed to +C are the key, the item and C. + +=cut + +*/ + +void map_apply(Map *map, map_action_t *action, void *data) +{ + Mapper *mapper; + + if (!map || !action) + return; + + if (!(mapper = mapper_create(map))) + return; + + while (mapper_has_next(mapper)) + { + const Mapping *mapping = mapper_next_mapping(mapper); + action(mapping->key, mapping->value, data); + } + + mapper_destroy(mapper); +} + +/* + +=item C + +Returns the number of mappings in C. On error, returns -1. + +=cut + +*/ + +ssize_t map_size(const Map *map) +{ + if (!map) + return -1; + + return map->items; +} + +/* + +=back + +=head1 BUGS + +C can't be a key. Neither can zero when using integers as keys. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +#ifdef NEEDS_SNPRINTF +#include "snprintf.h" +#endif + +#if 0 +static void map_print(const char *name, Map *map) +{ + size_t i, j; + + if (!map) + { + printf("%s = nil\n", name); + return; + } + + printf("%s =\n{\n", name); + + for (i = 0; i < map->size; ++i) + { + if (map->chain[i]) + { + List *chain = map->chain[i]; + + for (j = 0; j < list_length(chain); ++j) + { + Mapping *mapping = (Mapping *)list_item(chain, j); + + printf(" [%d/%d] \"%s\" -> \"%s\"\n", i, j, (char *)mapping->key, (char *)mapping->value); + } + } + } + + printf("}\n"); +} +#endif + +static void map_histogram(const char *name, Map *map) +{ + size_t i; + int *histogram; + + if (!map) + { + printf("%s = nil\n", name); + return; + } + + if (!(histogram = mem_create(map->items, int))) + { + printf("Failed to allocate histogram for map %s\n", name); + return; + } + + memset(histogram, '\0', map->items * sizeof(int)); + + for (i = 0; i < map->size; ++i) + { + size_t length = list_length(map->chain[i]); + ++histogram[length]; + } + + printf("\nhistogram %s =\n{\n", name); + + for (i = 0; i < map->items; ++i) + if (histogram[i]) + printf(" %d chain%s of length %d\n", histogram[i], (histogram[i] == 1) ? "" : "s", i); + + printf("}\n"); +} + +static void test_hash(void) +{ + FILE *words = fopen("/usr/dict/words", "r"); + char word[BUFSIZ]; + Map *map; + size_t c; + size_t min = 0xffffffff; + size_t max = 0x00000000; + int sum = 0; + + if (!words) + { + printf("Failed to open /usr/dict/words\n"); + exit(1); + } + + if (!(map = map_create(free))) + { + printf("Failed to create map\n"); + exit(1); + } + + while (fgets(word, BUFSIZ, words)) + { + char *eow = strchr(word, '\n'); + if (eow) + *eow = '\0'; + + map_add(map, word, mem_strdup(word)); + } + + fclose(words); + + printf("%d entries into %d buckets:\n\n", map->items, map->size); + + for (c = 0; c < map->size; ++c) + { + size_t length; + + if (!map->chain[c]) + continue; + + length = list_length(map->chain[c]); + + if (length > max) + max = length; + if (length < min) + min = length; + sum += length; + } + + printf("avg = %g\n", (double)sum / (double)map->size); + printf("min = %d\n", min); + printf("max = %d\n", max); + map_histogram("dict", map); + map_destroy(map); + + exit(0); +} + +static int sort_cmp(const char **a, const char **b) +{ + return strcmp(*a, *b); +} + +static void test_action(char *key, char *value, char *cat) +{ + size_t len = strlen(cat); + snprintf(cat + len, BUFSIZ, "%s%s=%s", (len) ? ", " : "", key, value); +} + +typedef struct Point Point; +struct Point +{ + int x; + int y; +}; + +static Point *point_create(int x, int y) +{ + Point *point = mem_create(1, Point); + + if (!point) + return NULL; + + point->x = x; + point->y = y; + + return point; +} + +static Point *point_copy(Point *point) +{ + return point_create(point->x, point->y); +} + +static int point_cmp(Point *a, Point *b) +{ + if (a->x > b->x) + return 1; + if (a->y > b->y) + return 1; + if (a->x == b->x && a->y == b->y) + return 0; + return -1; +} + +static size_t point_hash(size_t size, Point *point) +{ + return (point->x * 31 + point->y * 37) % size; +} + +static void point_release(Point *point) +{ + mem_release(point); +} + +static int direct_copy(int key) +{ + return key; +} + +static int direct_cmp(int a, int b) +{ + return a - b; +} + +static size_t direct_hash(size_t size, int key) +{ + return key % size; +} + +int main(int ac, char **av) +{ + int errors = 0; + Map *map; + Mapper *mapper; + List *keys, *values; + const void *ckey; + const char *cvalue; + char *value; + + char cat[BUFSIZ]; + + if (ac == 2 && !strcmp(av[1], "hash")) + test_hash(); + + printf("Testing: map\n"); + + /* Test map_create, map_add, map_get */ + + if (!(map = map_create(NULL))) + ++errors, printf("Test1: map_create(NULL) failed\n"); + else + { + if (map_add(map, "abc", "abc") == -1) + ++errors, printf("Test2: map_add(\"abc\") failed\n"); + if (map_add(map, "def", "def") == -1) + ++errors, printf("Test3: map_add(\"def\") failed\n"); + if (map_add(map, "ghi", "ghi") == -1) + ++errors, printf("Test4: map_add(\"ghi\") failed\n"); + if (map_add(map, "jkl", "jkl") == -1) + ++errors, printf("Test5: map_add(\"jkl\") failed\n"); + + value = (char *)map_get(map, "abc"); + if (!value || strcmp(value, "abc")) + ++errors, printf("Test6: map_get(\"abc\") failed\n"); + value = (char *)map_get(map, "def"); + if (!value || strcmp(value, "def")) + ++errors, printf("Test7: map_get(\"def\") failed\n"); + value = (char *)map_get(map, "ghi"); + if (!value || strcmp(value, "ghi")) + ++errors, printf("Test8: map_get(\"ghi\") failed\n"); + value = (char *)map_get(map, "jkl"); + if (!value || strcmp(value, "jkl")) + ++errors, printf("Test9: map_get(\"jkl\") failed\n"); + value = (char *)map_get(map, "zzz"); + if (value) + ++errors, printf("Test10: map_get(\"zzz\") failed\n"); + + /* Test mapper_create, mapper_has_next, mapper_next, mapp_destroy */ + + if (!(mapper = mapper_create(map))) + ++errors, printf("Test11: mapper_create() failed\n"); + else + { + if (!(keys = list_create(NULL))) + printf("Test12: failed to create keys list\n"); + + while (mapper_has_next(mapper)) + { + void *item = mapper_next(mapper); + + if (!item) + ++errors, printf("Test13: mapper_next() failed\n"); + else + list_append(keys, item); + } + + mapper_destroy(mapper); + if (mapper) + ++errors, printf("Test14: mapper_destroy() failed (%p, not null)", mapper); + + if (list_length(keys) != 4) + ++errors, printf("Test15: mapper failed (%d iterations not 4)\n", list_length(keys)); + else + { + list_sort(keys, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(keys, 0), "abc")) + ++errors, printf("Test16: mapper failed (\"%s\", not \"abc\")\n", (char *)list_item(keys, 0)); + if (strcmp((char *)list_item(keys, 1), "def")) + ++errors, printf("Test17: mapper failed (\"%s\", not \"def\")\n", (char *)list_item(keys, 1)); + if (strcmp((char *)list_item(keys, 2), "ghi")) + ++errors, printf("Test18: mapper failed (\"%s\", not \"ghi\")\n", (char *)list_item(keys, 2)); + if (strcmp((char *)list_item(keys, 3), "jkl")) + ++errors, printf("Test19: mapper failed (\"%s\", not \"jkl\")\n", (char *)list_item(keys, 3)); + } + + list_destroy(keys); + if (keys) + ++errors, printf("Test20: list_destroy() failed (%p, not null)\n", keys); + } + + /* Test mapper_next_mapping, mapping_key, mapping_value */ + + if (!(mapper = mapper_create(map))) + ++errors, printf("Test21: mapper_create() failed\n"); + else + { + if (!(keys = list_create(NULL))) + ++errors, printf("Test22: failed to create keys list\n"); + + if (!(values = list_create(NULL))) + ++errors, printf("Test23: failed to create values list\n"); + + while (mapper_has_next(mapper)) + { + const Mapping *mapping = mapper_next_mapping(mapper); + + if (!mapping) + ++errors, printf("Test24: mapper_next_mapping() failed\n"); + else + { + ckey = mapping_key((void *)mapping); + if (!ckey) + ++errors, printf("Test25: mapping_key() failed\n"); + else + list_append(keys, (void *)ckey); + + cvalue = mapping_value(mapping); + if (!cvalue) + ++errors, printf("Test26: mapping_value() failed\n"); + else + list_append(values, (void *)cvalue); + } + } + + mapper_destroy(mapper); + if (mapper) + ++errors, printf("Test27: mapper_destroy() failed (%p, not null)", mapper); + + if (list_length(keys) != 4) + ++errors, printf("Test28: mapper failed (%d key iterations not 4)\n", list_length(keys)); + else + { + list_sort(keys, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(keys, 0), "abc")) + ++errors, printf("Test29: mapper failed (\"%s\", not \"abc\")\n", (char *)list_item(keys, 0)); + if (strcmp((char *)list_item(keys, 1), "def")) + ++errors, printf("Test30: mapper failed (\"%s\", not \"def\")\n", (char *)list_item(keys, 1)); + if (strcmp((char *)list_item(keys, 2), "ghi")) + ++errors, printf("Test31: mapper failed (\"%s\", not \"ghi\")\n", (char *)list_item(keys, 2)); + if (strcmp((char *)list_item(keys, 3), "jkl")) + ++errors, printf("Test32: mapper failed (\"%s\", not \"jkl\")\n", (char *)list_item(keys, 3)); + } + + list_destroy(keys); + if (keys) + ++errors, printf("Test33: list_destroy() failed (%p, not null)\n", keys); + + if (list_length(values) != 4) + ++errors, printf("Test34: mapper failed (%d value iterations not 4)\n", list_length(values)); + else + { + list_sort(values, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(values, 0), "abc")) + ++errors, printf("Test35: mapper failed (\"%s\", not \"abc\")\n", (char *)list_item(values, 0)); + if (strcmp((char *)list_item(values, 1), "def")) + ++errors, printf("Test36: mapper failed (\"%s\", not \"def\")\n", (char *)list_item(values, 1)); + if (strcmp((char *)list_item(values, 2), "ghi")) + ++errors, printf("Test37: mapper failed (\"%s\", not \"ghi\")\n", (char *)list_item(values, 2)); + if (strcmp((char *)list_item(values, 3), "jkl")) + ++errors, printf("Test38: mapper failed (\"%s\", not \"jkl\")\n", (char *)list_item(values, 3)); + } + + list_destroy(values); + if (values) + ++errors, printf("Test39: list_destroy() failed (%p, not null)\n", values); + } + + /* Test map_has_next, map_next */ + + if (!(keys = list_create(NULL))) + printf("Test40: failed to create keys list\n"); + + while (map_has_next(map)) + { + void *item = map_next(map); + + if (!item) + ++errors, printf("Test41: map_next() failed\n"); + else + list_append(keys, item); + } + + if (list_length(keys) != 4) + ++errors, printf("Test42: map_has_next() failed (%d iterations not 4)\n", list_length(keys)); + else + { + list_sort(keys, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(keys, 0), "abc")) + ++errors, printf("Test43: map_has_next() failed (\"%s\", not \"abc\")\n", (char *)list_item(keys, 0)); + if (strcmp((char *)list_item(keys, 1), "def")) + ++errors, printf("Test44: map_has_next() failed (\"%s\", not \"def\")\n", (char *)list_item(keys, 1)); + if (strcmp((char *)list_item(keys, 2), "ghi")) + ++errors, printf("Test45: map_has_next() failed (\"%s\", not \"ghi\")\n", (char *)list_item(keys, 2)); + if (strcmp((char *)list_item(keys, 3), "jkl")) + ++errors, printf("Test46: map_has_next() failed (\"%s\", not \"jkl\")\n", (char *)list_item(keys, 3)); + } + + list_destroy(keys); + if (keys) + ++errors, printf("Test47: list_destroy() failed (%p, not null)\n", keys); + + /* Test map_next_mapping */ + + if (!(keys = list_create(NULL))) + printf("Test48: failed to create keys list\n"); + + if (!(values = list_create(NULL))) + printf("Test49: failed to create values list\n"); + + while (map_has_next(map)) + { + const Mapping *mapping = map_next_mapping(map); + + if (!mapping) + ++errors, printf("Test50: map_next_mapping() failed\n"); + else + { + ckey = mapping_key(mapping); + if (!ckey) + ++errors, printf("Test51: mapping_key() failed\n"); + else + list_append(keys, (void *)ckey); + + cvalue = mapping_value(mapping); + if (!cvalue) + ++errors, printf("Test52: mapping_value() failed\n"); + else + list_append(values, (void *)cvalue); + } + } + + if (list_length(keys) != 4) + ++errors, printf("Test53: map_has_next() failed (%d iterations not 4)\n", list_length(keys)); + else + { + list_sort(keys, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(keys, 0), "abc")) + ++errors, printf("Test54: map_has_next() failed (\"%s\", not \"abc\")\n", (char *)list_item(keys, 0)); + if (strcmp((char *)list_item(keys, 1), "def")) + ++errors, printf("Test55: map_has_next() failed (\"%s\", not \"def\")\n", (char *)list_item(keys, 1)); + if (strcmp((char *)list_item(keys, 2), "ghi")) + ++errors, printf("Test56: map_has_next() failed (\"%s\", not \"ghi\")\n", (char *)list_item(keys, 2)); + if (strcmp((char *)list_item(keys, 3), "jkl")) + ++errors, printf("Test57: map_has_next() failed (\"%s\", not \"jkl\")\n", (char *)list_item(keys, 3)); + } + + list_destroy(keys); + if (keys) + ++errors, printf("Test58: list_destroy() failed (%p, not null)\n", keys); + + if (list_length(values) != 4) + ++errors, printf("Test59: map_has_next() failed (%d iterations not 4)\n", list_length(values)); + else + { + list_sort(values, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(values, 0), "abc")) + ++errors, printf("Test60: map_has_next() failed (\"%s\", not \"abc\")\n", (char *)list_item(values, 0)); + if (strcmp((char *)list_item(values, 1), "def")) + ++errors, printf("Test61: map_has_next() failed (\"%s\", not \"def\")\n", (char *)list_item(values, 1)); + if (strcmp((char *)list_item(values, 2), "ghi")) + ++errors, printf("Test62: map_has_next() failed (\"%s\", not \"ghi\")\n", (char *)list_item(values, 2)); + if (strcmp((char *)list_item(values, 3), "jkl")) + ++errors, printf("Test63: map_has_next() failed (\"%s\", not \"jkl\")\n", (char *)list_item(values, 3)); + } + + list_destroy(values); + if (values) + ++errors, printf("Test64: list_destroy() failed (%p, not null)\n", values); + + /* Test map_keys */ + + if (!(keys = map_keys(map))) + ++errors, printf("Test65: map_keys() failed\n"); + else + { + if (list_length(keys) != 4) + ++errors, printf("Test66: map_keys failed (%d keys, not 4)\n", list_length(keys)); + else + { + list_sort(keys, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(keys, 0), "abc")) + ++errors, printf("Test67: map_keys failed (\"%s\", not \"abc\")\n", (char *)list_item(keys, 0)); + if (strcmp((char *)list_item(keys, 1), "def")) + ++errors, printf("Test68: map_keys failed (\"%s\", not \"def\")\n", (char *)list_item(keys, 1)); + if (strcmp((char *)list_item(keys, 2), "ghi")) + ++errors, printf("Test69: map_keys failed (\"%s\", not \"ghi\")\n", (char *)list_item(keys, 2)); + if (strcmp((char *)list_item(keys, 3), "jkl")) + ++errors, printf("Test70: map_keys failed (\"%s\", not \"jkl\")\n", (char *)list_item(keys, 3)); + } + + list_destroy(keys); + if (keys) + ++errors, printf("Test71: list_destroy() failed (%p, not null)\n", keys); + } + + /* Test map_values */ + + if (!(values = map_values(map))) + ++errors, printf("Test72: map_values() failed\n"); + else + { + if (list_length(values) != 4) + ++errors, printf("Test73: map_values failed (%d values not 4)\n", list_length(values)); + else + { + list_sort(values, (list_cmp_t *)sort_cmp); + + if (strcmp((char *)list_item(values, 0), "abc")) + ++errors, printf("Test74: map_values failed (\"%s\", not \"abc\")\n", (char *)list_item(values, 0)); + if (strcmp((char *)list_item(values, 1), "def")) + ++errors, printf("Test75: map_values failed (\"%s\", not \"def\")\n", (char *)list_item(values, 1)); + if (strcmp((char *)list_item(values, 2), "ghi")) + ++errors, printf("Test76: map_values failed (\"%s\", not \"ghi\")\n", (char *)list_item(values, 2)); + if (strcmp((char *)list_item(values, 3), "jkl")) + ++errors, printf("Test77: map_values failed (\"%s\", not \"jkl\")\n", (char *)list_item(values, 3)); + } + + list_destroy(values); + if (values) + ++errors, printf("Test78: list_destroy() failed (%p, not null)\n", values); + } + + /* Test map_remove */ + + if (map_remove(map, "zzz") != -1) + ++errors, printf("Test79: map_remove(\"zzz\") failed\n"); + if (map_remove(map, "abc") == -1) + ++errors, printf("Test80: map_remove(\"abc\") failed\n"); + if (map_remove(map, "def") == -1) + ++errors, printf("Test81: map_remove(\"def\") failed\n"); + if (map_remove(map, "ghi") == -1) + ++errors, printf("Test82: map_remove(\"ghi\") failed\n"); + if (map_remove(map, "jkl") == -1) + ++errors, printf("Test83: map_remove(\"jkl\") failed\n"); + + map_destroy(map); + if (map) + ++errors, printf("Test84: map_destroy() failed (%p, not null)\n", map); + } + + /* Test map_apply */ + + if (!(map = map_create(NULL))) + ++errors, printf("Test 85: map_create(NULL) failed\n"); + else + { + if (map_add(map, "1", "7") == -1) + ++errors, printf("Test86: map_add(1, 7) failed\n"); + if (map_add(map, "2", "6") == -1) + ++errors, printf("Test87: map_add(2, 6) failed\n"); + if (map_add(map, "3", "5") == -1) + ++errors, printf("Test88: map_add(3, 5) failed\n"); + if (map_add(map, "4", "4") == -1) + ++errors, printf("Test89: map_add(4, 4) failed\n"); + if (map_add(map, "5", "3") == -1) + ++errors, printf("Test90: map_add(5, 3) failed\n"); + if (map_add(map, "6", "2") == -1) + ++errors, printf("Test91: map_add(6, 2) failed\n"); + if (map_add(map, "7", "1") == -1) + ++errors, printf("Test92: map_add(7, 1) failed\n"); + + value = map_get(map, "1"); + if (strcmp(value, "7")) + ++errors, printf("Test93: map_get(1) failed (%s, not %s)\n", value, "7"); + + value = map_get(map, "2"); + if (strcmp(value, "6")) + ++errors, printf("Test94: map_get(2) failed (%s, not %s)\n", value, "6"); + + value = map_get(map, "3"); + if (strcmp(value, "5")) + ++errors, printf("Test95: map_get(3) failed (%s, not %s)\n", value, "5"); + + value = map_get(map, "4"); + if (strcmp(value, "4")) + ++errors, printf("Test96: map_get(4) failed (%s, not %s)\n", value, "4"); + + value = map_get(map, "5"); + if (strcmp(value, "3")) + ++errors, printf("Test97: map_get(5) failed (%s, not %s)\n", value, "3"); + + value = map_get(map, "6"); + if (strcmp(value, "2")) + ++errors, printf("Test98: map_get(6) failed (%s, not %s)\n", value, "2"); + + value = map_get(map, "7"); + if (strcmp(value, "1")) + ++errors, printf("Test99: map_get(7) failed (%s, not %s)\n", value, "1"); + + cat[0] = '\0'; + map_apply(map, (map_action_t *)test_action, cat); + if (strcmp(cat, "7=1, 1=7, 2=6, 3=5, 4=4, 5=3, 6=2")) + ++errors, printf("Test100: map_apply(cat) failed (cat = \"%s\", not \"%s\")\n", cat, "7=1, 1=7, 2=6, 3=5, 4=4, 5=3, 6=2"); + + map_destroy(map); + if (map) + ++errors, printf("Test101: map_destroy() failed (%p, not null)\n", map); + } + + /* Test mapper_remove, map_size */ + + if (!(map = map_create(NULL))) + ++errors, printf("Test 102: map_create(NULL) failed\n"); + else + { + if (map_add(map, "1", "7") == -1) + ++errors, printf("Test103: map_add(1, 7) failed\n"); + if (map_add(map, "2", "6") == -1) + ++errors, printf("Test104: map_add(2, 6) failed\n"); + if (map_add(map, "3", "5") == -1) + ++errors, printf("Test105: map_add(3, 5) failed\n"); + if (map_add(map, "4", "4") == -1) + ++errors, printf("Test106: map_add(4, 4) failed\n"); + if (map_add(map, "5", "3") == -1) + ++errors, printf("Test107: map_add(5, 3) failed\n"); + if (map_add(map, "6", "2") == -1) + ++errors, printf("Test108: map_add(6, 2) failed\n"); + if (map_add(map, "7", "1") == -1) + ++errors, printf("Test109: map_add(7, 1) failed\n"); + + if (!(mapper = mapper_create(map))) + ++errors, printf("Test110: mapper_create() failed\n"); + else + { + while (mapper_has_next(mapper)) + { + void *item = mapper_next(mapper); + if (!item) + ++errors, printf("Test111: mapper_next() failed\n"); + mapper_remove(mapper); + } + + if (map_size(map)) + ++errors, printf("Test112: mapper_remove() failed (%d item remaining, not 0)\n", map_size(map)); + + mapper_destroy(mapper); + if (mapper) + ++errors, printf("Test113: mapper_destroy() failed (%p, not null)\n", mapper); + } + + map_destroy(map); + if (map) + ++errors, printf("Test114: map_destroy() failed (%p, not null)\n", map); + } + + /* Test map_remove_current */ + + if (!(map = map_create(NULL))) + ++errors, printf("Test 115: map_create(NULL) failed\n"); + else + { + if (map_add(map, "1", "1") == -1) + ++errors, printf("Test116: map_add(1, 1) failed\n"); + if (map_add(map, "2", "2") == -1) + ++errors, printf("Test117: map_add(2, 2) failed\n"); + if (map_add(map, "3", "3") == -1) + ++errors, printf("Test118: map_add(3, 3) failed\n"); + if (map_add(map, "4", "4") == -1) + ++errors, printf("Test119: map_add(4, 4) failed\n"); + + while (map_has_next(map)) + { + void *item = map_next(map); + if (!item) + ++errors, printf("Test120: map_next() failed\n"); + + map_remove_current(map); + } + + if (map_size(map)) + ++errors, printf("Test121: map_remove_current() failed (%d item remaining, not 0)\n", map_size(map)); + + mapper_destroy(mapper); + if (mapper) + ++errors, printf("Test122: mapper_destroy() failed (%p, not null)\n", mapper); + + map_destroy(map); + if (map) + ++errors, printf("Test123: map_destroy() failed (%p, not null)\n", map); + } + + /* Test map_create_generic (Point -> char *) */ + + if (!(map = map_create_generic((map_copy_t *)point_copy, (map_cmp_t *)point_cmp, (map_hash_t *)point_hash, (map_destroy_t *)point_release, NULL))) + ++errors, printf("Test124: map_create_generic() failed\n"); + else + { + Point *point = point_create(0, 0); + + if (map_add(map, point_create(0, 0), "(0, 0)") == -1) + ++errors, printf("Test125: map_add(point(0, 0)) failed\n"); + if (map_add(map, point_create(1, 0), "(1, 0)") == -1) + ++errors, printf("Test126: map_add(point(1, 0)) failed\n"); + if (map_add(map, point_create(0, 1), "(0, 1)") == -1) + ++errors, printf("Test127: map_add(point(0, 1)) failed\n"); + if (map_add(map, point_create(1, 1), "(1, 1)") == -1) + ++errors, printf("Test128: map_add(point(1, 1)) failed\n"); + if (map_add(map, point_create(-1, 0), "(-1, 0)") == -1) + ++errors, printf("Test129: map_add(point(-1, 0)) failed\n"); + if (map_add(map, point_create(0, -1), "(0, -1)") == -1) + ++errors, printf("Test130: map_add(point(0, -1)) failed\n"); + if (map_add(map, point_create(-1, -1), "(-1, -1)") == -1) + ++errors, printf("Test131: map_add(point(-1, -1)) failed\n"); + if (map_add(map, point_create(2, 0), "(2, 0)") == -1) + ++errors, printf("Test132: map_add(point(2, 0)) failed\n"); + if (map_add(map, point_create(0, 2), "(0, 2)") == -1) + ++errors, printf("Test133: map_add(point(0, 2)) failed\n"); + if (map_add(map, point_create(2, 2), "(2, 2)") == -1) + ++errors, printf("Test134: map_add(point(2, 2)) failed\n"); + if (map_add(map, point_create(-2, 0), "(-2, 0)") == -1) + ++errors, printf("Test135: map_add(point(-2, 0)) failed\n"); + if (map_add(map, point_create(0, -2), "(0, -2)") == -1) + ++errors, printf("Test136: map_add(point(0, -2)) failed\n"); + if (map_add(map, point_create(-2, -2), "(-2, -2)") == -1) + ++errors, printf("Test137: map_add(point(-2, -2)) failed\n"); + if (map_add(map, point_create(0, 0), "(0, 0)") != -1) + ++errors, printf("Test138: map_add(point(0, 0)) failed\n"); + + if (map_size(map) != 13) + ++errors, printf("Test139: map_create_generic() failed (%d items, not %d)\n", map_size(map), 13); + + point->x = 0; + point->y = 0; + value = map_get(map, point); + if (!value) + ++errors, printf("Test140: map_get(generic) failed\n"); + else if (strcmp(value, "(0, 0)")) + ++errors, printf("Test141: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(0, 0)"); + + point->x = 1; + point->y = 0; + value = map_get(map, point); + if (!value) + ++errors, printf("Test142: map_get(generic) failed\n"); + else if (strcmp(value, "(1, 0)")) + ++errors, printf("Test143: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(1, 0)"); + + point->x = 0; + point->y = 1; + value = map_get(map, point); + if (!value) + ++errors, printf("Test144: map_get(generic) failed\n"); + else if (strcmp(value, "(0, 1)")) + ++errors, printf("Test145: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(0, 1)"); + + point->x = 1; + point->y = 1; + value = map_get(map, point); + if (!value) + ++errors, printf("Test146: map_get(generic) failed\n"); + else if (strcmp(value, "(1, 1)")) + ++errors, printf("Test147: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(1, 1)"); + + point->x = -1; + point->y = 0; + value = map_get(map, point); + if (!value) + ++errors, printf("Test148: map_get(generic) failed\n"); + else if (strcmp(value, "(-1, 0)")) + ++errors, printf("Test149: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(-1, 0)"); + + point->x = 0; + point->y = -1; + value = map_get(map, point); + if (!value) + ++errors, printf("Test150: map_get(generic) failed\n"); + else if (strcmp(value, "(0, -1)")) + ++errors, printf("Test151: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(0, -1)"); + + point->x = -1; + point->y = -1; + value = map_get(map, point); + if (!value) + ++errors, printf("Test152: map_get(generic) failed\n"); + else if (strcmp(value, "(-1, -1)")) + ++errors, printf("Test153: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(-1, -1)"); + + point->x = 2; + point->y = 0; + value = map_get(map, point); + if (!value) + ++errors, printf("Test154: map_get(generic) failed\n"); + else if (strcmp(value, "(2, 0)")) + ++errors, printf("Test155: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(2, 0)"); + + point->x = 0; + point->y = 2; + value = map_get(map, point); + if (!value) + ++errors, printf("Test156: map_get(generic) failed\n"); + else if (strcmp(value, "(0, 2)")) + ++errors, printf("Test157: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(0, 2)"); + + point->x = 2; + point->y = 2; + value = map_get(map, point); + if (!value) + ++errors, printf("Test158: map_get(generic) failed\n"); + else if (strcmp(value, "(2, 2)")) + ++errors, printf("Test159: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(2, 2)"); + + point->x = -2; + point->y = 0; + value = map_get(map, point); + if (!value) + ++errors, printf("Test160: map_get(generic) failed\n"); + else if (strcmp(value, "(-2, 0)")) + ++errors, printf("Test161: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(-2, 0)"); + + point->x = 0; + point->y = -2; + value = map_get(map, point); + if (!value) + ++errors, printf("Test162: map_get(generic) failed\n"); + else if (strcmp(value, "(0, -2)")) + ++errors, printf("Test163: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(0, -2)"); + + point->x = -2; + point->y = -2; + value = map_get(map, point); + if (!value) + ++errors, printf("Test164: map_get(generic) failed\n"); + else if (strcmp(value, "(-2, -2)")) + ++errors, printf("Test165: map_get(generic) failed (\"%s\", not \"%s\")\n", value, "(-2, -2)"); + + point_release(point); + map_destroy(map); + if (map) + ++errors, printf("Test166: map_destroy() failed (%p, not null)\n", map); + } + + /* Test map_create_generic (int -> int) and map growth */ + + if (!(map = map_create_generic((map_copy_t *)direct_copy, (map_cmp_t *)direct_cmp, (map_hash_t *)direct_hash, NULL, NULL))) + ++errors, printf("Test167: map_create_generic() failed\n"); + else + { + if (map_add(map, (void *)1, (void *)1) == -1) + ++errors, printf("Test168: map_add(1, 1) failed\n"); + if (map_add(map, (void *)2, (void *)2) == -1) + ++errors, printf("Test169: map_add(2, 2) failed\n"); + if (map_add(map, (void *)3, (void *)3) == -1) + ++errors, printf("Test170: map_add(3, 3) failed\n"); + if (map_add(map, (void *)4, (void *)4) == -1) + ++errors, printf("Test171: map_add(4, 4) failed\n"); + if (map_add(map, (void *)5, (void *)5) == -1) + ++errors, printf("Test172: map_add(5, 5) failed\n"); + if (map_add(map, (void *)6, (void *)6) == -1) + ++errors, printf("Test173: map_add(6, 6) failed\n"); + if (map_add(map, (void *)7, (void *)7) == -1) + ++errors, printf("Test174: map_add(7, 7) failed\n"); + if (map_add(map, (void *)8, (void *)8) == -1) + ++errors, printf("Test175: map_add(8, 8) failed\n"); + if (map_add(map, (void *)9, (void *)9) == -1) + ++errors, printf("Test175: map_add(9, 9) failed\n"); + if (map_add(map, (void *)10, (void *)10) == -1) + ++errors, printf("Test176: map_add(10, 10) failed\n"); + if (map_add(map, (void *)11, (void *)11) == -1) + ++errors, printf("Test177: map_add(11, 11) failed\n"); + if (map_add(map, (void *)12, (void *)12) == -1) + ++errors, printf("Test178: map_add(12, 12) failed\n"); + if (map_add(map, (void *)13, (void *)13) == -1) + ++errors, printf("Test179: map_add(13, 13) failed\n"); + if (map_add(map, (void *)14, (void *)14) == -1) + ++errors, printf("Test180: map_add(13, 14) failed\n"); + if (map_add(map, (void *)15, (void *)15) == -1) + ++errors, printf("Test181: map_add(13, 15) failed\n"); + if (map_add(map, (void *)16, (void *)16) == -1) + ++errors, printf("Test182: map_add(13, 16) failed\n"); + if (map_add(map, (void *)17, (void *)17) == -1) + ++errors, printf("Test183: map_add(17, 17) failed\n"); + if (map_add(map, (void *)18, (void *)18) == -1) + ++errors, printf("Test184: map_add(18, 18) failed\n"); + if (map_add(map, (void *)19, (void *)19) == -1) + ++errors, printf("Test185: map_add(19, 19) failed\n"); + if (map_add(map, (void *)20, (void *)20) == -1) + ++errors, printf("Test186: map_add(20, 20) failed\n"); + if (map_add(map, (void *)21, (void *)21) == -1) + ++errors, printf("Test187: map_add(21, 21) failed\n"); + if (map_add(map, (void *)22, (void *)22) == -1) + ++errors, printf("Test188: map_add(22, 22) failed\n"); + if (map_add(map, (void *)23, (void *)23) == -1) + ++errors, printf("Test189: map_add(23, 23) failed\n"); + if (map_add(map, (void *)24, (void *)24) == -1) + ++errors, printf("Test190: map_add(24, 24) failed\n"); + if (map_add(map, (void *)25, (void *)25) == -1) + ++errors, printf("Test191: map_add(25, 25) failed\n"); + if (map_add(map, (void *)25, (void *)25) != -1) + ++errors, printf("Test192: map_add(25, 25) failed\n"); + + if (map_size(map) != 25) + ++errors, printf("Test193: map_create_generic() failed (%d items, not %d)\n", map_size(map), 13); + + if ((int)map_get(map, (void *)1) != 1) + ++errors, printf("Test194: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)1), 1); + if ((int)map_get(map, (void *)2) != 2) + ++errors, printf("Test195: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)2), 2); + if ((int)map_get(map, (void *)3) != 3) + ++errors, printf("Test196: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)3), 3); + if ((int)map_get(map, (void *)4) != 4) + ++errors, printf("Test197: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)4), 4); + if ((int)map_get(map, (void *)5) != 5) + ++errors, printf("Test198: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)5), 5); + if ((int)map_get(map, (void *)6) != 6) + ++errors, printf("Test199: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)6), 6); + if ((int)map_get(map, (void *)7) != 7) + ++errors, printf("Test200: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)7), 7); + if ((int)map_get(map, (void *)8) != 8) + ++errors, printf("Test201: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)8), 8); + if ((int)map_get(map, (void *)9) != 9) + ++errors, printf("Test202: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)9), 9); + if ((int)map_get(map, (void *)10) != 10) + ++errors, printf("Test203: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)10), 10); + if ((int)map_get(map, (void *)11) != 11) + ++errors, printf("Test204: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)11), 11); + if ((int)map_get(map, (void *)12) != 12) + ++errors, printf("Test205: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)12), 12); + if ((int)map_get(map, (void *)13) != 13) + ++errors, printf("Test206: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)13), 13); + if ((int)map_get(map, (void *)14) != 14) + ++errors, printf("Test207: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)14), 14); + if ((int)map_get(map, (void *)15) != 15) + ++errors, printf("Test208: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)15), 15); + if ((int)map_get(map, (void *)16) != 16) + ++errors, printf("Test209: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)16), 16); + if ((int)map_get(map, (void *)17) != 17) + ++errors, printf("Test210: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)17), 17); + if ((int)map_get(map, (void *)18) != 18) + ++errors, printf("Test211: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)18), 18); + if ((int)map_get(map, (void *)19) != 19) + ++errors, printf("Test212: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)19), 19); + if ((int)map_get(map, (void *)20) != 20) + ++errors, printf("Test213: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)20), 20); + if ((int)map_get(map, (void *)21) != 21) + ++errors, printf("Test214: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)21), 21); + if ((int)map_get(map, (void *)22) != 22) + ++errors, printf("Test215: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)22), 22); + if ((int)map_get(map, (void *)23) != 23) + ++errors, printf("Test216: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)23), 23); + if ((int)map_get(map, (void *)24) != 24) + ++errors, printf("Test217: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)24), 24); + if ((int)map_get(map, (void *)25) != 25) + ++errors, printf("Test218: map_get(generic, int) failed (%d, not %d)\n", (int)map_get(map, (void *)25), 25); + + map_destroy(map); + if (map) + ++errors, printf("Test219: map_destroy() failed (%p, not null)\n", map); + } + + if (errors) + printf("%d/219 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/map.h b/map.h new file mode 100644 index 0000000..f3c5792 --- /dev/null +++ b/map.h @@ -0,0 +1,81 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_MAP_H +#define LIBSLACK_MAP_H + +#include +#include + +typedef struct Map Map; +typedef struct Mapper Mapper; +typedef struct Mapping Mapping; +typedef list_destroy_t map_destroy_t; +typedef list_copy_t map_copy_t; +typedef list_cmp_t map_cmp_t; +typedef size_t map_hash_t(size_t table_size, const void *key); +typedef void map_action_t(void *key, void *item, void *data); + +#undef map_destroy +#undef mapper_destroy + +__START_DECLS +Map *map_create __PROTO ((map_destroy_t *destroy)); +Map *map_create_sized __PROTO ((size_t size, map_destroy_t *destroy)); +Map *map_create_with_hash __PROTO ((map_hash_t *hash, map_destroy_t *destroy)); +Map *map_create_with_hash_sized __PROTO ((size_t size, map_hash_t *hash, map_destroy_t *destroy)); +Map *map_create_generic __PROTO ((map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_destroy_t *key_destroy, map_destroy_t *value_destroy)); +Map *map_create_generic_sized __PROTO ((size_t size, map_copy_t *copy, map_cmp_t *cmp, map_hash_t *hash, map_destroy_t *key_destroy, map_destroy_t *value_destroy)); +void map_release __PROTO ((Map *map)); +#define map_destroy(map) map_destroy_func(&(map)) +void *map_destroy_func __PROTO ((Map **map)); +int map_own __PROTO ((Map *map, map_destroy_t *destroy)); +map_destroy_t *map_disown __PROTO ((Map *map)); +int map_add __PROTO ((Map *map, const void *key, void *value)); +int map_put __PROTO ((Map *map, const void *key, void *value)); +int map_insert __PROTO ((Map *map, const void *key, void *value, int replace)); +int map_remove __PROTO ((Map *map, const void *key)); +void *map_get __PROTO ((const Map *map, const void *key)); +Mapper *mapper_create __PROTO ((Map *map)); +void mapper_release __PROTO ((Mapper *mapper)); +#define mapper_destroy(mapper) mapper_destroy_func(&(mapper)) +void *mapper_destroy_func __PROTO ((Mapper **mapper)); +int mapper_has_next __PROTO ((Mapper *mapper)); +void *mapper_next __PROTO ((Mapper *mapper)); +const Mapping *mapper_next_mapping __PROTO ((Mapper *mapper)); +void mapper_remove __PROTO ((Mapper *mapper)); +int map_has_next __PROTO ((Map *map)); +void *map_next __PROTO ((Map *map)); +const Mapping *map_next_mapping __PROTO ((Map *map)); +void map_remove_current __PROTO ((Map *map)); +const void *mapping_key __PROTO ((const Mapping *mapping)); +const void *mapping_value __PROTO ((const Mapping *mapping)); +List *map_keys __PROTO ((Map *map)); +List *map_values __PROTO ((Map *map)); +void map_apply __PROTO ((Map *, map_action_t *action, void *data)); +ssize_t map_size __PROTO ((const Map *map)); +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/mem.c b/mem.c new file mode 100644 index 0000000..2cb3e00 --- /dev/null +++ b/mem.c @@ -0,0 +1,823 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - memory module + +=head1 SYNOPSIS + + #include + + #define mem_new(type) + #define mem_create(size, type) + #define mem_resize(mem, size) + void *mem_resize_fn(void **mem, size_t size); + #define mem_release(mem) + #define mem_destroy(mem) + void *mem_destroy_fn(void **mem); + char *mem_strdup(const char *str); + #define mem_create2d(type, x, y) + #define mem_create3d(type, x, y, z) + #define mem_create4d(type, x, y, z, a) + void *mem_create_space(size_t size, ...); + size_t mem_space_start(size_t size, ...); + #define mem_release2d(space) + #define mem_release3d(space) + #define mem_release4d(space) + #define mem_release_space(space) + #define mem_destroy2d(space) + #define mem_destroy3d(space) + #define mem_destroy4d(space) + #define mem_destroy_space(space) + +=head1 DESCRIPTION + +This module is mostly just an interface to I, I and +I that tries to ensure that pointers that don't point to anything +are C. It also provides dynamically allocated multi-dimensional arrays. + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include "mem.h" + +/* + +=item C< #define mem_new(type)> + +Allocates enough memory (with I) to store an object of type +C. The memory returned is of type C. On success, returns the +address of the allocated memory. On error, returns C. + +=item C< #define mem_create(size, type)> + +Allocates enough memory (with I) to store C objects of type +C. The memory returned is of type C. On success, returns the +address of the allocated memory. On error, returns C. + +=item C< #define mem_resize(mem, num)> + +Alters the amount of memory pointed to by C. If C is C, +I is used to allocate new memory. If size is zero, I is +called to deallocate the memory and C is set to C. Otherwise, +I is called. If I needs to allocate new memory to +satisfy a request, C is set to the new address. On success, returns +I (though it may be C if C is zero). On error, C is +returned and I is not altered. + +=item C + +A single interface for altering the size of allocated memory. C points +to the pointer to be affected. C is the size in bytes of the memory +that this pointer is to point to. If the pointer is C, I is +used to obtain memory. If C is zero, I is used to release the +memory. In all other cases, I is used to alter the size of the +memory. In all cases, the pointer pointed to by C is assigned to the +memory's location (or C when C is zero). This function is +exposed as an implementation side effect. Don't call it directly. Call +I instead. On error (i.e. I or I fail +or C is C), returns C without setting C<*mem> to anything. + +=cut + +*/ + +void *mem_resize_fn(void **mem, size_t size) +{ + void *p; + + if (!mem) + return NULL; + + if (size) + { + if (*mem) + { + p = realloc(*mem, size); + if (p) + return *mem = p; + } + else + { + p = malloc(size); + if (p) + return *mem = p; + } + } + else + { + free(*mem); + return *mem = NULL; + } + + return NULL; +} + +/* + +=item C< #define mem_release(mem)> + +Releases (deallocates) C. Same as I. Only to be used in +destructor functions. In other cases, use I which also sets +C to C. + +=item C< #define mem_destroy(mem)> + +Destroys (deallocates and sets to C) the memory pointed to by C. + +=item C + +Calls I on the pointer pointed to by C. Then assigns C +to this pointer. Returns C. This function is exposed as an +implementation side effect. Don't call it directly. Call I +instead. + +=cut + +*/ + +void *mem_destroy_fn(void **mem) +{ + if (mem && *mem) + { + free(*mem); + *mem = NULL; + } + + return NULL; +} + +/* + +=item C + +Returns a dynamically allocated copy of C. On error, returns C. +The caller must deallocate the memory returned. + +=cut + +*/ + +char *mem_strdup(const char *str) +{ + char *s; + + if (!str || !(s = mem_create(strlen(str) + 1, char))) + return NULL; + + return strcpy(s, str); +} + +/* + +=item C< #define mem_create2d(i, j, type)> + +Shorthand for allocating a 2-dimensional array. See I. + +=item C< #define mem_create3d(i, j, k, type)> + +Shorthand for allocating a 3-dimensional array. See I. + +=item C< #define mem_create4d(i, j, k, l, type)> + +Shorthand for allocating a 4-dimensional array. See I. + +=item C + +Allocates a multi-dimensional array of elements of size C and sets the +memory to zero. The remaining arguments specify the sizes of each dimension. +The last argument must be zero. There is an arbitrary limit of 32 +dimensions. The memory returned is set to zero. The memory returned needs to +be cast or assigned into the appropriate pointer type. You can then set and +access elements exactly like a real multi-dimensional C array. Finally, it +must be deallocated with I or I or +I or I or I. + +Note: You must not use I on all of the returned memory because +the start of this memory contains pointers into the remainder. The exact +amount of this overhead depends on the number and size of dimensions. The +memory is allocated with I to reduce the need to I the +elements but if you need to know where the elements begin, use +I. + +The memory returned looks like (e.g.): + + char ***a = mem_create3d(2, 2, 3, char); + + +-------------------------+ + +-------|-------------------+ | + +-------|-------|-------------+ | | + +-------|-------|-------|-------+ | | | + | | | | V V V V + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + a ->| a[0] | a[1] |a[0][0]|a[0][1]|a[1][0]|a[1][1]| | | | | | | | | | | | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | ^ ^ a a a a a a a a a a a a + +-------|-------+ | 0 0 0 0 0 0 1 1 1 1 1 1 + +-----------------------+ 0 0 0 1 1 1 0 0 0 1 1 1 + 0 1 2 0 1 2 0 1 2 0 1 2 + +=cut + +*/ + +#ifndef MEM_MAX_DIM +#define MEM_MAX_DIM 32 +#endif + +void *mem_create_space(size_t size, ...) +{ + size_t dim[MEM_MAX_DIM], d, i, j; + size_t lengths[MEM_MAX_DIM]; + size_t starts[MEM_MAX_DIM]; + size_t sizes[MEM_MAX_DIM]; + char *space; + size_t arg, length; + va_list args; + + va_start(args, size); + for (d = 0; d < MEM_MAX_DIM && (arg = va_arg(args, size_t)); ++d) + dim[d] = arg; + va_end(args); + + length = 0; + for (i = 0; i < d; ++i) + { + starts[i] = length; + lengths[i] = sizes[i] = (i == d - 1) ? size : sizeof(void *); + for (j = 0; j <= i; ++j) + lengths[i] *= dim[j]; + length += lengths[i]; + } + + if (!(space = calloc(length, 1))) + return NULL; + + for (i = 0; i < d - 1; ++i) + { + size_t num = dim[i]; + + for (j = 0; j < i; ++j) + num *= dim[j]; + + for (j = 0; j < num; ++j) + *(char **)(space + starts[i] + j * sizes[i]) = space + starts[i + 1] + j * dim[i + 1] * sizes[i + 1]; + } + + return space; +} + +/* + +=item C + +Calculates the amount of overhead required for a multi-dimensional array +created by a call to I with the same arguments. If you +need reset all elements in such an array to zero: + + int ****space = mem_create_space(sizeof(int), 2, 3, 4, 5, 0); + size_t start = mem_space_start(sizeof(int), 2, 3, 4, 5, 0); + memset((char *)space + start, '\0', sizeof(int) * 2 * 3 * 4 * 5); + +=cut + +*/ + +size_t mem_space_start(size_t size, ...) +{ + size_t dim[MEM_MAX_DIM], d, i, j; + size_t lengths[MEM_MAX_DIM]; + size_t arg, length; + va_list args; + + va_start(args, size); + for (d = 0; d < MEM_MAX_DIM && (arg = va_arg(args, size_t)); ++d) + dim[d] = arg; + va_end(args); + + length = 0; + for (i = 0; i < d; ++i) + { + lengths[i] = (i == d - 1) ? size : sizeof(void *); + for (j = 0; j <= i; ++j) + lengths[i] *= dim[j]; + length += lengths[i]; + } + + return length - lengths[d - 1]; +} + +/* + +=item C< #define mem_release2d(space)> + +Alias for releasing (deallocating) a 2-dimensional array. +See I. + +=item C< #define mem_release3d(space)> + +Alias for releasing (deallocating) a 3-dimensional array. +See I. + +=item C< #define mem_release4d(space)> + +Alias for releasing (deallocating) a 4-dimensional array. +See I. + +=item C< #define mem_release_space(space)> + +Releases (deallocates) a multi-dimensional array, C, allocated with +I. Same as I. Only to be used in destructor +functions. In other cases, use I which also sets +C to C. + +=item C< #define mem_destroy2d(space)> + +Alias for destroying (deallocating and setting to C) a +2-dimensional array. See I. + +=item C< #define mem_destroy3d(space)> + +Alias for destroying (deallocating and setting to C) a +3-dimensional array. See I. + +=item C< #define mem_destroy4d(space)> + +Alias for destroying (deallocating and setting to C) a +4-dimensional array. See I. + +=item C< #define mem_destroy_space(mem)> + +Destroys (deallocates and sets to C) the multi-dimensional array +pointed to by C. + +=back + +=head1 EXAMPLES + +1D array of longs: + + long *mem = mem_create(100, long); + mem_resize(mem, 200); + mem_destroy(mem); + +3D array of ints: + + int ***space = mem_create3d(10, 20, 30, int); + int i, j, k; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 20; ++j) + for (k = 0; k < 30; ++k) + space[i][j][k] = i + j + j; + + mem_destroy3d(space); + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L + +=head1 AUTHOR + +20000902 raf + +=cut + +*/ + +#ifdef TEST + +int main(int ac, char **av) +{ + int *mem1 = NULL; + char *mem2 = NULL; + char *str = NULL; + int **space2 = NULL; + int ***space3 = NULL; + int ****space4 = NULL; + int *****space5 = NULL; + char **space2d = NULL; + double ***space3d = NULL; + int i, j, k, l, m; + int errors = 0; + + printf("Testing: mem\n"); + + /* Test create, resize and destroy */ + + mem1 = mem_create(100, int); + if (!mem1) + ++errors, printf("Test1: mem_create(100, int) failed\n"); + + mem_resize(mem1, 50); + if (!mem1) + ++errors, printf("Test2: mem_resize(100 -> 50) failed\n"); + + mem_resize(mem1, 0); + if (mem1) + ++errors, printf("Test3: mem_resize(50 -> 0) failed\n"); + + mem_resize(mem1, 50); + if (!mem1) + ++errors, printf("Test4: mem_resize(0 -> 50) failed\n"); + + mem_destroy(mem1); + if (mem1) + ++errors, printf("Test5: mem_destroy() failed\n"); + + mem2 = mem_create(0, char); + if (!mem2) + ++errors, printf("Test6: mem_create(0, char) failed\n"); + + mem_resize(mem2, 10); + if (!mem2) + ++errors, printf("Test7: mem_resize(0 -> 10) failed\n"); + + mem_resize(mem2, 0); + if (mem2) + ++errors, printf("Test8: mem_resize(10 -> 0) failed\n"); + + mem_destroy(mem2); + if (mem2) + ++errors, printf("Test9: mem_destroy() failed\n"); + + /* Test strdup */ + + str = mem_strdup("test"); + if (!str) + ++errors, printf("Test10: mem_strdup() failed (returned null)\n"); + else if (strcmp(str, "test")) + { + ++errors, printf("Test11: mem_strdup() failed (equals \"%s\", not \"test\")\n", str); + mem_release(str); + } + + /* Test 2D space allocation and deallocation */ + + space2 = mem_create_space(sizeof(int), 1, 1, 0); + if (!space2) + ++errors, printf("Test12: mem_create_space(1, 1) failed (returned null)\n"); + else + { + space2[0][0] = 37; + if (space2[0][0] != 37) + ++errors, printf("Test13: mem_create_space(1, 1) failed (space2[%d][%d] = %d, not %d)\n", 0, 0, space2[0][0], 37); + + mem_destroy_space(space2); + if (space2) + ++errors, printf("Test14: mem_destroy_space(1, 1) failed\n"); + } + + space2 = mem_create_space(sizeof(int), 10, 10, 0); + if (!space2) + ++errors, printf("Test15: mem_create_space(10, 10) failed (returned null)\n"); + else + { + int error = 0; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + space2[i][j] = i + j; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + if (space2[i][j] != i + j) + ++error, printf("Test16: mem_create_space(10, 10) failed (space2[%d][%d] = %d, not %d)\n", i, j, space2[i][j], i + j); + + if (error) + ++errors; + + mem_destroy_space(space2); + if (space2) + ++errors, printf("Test17: mem_destroy_space(10, 10) failed\n"); + } + + /* Test 3D space allocation and deallocation */ + + space3 = mem_create_space(sizeof(int), 1, 1, 1, 0); + if (!space3) + ++errors, printf("Test18: mem_create_space(1, 1, 1) failed (returned null)\n"); + else + { + space3[0][0][0] = 37; + if (space3[0][0][0] != 37) + ++errors, printf("Test19: mem_create_space(1, 1, 1) failed (space3[%d][%d][%d] = %d, not %d)\n", 0, 0, 0, space3[0][0][0], 37); + + mem_destroy_space(space3); + if (space3) + ++errors, printf("Test20: mem_destroy_space(1, 1, 1) failed\n"); + } + + space3 = mem_create_space(sizeof(int), 10, 10, 10, 0); + if (!space3) + ++errors, printf("Test21: mem_create_space(10, 10, 10) failed (returned null)\n"); + else + { + int error = 0; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + space3[i][j][k] = i + j + k; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + if (space3[i][j][k] != i + j + k) + ++error, printf("Test22: mem_create_space(10, 10, 10) failed (space3[%d][%d][%d] = %d, not %d)\n", i, j, k, space3[i][j][k], i + j + k); + + if (error) + ++errors; + + mem_destroy_space(space3); + if (space3) + ++errors, printf("Test23: mem_destroy_space(10, 10, 10) failed\n"); + } + + /* Test 4D space allocation and deallocation */ + + space4 = mem_create_space(sizeof(int), 1, 1, 1, 1, 0); + if (!space4) + ++errors, printf("Test24: mem_create_space(1, 1, 1, 1) failed (returned null)\n"); + else + { + space4[0][0][0][0] = 37; + if (space4[0][0][0][0] != 37) + ++errors, printf("Test25: mem_create_space(1, 1, 1, 1) failed (space4[%d][%d][%d][%d] = %d, not %d)\n", 0, 0, 0, 0, space4[0][0][0][0], 37); + + mem_destroy_space(space4); + if (space4) + ++errors, printf("Test26: mem_destroy_space(1, 1, 1, 1) failed\n"); + } + + space4 = mem_create_space(sizeof(int), 10, 10, 10, 10, 0); + if (!space4) + ++errors, printf("Test27: mem_create_space(10, 10, 10, 10) failed (returned null)\n"); + else + { + int error = 0; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + for (l = 0; l < 10; ++l) + space4[i][j][k][l] = i + j + k + l; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + for (l = 0; l < 10; ++l) + if (space4[i][j][k][l] != i + j + k + l) + ++error, printf("Test28: mem_create_space(10, 10, 10, 10) failed (space4[%d][%d][%d][%d] = %d, not %d)\n", i, j, k, l, space4[i][j][k][l], i + j + k + l); + + if (error) + ++errors; + + mem_destroy_space(space4); + if (space4) + ++errors, printf("Test29: mem_destroy_space(10, 10, 10, 10) failed\n"); + } + + /* Test 5D space allocation and deallocation */ + + space5 = mem_create_space(sizeof(int), 1, 1, 1, 1, 1, 0); + if (!space5) + ++errors, printf("Test30: mem_create_space(1, 1, 1, 1, 1) failed (returned null)\n"); + else + { + space5[0][0][0][0][0] = 37; + if (space5[0][0][0][0][0] != 37) + ++errors, printf("Test31: mem_create_space(1, 1, 1, 1, 1) failed (space5[%d][%d][%d][%d][%d] = %d, not %d)\n", 0, 0, 0, 0, 0, space5[0][0][0][0][0], 37); + + mem_destroy_space(space5); + if (space5) + ++errors, printf("Test32: mem_destroy_space(1, 1, 1, 1, 1) failed\n"); + } + + space5 = mem_create_space(sizeof(int), 10, 10, 10, 10, 10, 0); + if (!space5) + ++errors, printf("Test33: mem_create_space(10, 10, 10, 10, 10) failed (returned null)\n"); + else + { + int error = 0; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + for (l = 0; l < 10; ++l) + for (m = 0; m < 10; ++m) + space5[i][j][k][l][m] = i + j + k + l + m; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + for (l = 0; l < 10; ++l) + for (m = 0; m < 10; ++m) + if (space5[i][j][k][l][m] != i + j + k + l + m) + ++error, printf("Test34: mem_create_space(10, 10, 10, 10, 10) failed (space5[%d][%d][%d][%d][%d] = %d, not %d)\n", i, j, k, l, m, space5[i][j][k][l][m], i + j + k + l + m); + + if (error) + ++errors; + + mem_destroy_space(space5); + if (space5) + ++errors, printf("Test35: mem_destroy_space(10, 10, 10, 10, 10) failed\n"); + } + + /* Test element sizes smaller than sizeof(void *) */ + + space2d = mem_create_space(sizeof(char), 1, 1, 0); + if (!space2d) + ++errors, printf("Test36: mem_create_space(char, 1, 1) failed (returned null)\n"); + else + { + space2d[0][0] = 'a'; + if (space2d[0][0] != 'a') + ++errors, printf("Test37: mem_create_space(char, 1, 1) failed (space2d[%d][%d] = '%c', not '%c')\n", 0, 0, space2d[0][0], 'a'); + + mem_destroy_space(space2d); + if (space2d) + ++errors, printf("Test38: mem_destroy_space(char, 1, 1) failed\n"); + } + + space2d = mem_create_space(sizeof(char), 10, 10, 0); + if (!space2d) + ++errors, printf("Test39: mem_create_space(char, 10, 10) failed (returned null)\n"); + else + { + int error = 0; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + space2d[i][j] = 'a' + (i + j) % 26; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + if (space2d[i][j] != 'a' + (i + j) % 26) + ++error, printf("Test40: mem_create_space(char, 10, 10) failed (space2d[%d][%d] = '%c', not '%c')\n", i, j, space2d[i][j], 'a' + (i + j) % 26); + + if (error) + ++errors; + + mem_destroy_space(space2d); + if (space2d) + ++errors, printf("Test41: mem_destroy_space(char, 10, 10) failed\n"); + } + + /* Test element sizes larger than sizeof(void *) */ + + space3d = mem_create_space(sizeof(double), 1, 1, 1, 0); + if (!space3d) + ++errors, printf("Test42: mem_create_space(double, 1, 1, 1) failed (returned null)\n"); + else + { + space3d[0][0][0] = 37.5; + if (space3d[0][0][0] != 37.5) + ++errors, printf("Test43: mem_create_space(double, 1, 1, 1) failed (space3d[%d][%d][%d] = %g, not %g)\n", 0, 0, 0, space3d[0][0][0], 37.5); + + mem_destroy_space(space3d); + if (space3d) + ++errors, printf("Test44: mem_destroy_space(double, 1, 1, 1) failed\n"); + } + + space3d = mem_create_space(sizeof(double), 10, 10, 10, 0); + if (!space3d) + ++errors, printf("Test45: mem_create_space(double, 10, 10, 10) failed (returned null)\n"); + else + { + int error = 0; + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + space3d[i][j][k] = (double)(i + j + k); + + for (i = 0; i < 10; ++i) + for (j = 0; j < 10; ++j) + for (k = 0; k < 10; ++k) + if (space3d[i][j][k] != (double)(i + j + k)) + ++error, printf("Test46: mem_create_space(double, 10, 10, 10) failed (space3[%d][%d][%d] = %g, not %g)\n", i, j, k, space3d[i][j][k], (double)(i + j + k)); + + if (error) + ++errors; + + mem_destroy_space(space3d); + if (space3d) + ++errors, printf("Test47: mem_destroy_space(double, 10, 10, 10) failed\n"); + } + + /* Test mem_space_start() */ + + space4 = mem_create_space(sizeof(int), 2, 3, 4, 5, 0); + if (!space4) + ++errors, printf("Test48: mem_create_space(int, 2, 3, 4, 5) failed (returned null)\n"); + else + { + int error = 0; + size_t start; + + for (i = 0; i < 2; ++i) + for (j = 0; j < 3; ++j) + for (k = 0; k < 4; ++k) + for (l = 0; l < 5; ++l) + space4[i][j][k][l] = i + j + k + l; + + for (i = 0; i < 2; ++i) + for (j = 0; j < 3; ++j) + for (k = 0; k < 4; ++k) + for (l = 0; l < 5; ++l) + if (space4[i][j][k][l] != i + j + k + l) + ++error, printf("Test49: mem_create_space(int, 2, 3, 4, 5) failed (space4[%d][%d][%d][%d] = %d, not %d)\n", i, j, k, l, space4[i][j][k][l], i + j + k + l); + + if (error) + ++errors; + + start = mem_space_start(sizeof(int), 2, 3, 4, 5, 0); + memset((char *)space4 + start, '\0', sizeof(int) * 2 * 3 * 4 * 5); + + for (i = 0; i < 2; ++i) + for (j = 0; j < 3; ++j) + for (k = 0; k < 4; ++k) + for (l = 0; l < 4; ++l) + if (space4[i][j][k][l] != 0) + ++error, printf("Test50: mem_space_start(int, 2, 3, 4, 5) failed (space4[%d][%d][%d][%d] = %d, not %d)\n", i, j, k, l, space4[i][j][k][l], 0); + + for (i = 0; i < 2; ++i) + for (j = 0; j < 3; ++j) + for (k = 0; k < 4; ++k) + for (l = 0; l < 5; ++l) + space4[i][j][k][l] = i + j + k + l; + + for (i = 0; i < 2; ++i) + for (j = 0; j < 3; ++j) + for (k = 0; k < 4; ++k) + for (l = 0; l < 5; ++l) + if (space4[i][j][k][l] != i + j + k + l) + ++error, printf("Test51: mem_space_start(int, 2, 3, 4, 5) failed (space4[%d][%d][%d][%d] = %d, not %d)\n", i, j, k, l, space4[i][j][k][l], i + j + k + l); + + if (error) + ++errors; + + mem_destroy_space(space4); + if (space4) + ++errors, printf("Test52: mem_destroy_space(int, 2, 3, 4, 5) failed\n"); + } + + if (errors) + printf("%d/52 tests failed\n", errors); + else + printf("All tests passed\n"); + + return 0; +} + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/mem.h b/mem.h new file mode 100644 index 0000000..ee5255b --- /dev/null +++ b/mem.h @@ -0,0 +1,74 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +#ifndef LIBSLACK_MEM_H +#define LIBSLACK_MEM_H + +#include + +#include + +#undef mem_new +#undef mem_create +#undef mem_resize +#undef mem_release +#undef mem_destroy +#undef mem_create2d +#undef mem_create3d +#undef mem_create4d +#undef mem_release2d +#undef mem_release3d +#undef mem_release4d +#undef mem_release_space +#undef mem_destroy2d +#undef mem_destroy3d +#undef mem_destroy4d +#undef mem_destroy_space + +__START_DECLS +#define mem_new(type) malloc(sizeof(type)) +#define mem_create(size, type) malloc((size) * sizeof(type)) +#define mem_resize(mem, size) mem_resize_fn((void *)&(mem), (size) * sizeof(*(mem))) +void *mem_resize_fn __PROTO ((void **mem, size_t size)); +#define mem_release(mem) free(mem) +#define mem_destroy(mem) mem_destroy_fn((void **)&(mem)) +void *mem_destroy_fn __PROTO ((void **mem)); +char *mem_strdup __PROTO ((const char *str)); +#define mem_create2d(i, j, type) ((type **)mem_create_space(sizeof(type), (i), (j), 0)) +#define mem_create3d(i, j, k, type) ((type ***)mem_create_space(sizeof(type), (i), (j), (k), 0)) +#define mem_create4d(i, j, k, l, type) ((type ****)mem_create_space(sizeof(type), (i), (j), (k), (l), 0)) +void *mem_create_space __PROTO ((size_t size, ...)); +size_t mem_space_start __PROTO ((size_t size, ...)); +#define mem_release2d(space) mem_release_space(space) +#define mem_release3d(space) mem_release_space(space) +#define mem_release4d(space) mem_release_space(space) +#define mem_release_space(space) mem_release(space) +#define mem_destroy2d(space) mem_destroy_space(space) +#define mem_destroy3d(space) mem_destroy_space(space) +#define mem_destroy4d(space) mem_destroy_space(space) +#define mem_destroy_space(space) mem_destroy(space) +__STOP_DECLS + +#endif + +/* vi:set ts=4 sw=4: */ diff --git a/msg.c b/msg.c new file mode 100644 index 0000000..2dea7b1 --- /dev/null +++ b/msg.c @@ -0,0 +1,908 @@ +/* +* libslack - http://libslack.org/ +* +* Copyright (C) 1999, 2000 raf +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 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 General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +* or visit http://www.gnu.org/copyleft/gpl.html +* +* 20000902 raf +*/ + +/* + +=head1 NAME + +I - message module + +=head1 SYNOPSIS + + #include + + typedef struct Msg Msg; + + void msg_release(Msg *msg); + #define msg_destroy(msg) + void *msg_destroy_fn(Msg **msg); + void vmsg_out(Msg *dst, const char *fmt, va_list args); + void msg_out(Msg *dst, const char *fmt, ...); + Msg *msg_create_fd(int fd); + Msg *msg_create_stderr(void); + Msg *msg_create_stdout(void); + Msg *msg_create_file(const char *path); + Msg *msg_create_syslog(const char *ident, int option, int facility); + Msg *msg_create_plex(Msg *msg1, Msg *msg2); + int msg_add_plex(Msg *msg, Msg *item); + const char *msg_set_timestamp_format(const char *format); + +=head1 DESCRIPTION + +This module provides general messaging functions. Message channels can be +created that send messages to a file descriptor, a file, I or +multiplex messages to any combination of the above. Messages sent to files +are timestamped using (by default) the I format: C<"%Y%m%d +%H:%M:%S">. + +=over 4 + +=cut + +*/ + +#include "std.h" + +#include +#include +#include + +#include + +#include "msg.h" +#include "mem.h" + +#ifdef NEEDS_SNPRINTF +#include "snprintf.h" +#endif + +typedef void msg_out_t(void *data, const void *msg, size_t msglen); +typedef void msg_destroy_t(void *data); +typedef int MsgFDData; +typedef struct MsgFileData MsgFileData; +typedef struct MsgSysData MsgSysData; +typedef struct MsgPlexData MsgPlexData; + +struct Msg +{ + msg_out_t *out; /* message handling function */ + void *data; /* sybtype specific data */ + msg_destroy_t *destroy; /* destructor function for data */ +}; + +struct MsgFileData +{ + char *path; /* file path */ + int fd; /* file descriptor (-1 when closed) */ +}; + +struct MsgSysData +{ + char *ident; /* syslog(3) ident */ + int option; /* sysglog(3) option */ + int facility; /* syslog(3) facility | priority */ +}; + +struct MsgPlexData +{ + size_t size; /* elements allocated */ + size_t length; /* length of Msg list */ + Msg **list; /* list of Msg objects */ +}; + +/* + +C + +Creates a I object initialised with C, C and C. +On success, returns the new I object. On error, returns C. + +*/ + +static Msg *msg_create(msg_out_t *out, void *data, msg_destroy_t *destroy) +{ + Msg *msg; + + if (!(msg = mem_new(Msg))) + return NULL; + + msg->out = out; + msg->data = data; + msg->destroy = destroy; + + return msg; +} + +/* + +=item C + +Releases (deallocates) C and its internal data. + +=cut + +*/ + +void msg_release(Msg *msg) +{ + if (!msg) + return; + + if (msg->destroy) + msg->destroy(msg->data); + + mem_release(msg); +} + +/* + +=item C< #define msg_destroy(msg)> + +Destroys (deallocates and sets to C) C. Returns C. + +=item C + +Destroys (deallocates and sets to C) the I object pointer pointed +to by C. Returns C. This function is exposed as an implementation +side effect. Don't call it directly. Call I instead. + +=cut + +*/ + +void *msg_destroy_fn(Msg **msg) +{ + if (msg && *msg) + { + msg_release(*msg); + *msg = NULL; + } + + return NULL; +} + +/* + +=item C + +Sends a message to C. C is a I-like format string. +C is processed as in I. + +=cut + +*/ + +void vmsg_out(Msg *dst, const char *fmt, va_list args) +{ + if (dst && dst->out) + { + char msg[MSG_SIZE]; + vsnprintf(msg, MSG_SIZE, fmt, args); + dst->out(dst->data, msg, strlen(msg)); + } +} + +/* + +=item C + +Sends a message to C. C is a I-like format string. Any +remaining arguments are processed as in I. + +=cut + +*/ + +void msg_out(Msg *dst, const char *fmt, ...) +{ + if (dst && dst->out) + { + va_list args; + va_start(args, fmt); + vmsg_out(dst, fmt, args); + va_end(args); + } +} + +/* + +C + +Creates and initialises the internal data needed by a I object that +sends messages to file descriptor C. On success, returns the data. On +error, returns C. + +*/ + +static MsgFDData *msg_fddata_create(int fd) +{ + MsgFDData *data; + + if (!(data = mem_new(MsgFDData))) + return NULL; + + *data = fd; + + return data; +} + +/* + +C + +Releases (deallocates) the internal data needed by a I object that +sends messages to a file descriptor. The file descriptor is not closed. + +*/ + +static void msg_fddata_release(MsgFDData *data) +{ + mem_release(data); +} + +/* + +C + +Sends a message to a file descriptor. C is a pointer to the file +descriptor. C is the message. C is it's length. + +*/ + +static void msg_out_fd(void *data, const void *msg, size_t msglen) +{ + if (data && msg) + write(*(MsgFDData *)data, msg, msglen); +} + +/* + +=item C + +Creates a I object that sends messages to file descriptor C. +On success, returns the new I object. On error, returns C. + +=cut + +*/ + +Msg *msg_create_fd(int fd) +{ + MsgFDData *data; + Msg *msg; + + if (!(data = msg_fddata_create(fd))) + return NULL; + + if (!(msg = msg_create(msg_out_fd, data, (msg_destroy_t *)msg_fddata_release))) + { + msg_fddata_release(data); + return NULL; + } + + return msg; +} + +/* + +=item C + +Creates a I object that sends messages to standard error. On success, +returns the new I object. On error, returns C. + +=cut + +*/ + +Msg *msg_create_stderr(void) +{ + return msg_create_fd(STDERR_FILENO); +} + +/* + +=item C + +Creates a I object that sends messages to standard output. On success, +returns the new I object. On error, returns C. + +=cut + +*/ + +Msg *msg_create_stdout(void) +{ + return msg_create_fd(STDOUT_FILENO); +} + +/* + +C + +Initialises the internal data needed by a I object that sends messages +to the file specified by C. This data consists of a copy of C +and an open file descriptor to the file. The file descriptor is opened with +the C, C and C flags. On success, returns 0. On +error, returns -1. + +*/ + +static int msg_filedata_init(MsgFileData *data, const char *path) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + if (!data || !path) + return -1; + + if (!(data->path = mem_strdup(path))) + return -1; + + if ((data->fd = open(data->path, O_WRONLY | O_CREAT | O_APPEND, mode)) == -1) + return -1; + + return 0; +} + +/* + +C + +Creates the internal data needed by a I object that sends messages to +the file specified by C. On success, returns the data. On error, +returns C. + +*/ + +static MsgFileData *msg_filedata_create(const char *path) +{ + MsgFileData *data; + + if (!(data = mem_new(MsgFileData))) + return NULL; + + if (msg_filedata_init(data, path) == -1) + { + mem_release(data); + return NULL; + } + + return data; +} + +/* + +C + +Releases (deallocates) the internal data needed by a I object that +sends messages to a file. The file descriptor is closed first. + +*/ + +static void msg_filedata_release(MsgFileData *data) +{ + if (!data) + return; + + if (data->fd != -1) + close(data->fd); + + mem_release(data->path); + mem_release(data); +} + +/* + +=item C + +Sets the I format string used when sending messages to a file. +By default, it is C<"%Y%m%d %H:%M:%S">. On success, returns the previous +format string. On error (i.e. C is C), returns C. + +=cut + +*/ + +static const char *timestamp_format = "%Y%m%d %H:%M:%S"; +const char *msg_set_timestamp_format(const char *format) +{ + if (format) + { + const char *save = timestamp_format; + timestamp_format = format; + return save; + } + + return NULL; +} + +/* + +C + +Sends a message to a file. C contains the file descriptor. C is +the message. C is it's length. + +*/ + +static void msg_out_file(void *data, const void *msg, size_t msglen) +{ + MsgFileData *dst = data; + char buf[MSG_SIZE]; + size_t buflen; + + time_t t = time(NULL); + strftime(buf, MSG_SIZE, timestamp_format, localtime(&t)); + buflen = strlen(buf); + buf[buflen++] = ' '; + if (buflen + msglen >= MSG_SIZE) + msglen -= MSG_SIZE - buflen; + memmove(buf + buflen, msg, msglen); + + if (msg && dst && dst->fd != -1) + write(dst->fd, buf, buflen + msglen); +} + +/* + +=item C + +Creates a I object that sends messages to the file specified by +C. On success, returns the new I object. On error, returns +C. + +=cut + +*/ + +Msg *msg_create_file(const char *path) +{ + MsgFileData *data; + Msg *msg; + + if (!(data = msg_filedata_create(path))) + return NULL; + + if (!(msg = msg_create(msg_out_file, data, (msg_destroy_t *)msg_filedata_release))) + { + msg_filedata_release(data); + return NULL; + } + + return msg; +} + +/* + +C + +Initialises the internal data needed by a I object that sends messages +to I. I is called with C and C