diff options
| author | John Vogel <jvogel4@stny.rr.com> | 2024-07-21 16:30:48 -0400 |
|---|---|---|
| committer | John Vogel <jvogel4@stny.rr.com> | 2024-07-21 16:30:48 -0400 |
| commit | d161261b35b5ab2ba561a21dbd9420f3d2496895 (patch) | |
| tree | 9dd1cbe7053723532873c1dff9ff217dd6baca24 | |
| parent | a7dfcf130cdeeba7fee0a1ff6bf44bd746767bdf (diff) | |
| download | my-aports-d161261b35b5ab2ba561a21dbd9420f3d2496895.tar.gz | |
local/surveil: new local aport
| -rw-r--r-- | surveil/APKBUILD | 77 | ||||
| -rw-r--r-- | surveil/surveil/LICENSE | 22 | ||||
| -rw-r--r-- | surveil/surveil/Makefile | 55 | ||||
| -rw-r--r-- | surveil/surveil/NOTES | 60 | ||||
| -rw-r--r-- | surveil/surveil/README.md | 39 | ||||
| -rw-r--r-- | surveil/surveil/buf.c | 63 | ||||
| -rw-r--r-- | surveil/surveil/buf.h | 22 | ||||
| -rw-r--r-- | surveil/surveil/common.h | 13 | ||||
| -rw-r--r-- | surveil/surveil/config.c | 300 | ||||
| -rw-r--r-- | surveil/surveil/config.h | 28 | ||||
| -rw-r--r-- | surveil/surveil/db.c | 368 | ||||
| -rw-r--r-- | surveil/surveil/db.h | 19 | ||||
| -rw-r--r-- | surveil/surveil/dgst.c | 118 | ||||
| -rw-r--r-- | surveil/surveil/dgst.h | 13 | ||||
| -rw-r--r-- | surveil/surveil/diff.c | 55 | ||||
| -rw-r--r-- | surveil/surveil/diff.h | 8 | ||||
| -rw-r--r-- | surveil/surveil/example.conf | 22 | ||||
| -rw-r--r-- | surveil/surveil/job.c | 97 | ||||
| -rw-r--r-- | surveil/surveil/job.h | 25 | ||||
| -rw-r--r-- | surveil/surveil/main.c | 489 | ||||
| -rw-r--r-- | surveil/surveil/net.c | 107 | ||||
| -rw-r--r-- | surveil/surveil/net.h | 11 | ||||
| -rw-r--r-- | surveil/surveil/search.c | 241 | ||||
| -rw-r--r-- | surveil/surveil/search.h | 26 | ||||
| -rw-r--r-- | surveil/surveil/surveil.1 | 82 | ||||
| -rw-r--r-- | surveil/surveil/util.c | 219 | ||||
| -rw-r--r-- | surveil/surveil/util.h | 18 |
27 files changed, 2597 insertions, 0 deletions
diff --git a/surveil/APKBUILD b/surveil/APKBUILD new file mode 100644 index 0000000..46a1804 --- /dev/null +++ b/surveil/APKBUILD @@ -0,0 +1,77 @@ +# Contributor: John Vogel <jvogel4@stny.rr.com> +# Maintainer: John Vogel <jvogel4@stny.rr.com> +pkgname=surveil +pkgver=0.1 +pkgrel=2 +pkgdesc="trach changes to remote files" +url="http://localhost/home/john/Code/surveil" +arch="all" +license="MIT" +makedepends="curl-dev openssl-dev inih-dev xdiff-dev sqlite-dev libqueue-dev" +subpackages="$pkgname-doc" +source="surveil/LICENSE + surveil/Makefile + surveil/NOTES + surveil/README.md + surveil/buf.c + surveil/buf.h + surveil/common.h + surveil/config.c + surveil/config.h + surveil/db.c + surveil/db.h + surveil/dgst.c + surveil/dgst.h + surveil/diff.c + surveil/diff.h + surveil/example.conf + surveil/job.c + surveil/job.h + surveil/main.c + surveil/net.c + surveil/net.h + surveil/search.c + surveil/search.h + surveil/surveil.1 + surveil/util.c + surveil/util.h" +options="!check" + +builddir="$srcdir/" + +build() { + make +} + +package() { + make DESTDIR="$pkgdir" PREFIX=/usr install +} + +sha512sums=" +9aa08e37eca237a54b10ac6a78a8e496641b7fdfa6a220e9d59f1db061cdf13165619ea4cda129e8d97afd9dfa4630f9d923df1c70a7f7a7e65dd7420b1329f0 LICENSE +d2ea7eb831f22576fe7e81c6dea3e7d2c9e9fad1dae63f15926142aaa56ba26f6c55c8cf58edb3cd5be1641ae12fb38fdf0fdfc52ff419e16b0dac74fe8bb064 Makefile +a061db311e7ba7fcb29659eb584b0b530bdd69324966036f28046c2931ee63f23a07ed0e8c06b0aa6ff8619134e11f8ed4ba31a6d5717a6d67a53498651166bc NOTES +f6767772a8d024c156d2f5b6baf635c159ea2939ecdc25e1d4d55189d22f0bd00da6a0ab6ad7f02f38d01bbb5c564d7db743fcb81a8b4a1dd413cf21986b91f3 README.md +527f4f820488574bae8ab81ce3df11a95f3dc419ab6eef6d565d0a817ea2257f98c6b3152ded22521059b9999cf71f892dddc5d8f05180bd087aaa55168b3417 buf.c +999a896fa4c896414c21cd91bb9f954e45c71aed025e8d44df0c40914b839320b88dc6da9437818509c9145f0c5dc2034c9180c0cbb370f2ab5e17ece5d90b14 buf.h +e96e532a270e16962e3dd01df2ebb8347e06168e977f8f8a97bd480e31f7468daca8308ea7a675a44b4c3199bf658601272c5790fa97a0a7fa73995631dd4a97 common.h +1c1906e1e11834ad4c6ad1bbd3fd4778352915c7f361678f72cd805924b238c9f75862aa2d9d86f0fb2fad959158aa87a0da83ef40b2d43f15a2942abedabcab config.c +ac91e67d546dc8a2b66aae1a216bc271813c67a33a50eb3978e00834ef9ffd50afb16d88edbd61c1598a9484438f54f4a2bdbc73e444c5bd441f4c889d29172c config.h +c5a3819658879ea7a7d146c79615fc8f9925a8a90efe6946434b75a215fae84e7349fab751aa22521c03548909a810b23cd2cbfc12455c634eb3a5235d9175dc db.c +e2549a6caa1ec65f1753f44dfdfe4dd0c7baa5987be2ea13c1235b3f2388a79d5e89fda450a074120d5cd8d9cde596a89bb308c3f927ba08aab63cf357b7b140 db.h +e8b344ea99ac5c9e47354f11e71ab0e0a65fc7df7cca9d4bf103f7801a0fe2e485833e53d2b6e1d1f4b799ab7c3b993fac3de0688ac4c389ac1ea2f584216b4b dgst.c +bae7269e443ffa471d23079ad7ba206256ab71053f678ee89353e8bfa05ffbae1d7b964aa39eb5ba5eeb6e0fdf0d53fb9010b4315ade7491ee319adb1026dc34 dgst.h +5c849781dcd51b78196406bcb7fa4067082ac1e9f7c62d0f812796dbc1aaee2837ab1f2c937673d72df7c9a121bc4741de12ec3351e74ffab93b13f09edc4834 diff.c +4b745cb067b7a3e9215427a2eef4fcf617ec9f9939650921f16f33dcaa2b6ac7cf921cbc67922737d34e4c1bf7c21366fc06152fc10558618eb74d1278d867ce diff.h +5216eb767b1cd2b65923905f28a93637049e712055badcfc8cb999908e8d748d70284c9ebe67aa52b040d557544f44a83b52c108b926ae7945fc12a35621194d example.conf +39938dfd96c72993d8bddbc9a6d685ee18af1df61dac6039448630aa05283b63b14352594972c2ea8e96553a135234b01fe1334702ecada306bce25a717b27d6 job.c +4ea25eb9fb63df997cc3be98cd7ef0b0f2b9750504731566f39210ba7062877cd53842aea78f7c4fb418eef841f19081b50d287495d4012d80f1fe343c04fd83 job.h +3d3c7fd628b5a31de5f7bf123e3826c5e842d455e1f2cb4333540a2a9737b729d8b6210bd9cd70d31504461ecc41218d2fb9d376854961fb648e55fb24ce9fae main.c +0a61764ac07548b3099ad93dfb161c0482a87a28df75ed0f8f89ef839414bffb775a17d6f8b215cb03bdefa1f5de6d1f963fdd61ad677f40388324030aca88bb net.c +d5b2accdbf041d92f7b1874423d205a0cb9bdfcbf6a8dbbff99bd00d542ce318f5566e3634728b5b4ecedb0534be043f6a121adf4dd0c44fc5bac7cc6a34552e net.h +8378702b8fc77a40091c6677ee001f6a8453e6a311f37422aa8342002adce4251942d60274b3cb1da048b3ded3bfa9266f3cfb6abf503b0a72bf72672a7ff4a0 search.c +795b356d875804b39576b99c155f4fa2a954ec7c8281029e6f2f43a88b3ab6535c50bee85d0105fa30cd08329286addc11a8207ee7d6450331d2ba952b5b949d search.h +c642b266fd3300f832cc597b02875c28ac9fb37fbc3975206b1ba665dbb64a3f8e15663d6299553b4915193c6ade418e346503f7499c7371be994c9cbd6a943e surveil.1 +a7227dc9a91339bbf7aeed27f335f9d4cc6b277c07f7d29fe81c1942513b5d9bcb22f7c2ba6050d2504728ca8ad23392bdc9957a4f3412a0478b79b89966ded2 util.c +29006ed3c8c709ba66aa1a3b2fda403f711e58e8709d2b0d73820367f6bd9d80d23ed4250d75813241c35fd5bf7dbecb89f07a79f4fc68c131d290db7de5f994 util.h +" diff --git a/surveil/surveil/LICENSE b/surveil/surveil/LICENSE new file mode 100644 index 0000000..2e52187 --- /dev/null +++ b/surveil/surveil/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2023, John Vogel +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/surveil/surveil/Makefile b/surveil/surveil/Makefile new file mode 100644 index 0000000..8212189 --- /dev/null +++ b/surveil/surveil/Makefile @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: BSD-2-Clause +# Copyright (C) 2023 John Vogel + +CC ?= gcc +CFLAGS += -fPIE -std=c11 -fanalyzer -Wpedantic -Wall -Wextra -fstack-protector -D_POSIX_SOURCE +LDFLAGS += -pie -Wl,-z,noexecstack +LIBS = -linih -lcurl -lcrypto -lsqlite3 -lxdiff -lqueue +INSTALL = install +INSTALL_BIN = $(INSTALL) -m 755 +INSTALL_DATA = $(INSTALL) -m 644 +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin +MANDIR = $(PREFIX)/share/man +DOCDIR = $(PREFIX)/share/doc/surveil + +BIN = surveil +SRCS = buf.c config.c db.c dgst.c diff.c job.c main.c net.c search.c util.c +HDRS = buf.h common.h config.h db.h dgst.h diff.h job.h net.h search.h util.h +OBJS = $(SRCS:.c=.o) + +ifdef DEBUG + CFLAGS += -O0 -g -ggdb -gdwarf-4 +else + CFLAGS += -O2 +endif + +.PHONY: all clean + +.c.o: + $(CC) -c $(CFLAGS) $< -o $@ + +all: $(BIN) + +$(BIN): $(OBJS) + $(CC) -o $(BIN) $(OBJS) $(LDFLAGS) $(LIBS) + +$(OBJS): $(HDRS) + +clean: + $(RM) $(BIN) $(OBJS) + +install: $(BIN) + mkdir -p -m 755 $(DESTDIR)$(BINDIR) + mkdir -p -m 755 $(DESTDIR)$(MANDIR) + mkdir -p -m 755 $(DESTDIR)$(DOCDIR) + $(INSTALL_BIN) $(BIN) $(DESTDIR)$(BINDIR) + $(INSTALL_DATA) -D -t $(DESTDIR)$(MANDIR)/man1 surveil.1 + $(INSTALL_DATA) -D -t $(DESTDIR)$(DOCDIR) README.md NOTES LICENSE example.conf + +uninstall: + rm -f $(DESTDIR)$(BINDIR)/$(BIN) + rm -f $(DESTDIR)$(MANDIR)/man1/surveil.1 + rm -f $(DESTDIR)$(DOCDIR)/README.md + rm -f $(DESTDIR)$(DOCDIR)/NOTES + rm -f $(DESTDIR)$(DOCDIR)/LICENSE diff --git a/surveil/surveil/NOTES b/surveil/surveil/NOTES new file mode 100644 index 0000000..b4cf521 --- /dev/null +++ b/surveil/surveil/NOTES @@ -0,0 +1,60 @@ +Handle unused variables more gracefully. + +Program options: +-h + prints help/usage message and exit + +-p + prints digest providers available + +-q + turn on quiet mode + +-t + test configuration and exit + +-v + turn on verbose mode + +-c file + sets a specific configuration file + defaults to stdout for data + overrides configuration directory, ignored and mutually exclusive + if -d, data to that file + if -D, data to that directory at $conf.dat + +-d file + sets a specific data file + defaults to configuration from $config/surveil.conf + or if -C dir, then $dir/surveil.dat + of if -c file, then $file.dat + override data directory, ignored and mutually exclusive + +-C dir + sets configuration directory + defaults to $XDG_CONFIG_HOME/$progname or $HOME/.$progname + main configuration defaults to $dir/$progname.conf + +-D dir + set data directory + defaults to $XDG_DATA_HOME/$progname or $HOME/.$progname + main data file defaults to $dir/$progname.dat + +With no command line options to change the configuration/data directories/files, +then defaults are (searched in order listed: + +configuration: + $XDG_CONFIG_HOME/$progname + $HOME/.$progname + +data: + $XDG_DATA_HOME/$progname + $HOME/.$progname + +-P prunetype + set pruning and method, for clearing out database entries + + without prune set in configuration file, defaults to none (keep all) + STALE: only prune db entries that don't exist in config file + PURGE: purge all db entries from db (empty) + SPECIFIC: prune db entry for command line specified jobs diff --git a/surveil/surveil/README.md b/surveil/surveil/README.md new file mode 100644 index 0000000..71676de --- /dev/null +++ b/surveil/surveil/README.md @@ -0,0 +1,39 @@ +Surveil is used to monitor changes to web pages. +It can track whole content from a web page or just portions of it. +It was initially inspired by the Crux Linux utility, ck4up. + +Surveil depends on: + + * [libcurl](https://curl.se/libcurl/) + * [inih](https://github.com/benhoyt/inih) + * [libcrypto](https://www.openssl.org/) + * [libqueue](https://git.stygian.me/libqueue/) + * [libxdiff](https://git.stygian.me/libxdiff/) + +Configuration is done using INI syntax, ex: + + prune=off + quiet=off + verbose=off + [job_name] + url=https://www.example.com/ + pat=progname-([[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+)\.tar\.gz + rpl=\1 + dig=sha1 + +Global options: + +prune - whether to clean out stale db entries +quiet - silent mode +verbose - output more + +The portion in square brackets is the job name, commonly called the section in INI sytax. + +url is the web page to monitor. + +pat is regular expression (regex(7)) pattern to search to page with (optional). + +rpl is the replace string (regex(7)) to use (sed style s/pat/rpl/, option). + +dig is the digest type to use for stored data. + diff --git a/surveil/surveil/buf.c b/surveil/surveil/buf.c new file mode 100644 index 0000000..cc04e70 --- /dev/null +++ b/surveil/surveil/buf.c @@ -0,0 +1,63 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <stdio.h> +#include <string.h> + +#include "buf.h" +#include "util.h" + +int buf_init(struct buf *buf, size_t chunksize) +{ + size_t sz; + + sz = chunksize ? ((chunksize / CHNKSZ) + 1) * CHNKSZ : CHNKSZ; + + buf->ptr = xmalloc(sz); + buf->len = 0; + buf->size = buf->chunksize = sz; + + return 0; +} + +int buf_space(struct buf *buf, size_t more) +{ + char *s; + size_t add; + + if (!buf->ptr && buf_init(buf, more) == -1) + return -1; + + if (buf->size - buf->len <= more) { + add = ((more / buf->chunksize) + 1) * buf->chunksize; + s = xrealloc(buf->ptr, buf->size + add); + buf->ptr = s; + buf->size += add; + } + + return 0; +} + +int buf_addchr(struct buf *buf, int c) +{ + if (buf_space(buf, 1) == -1) + return -1; + else + buf->ptr[buf->len++] = c; + + if (c == 0) { + buf->len--; + buf->ptr[buf->size - 1] = c; + } + return 0; +} + +int buf_addstr(struct buf *buf, const char *str, size_t len) +{ + if (buf_space(buf, len) == 0) + memcpy(buf->ptr+buf->len, str, len); + else + return -1; + + buf->len += len; + + return 0; +} diff --git a/surveil/surveil/buf.h b/surveil/surveil/buf.h new file mode 100644 index 0000000..42eed19 --- /dev/null +++ b/surveil/surveil/buf.h @@ -0,0 +1,22 @@ +/* vim: set ts=4 sw=4 sts=4: */ + +#ifndef SURVEIL_BUF_H +#define SURVEIL_BUF_H + +#ifndef CHNKSZ +#define CHNKSZ 512 +#endif + +struct buf { + char *ptr; + size_t len; + size_t size; + size_t chunksize; +}; + +int buf_init(struct buf *, size_t); +int buf_space(struct buf *, size_t); +int buf_addchr(struct buf *, int); +int buf_addstr(struct buf *, const char *, size_t); + +#endif/*!SURVEIL_BUF_H*/ diff --git a/surveil/surveil/common.h b/surveil/surveil/common.h new file mode 100644 index 0000000..0730c26 --- /dev/null +++ b/surveil/surveil/common.h @@ -0,0 +1,13 @@ +/* vim: set ts=4 sw=4 ai: */ +#ifndef SURVEIL_COMMON_H +#define SURVEIL_COMMON_H + +#include <queue/tq.h> +#include "config.h" + +struct context { + struct tqh *jobs; + struct config *cfg; +}; + +#endif/*!SURVEIL_COMMON_H*/ diff --git a/surveil/surveil/config.c b/surveil/surveil/config.c new file mode 100644 index 0000000..d42e69a --- /dev/null +++ b/surveil/surveil/config.c @@ -0,0 +1,300 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <sys/stat.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <limits.h> +#include <pwd.h> +#include <errno.h> +#include <err.h> + +#include "util.h" +#include "config.h" + +static char *getuserhome(void) +{ + char *env; + struct passwd *pw; + uid_t euid; + + if ((env = getenv("HOME")) != NULL) + return env; + + euid = geteuid(); + errno = 0; + if ((pw = getpwuid(euid)) != NULL) + return pw->pw_dir; + + warn("getpwuid(%u)", euid); + + return NULL; +} + +static int config_path(char *dst, size_t n, char *env, char *home, char *dfl, char *fb, char *sub) +{ + char tbuf[PATH_MAX]; + int l, r; + struct stat st; + + /* fortify complains tbuf may be used uninitialized without this */ + memset(tbuf, 0, sizeof(tbuf)); + + l = snprintf(tbuf, sizeof(tbuf), "%s%s%s", + env ? "" : home, + env ? "" : "/", + env ? env : dfl); + if ((unsigned long)l >= sizeof(tbuf)) { + errno = ENAMETOOLONG; + return -1; + } + + r = stat(tbuf, &st) == 0 && S_ISDIR(st.st_mode); + + l = snprintf(dst, n, "%s/%s", r ? tbuf : home, r ? sub : fb); + if ((unsigned long)l >= n ) { + errno = ENAMETOOLONG; + return -1; + } + + if (stat(dst, &st) == -1) + errno = ENOENT; + else if (!S_ISDIR(st.st_mode)) + errno = ENOTDIR; + else + return 0; + + return -1; +} + +static int get_value(const char *value, unsigned int *ret) +{ + unsigned int val; + + if (!ret) + return -1; + + if (s2u(value, &val) == 0) + *ret = val ? 1 : 0; + else if (strcasecmp(value, "on") == 0 || + strcasecmp(value, "yes") == 0) + *ret = 1; + else if (strcasecmp(value, "off") == 0 || + strcasecmp(value, "no") == 0) + *ret = 0; + else + return -1; + + return 0; +} + +int config_global(struct config *cfg, const char *name, const char *value) +{ + unsigned int val; + + if (strcasecmp(name, "prune") == 0) { + if (get_value(value, &val) != 0) { + warnx("unrecognized value for prune setting: '%s'", value); + return -1; + } + cfg->prune = val; + } + else if (strcasecmp(name, "quiet") == 0) { + if (get_value(value, &val) != 0) { + warnx("unrecoggized value for quiet setting: '%s'", value); + return -1; + } + cfg->quiet = val; + } + else if (strcasecmp(name, "verbose") == 0) { + if (get_value(value, &val) != 0) { + warnx("unrecoggized value for verbose setting: '%s'", value); + return -1; + } + cfg->verbose = val; + } + else { + warnx("unrecognized config setting: '%s'", name); + return -1; + } + + + return 0; +} + +struct config *config(char *cfgd, char *cfgf, char *datd, char *datf) +{ + int l; + size_t len; + char dir[PATH_MAX], file[PATH_MAX]; + char *home, *configfile, *datafile; + struct stat st; + struct config *cfg; + + cfg = NULL; + configfile = datafile = NULL; + + home = getuserhome(); + if (!home) { + warnx("unable to determine home directory"); + return NULL; + } + + if (cfgf) { + len = strlen(cfgf); + if (len >= PATH_MAX) { + errno = ENAMETOOLONG; + warn("%s", cfgf); + return NULL; + } + + if (stat(cfgf, &st) != 0) { + warn("config: %s", cfgf); + return NULL; + } + + configfile = xstrdup(cfgf); + } + else { + if (cfgd) { + len = strlen(cfgd); + if (len >= sizeof(dir)) { + errno = ENAMETOOLONG; + warn("%s", cfgd); + return NULL; + } + + if (stat(cfgd, &st) != 0) { + warn("config: %s", cfgd); + return NULL; + } + else if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + warn("config: %s", cfgd); + return NULL; + } + + memcpy(dir, cfgd, len+1); + } + else if (config_path(dir, sizeof(dir), getenv("XDG_CONFIG_HOME"), + home, CONFIG_HOME_DEFAULT, CONFIG_HOME_FALLBACK, "surveil") == -1) { + if (errno == ENAMETOOLONG) + warn("config directory path"); + else + warn("config directory path (%s)", dir); + + return NULL; + } + + l = snprintf(file, sizeof(file), "%s/%s", dir, CONFIG_FILE_DEFAULT); + if ((unsigned long)l > sizeof(file)) { + errno = ENAMETOOLONG; + warn("config file path"); + return NULL; + } + + if (stat(file, &st) != 0) { + warn("config: %s", file); + return NULL; + } + + configfile = xstrdup(file); + } + + if (datf) { + len = strlen(datf); + if (len >= PATH_MAX) { + errno = ENAMETOOLONG; + warn("%s", datf); + goto config_fail; + } + + datafile = xstrdup(datf); + } + else { + char name[PATH_MAX]; + char *tname, *dot; + + if (datd) { + len = strlen(datd); + if (len >= sizeof(dir)) { + errno = ENAMETOOLONG; + warn("%s", datd); + goto config_fail; + } + memcpy(dir, datd, len+1); + } + else if (config_path(dir, sizeof(dir), getenv("XDG_DATA_HOME"), + home, DATA_HOME_DEFAULT, DATA_HOME_FALLBACK, "surveil") == -1) { + if (errno == ENAMETOOLONG) { + warn("data directory path"); + goto config_fail; + } + warn("%s", dir); + } + + tname = cfgf ? cfgf : DATA_FILE_DEFAULT; + memcpy(name, tname, strlen(tname)+1); + tname = strrchr(name, '/'); + if (tname) + tname++; + else + tname = name; + dot = strrchr(tname, '.'); + if (dot && dot != tname) + *dot = '\0'; + + l = snprintf(file, sizeof(file), "%s/%s.db", dir, tname); + if ((unsigned long)l > sizeof(file)) { + errno = ENAMETOOLONG; + warn("%s", file); + goto config_fail; + } + + if (stat(dir, &st) == -1) + warn("config: %s", dir); + else if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + warn("config: %s", dir); + goto config_fail; + } + + datafile = xstrdup(file); + } + + cfg = xmalloc(sizeof(struct config)); + cfg->prune = 0; + cfg->quiet = 0; + cfg->verbose = 0; + cfg->configfile = configfile; + cfg->datafile = datafile; + +config_fail: + if (!cfg) { + free(configfile); + free(datafile); + } + + return cfg; +} + +void config_cleanup(struct config *cfg) +{ + if (cfg->nspecified) + free(cfg->specified); + free(cfg->configfile); + free(cfg->datafile); + free(cfg); +} + +int name_is_specified(struct config *cfg, const char *name) +{ + size_t i; + + for (i = 0; i < cfg->nspecified; i++) + if (strcmp(cfg->specified[i], name) == 0) + return 1; + + return 0; +} diff --git a/surveil/surveil/config.h b/surveil/surveil/config.h new file mode 100644 index 0000000..3a1935d --- /dev/null +++ b/surveil/surveil/config.h @@ -0,0 +1,28 @@ +/* vim: set ts=4 sw=4: */ +#ifndef SURVEIL_CONFIG_H +#define SURVEIL_CONFIG_H + +#define CONFIG_HOME_DEFAULT ".config" +#define DATA_HOME_DEFAULT ".local/share" +#define CONFIG_HOME_FALLBACK ".surveil" +#define DATA_HOME_FALLBACK ".surveil" +#define CONFIG_FILE_DEFAULT "surveil.conf" +#define DATA_FILE_DEFAULT "surveil.db" + +struct config { + unsigned int prune; + unsigned int quiet; + unsigned int verbose; + unsigned int test; + char *configfile; + char *datafile; + char **specified; + size_t nspecified; +}; + +int config_global(struct config *, const char *, const char *); +struct config *config(char *, char *, char *, char *); +int name_is_specified(struct config *, const char *); +void config_cleanup(struct config *); + +#endif/*!SURVEIL_CONFIG_H*/ diff --git a/surveil/surveil/db.c b/surveil/surveil/db.c new file mode 100644 index 0000000..2a337f1 --- /dev/null +++ b/surveil/surveil/db.c @@ -0,0 +1,368 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <time.h> +#include <err.h> +#include <sqlite3.h> +#include <queue/tq.h> + +#include "db.h" +#include "job.h" +#include "util.h" + +#define TABLE_NAME "data" + +#define CREATE_TABLE "CREATE TABLE IF NOT EXISTS " TABLE_NAME \ + " (name TEXT PRIMARY KEY, url TEXT, date INTEGER, buf BLOB);" + +#define INSERT "INSERT INTO " TABLE_NAME " (name, url, date, buf)" \ + " VALUES (?,?,?,?)"\ + " ON CONFLICT(name) DO UPDATE" \ + " SET url = excluded.url, date = excluded.date, buf = excluded.buf;" + +#define UPDATE "UPDATE " TABLE_NAME " SET url = ?, date = ?, buf = ?" \ + " WHERE name = ?;" + +#define DELETE "DELETE FROM " TABLE_NAME " WHERE name = ?;" + +#define FIND "SELECT url,date,buf FROM " TABLE_NAME " WHERE name = ?;" + +#define FIND_ALL "SELECT name,url,date,buf FROM " TABLE_NAME ";" + +static sqlite3 *db; + +int db_init(const char *path) +{ + int res, ret = -1; + sqlite3_stmt *stmt = NULL; + const char *leftover = NULL; + const char *cmd = CREATE_TABLE; + + if (sqlite3_open(path, &db) != SQLITE_OK) { + warnx("%s: failed to open sqlite database %s: %s", + __func__, path, sqlite3_errmsg(db)); + goto db_init_fail; + } + + res = sqlite3_prepare_v2(db, cmd, -1, &stmt, &leftover); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_prepare_v2: %s", __func__, sqlite3_errmsg(db)); + goto db_init_fail; + } + if (leftover && leftover < cmd+strlen(cmd)) { + warnx("%s: unused text in SQL statement: %s in %s", + __func__, leftover, cmd); + goto db_init_fail; + } + + res = sqlite3_step(stmt); + if (res != SQLITE_DONE) { + warnx("%s: sqlite3_step returned %d, expected SQLITE_DONE(%d)", + __func__, res, SQLITE_DONE); + goto db_init_fail; + } + + ret = 0; + +db_init_fail: + sqlite3_finalize(stmt); + + if (ret == -1) + sqlite3_close(db); + + return ret; +} + +int db_insert(const struct job *j) +{ + int res, ret = -1; + sqlite3_stmt *stmt = NULL; + const char *leftover = NULL; + const char *cmd = INSERT; + + res = sqlite3_prepare_v2(db, cmd, -1, &stmt, &leftover); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_prepare_v2: %s", __func__, sqlite3_errmsg(db)); + return -1; + } + if (leftover && leftover < cmd+strlen(cmd)) { + warnx("%s: unused text in SQL statement: %s in %s", + __func__, leftover, cmd); + goto db_insert_fail; + } + res = sqlite3_bind_text(stmt, 1, j->name, strlen(j->name), SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(name,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_insert_fail; + } + res = sqlite3_bind_text(stmt, 2, j->url, strlen(j->url), SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(url,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_insert_fail; + } + res = sqlite3_bind_int64(stmt, 3, j->date); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_int64(date,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_insert_fail; + } + res = sqlite3_bind_blob(stmt, 4, j->buf, j->bufsz, SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(dtype,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_insert_fail; + } + + res = sqlite3_step(stmt); + if (res != SQLITE_DONE) { + warnx("sqlite3_step returned %d, expected SQLITE_DONE(%d)", + res, SQLITE_DONE); + goto db_insert_fail; + } + + ret = 0; + +db_insert_fail: + sqlite3_finalize(stmt); + + return ret; +} + +int db_update(const struct job *j) +{ + int res, ret = -1; + sqlite3_stmt *stmt = NULL; + const char *leftover = NULL; + const char *cmd = UPDATE; + + res = sqlite3_prepare_v2(db, cmd, -1, &stmt, &leftover); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_prepare_v2: %s", __func__, sqlite3_errmsg(db)); + return -1; + } + if (leftover && leftover < cmd+strlen(cmd)) { + warnx("%s: unused text in SQL statement: %s in %s", + __func__, leftover, cmd); + goto db_update_fail; + } + res = sqlite3_bind_text(stmt, 1, j->url, strlen(j->url), SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(url,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_update_fail; + } + res = sqlite3_bind_int64(stmt, 2, j->date); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_int64(date,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_update_fail; + } + res = sqlite3_bind_blob(stmt, 3, j->buf, j->bufsz, SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(buf,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_update_fail; + } + res = sqlite3_bind_text(stmt, 4, j->name, strlen(j->name), SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(name,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_update_fail; + } + + res = sqlite3_step(stmt); + if (res != SQLITE_DONE) { + warnx("%s: sqlite3_step returned %d, expected SQLITE_DONE(%d)", + __func__, res, SQLITE_DONE); + goto db_update_fail; + } + + ret = 0; + +db_update_fail: + sqlite3_finalize(stmt); + + return ret; +} + +int db_delete(const char *name) +{ + int res, ret = -1; + sqlite3_stmt *stmt = NULL; + const char *leftover = NULL; + const char *cmd = DELETE; + + res = sqlite3_prepare_v2(db, cmd, -1, &stmt, &leftover); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_prepare_v2: %s", __func__, sqlite3_errmsg(db)); + return -1; + } + if (leftover && leftover < cmd+strlen(cmd)) { + warnx("%s: unused text in SQL statement: %s in %s", __func__, leftover, cmd); + goto db_delete_fail; + } + res = sqlite3_bind_text(stmt, 1, name, strlen(name), SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(name,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_delete_fail; + } + + res = sqlite3_step(stmt); + if (res != SQLITE_DONE) { + warnx("%s: sqlite3_step returned %d, expected SQLITE_DONE(%d)", + __func__, res, SQLITE_DONE); + goto db_delete_fail; + } + + ret = 0; + +db_delete_fail: + sqlite3_finalize(stmt); + + return ret; +} + +int db_find(const char *name, struct job **retval) +{ + int res, ret = -1; + sqlite3_stmt *stmt = NULL; + const char *leftover = NULL; + const char *cmd = FIND; + int cnt; + struct job *r; + + res = sqlite3_prepare_v2(db, cmd, -1, &stmt, &leftover); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_prepare_v2: %s", __func__, sqlite3_errmsg(db)); + return -1; + } + if (leftover && leftover < cmd+strlen(cmd)) { + warnx("%s: unused text in SQL statement: %s in %s", __func__, leftover, cmd); + goto db_find_fail; + } + res = sqlite3_bind_text(stmt, 1, name, strlen(name), SQLITE_STATIC); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_bind_text(name,1) failed: %s", + __func__, sqlite3_errmsg(db)); + goto db_find_fail; + } + + res = sqlite3_step(stmt); + if (res != SQLITE_ROW) { + if (res == SQLITE_DONE) + ret = 0; + else + warnx("%s: sqlite3_step returned %d, expected SQLITE_DONE(%d) or SQLITE_ROW(%d)", + __func__, res, SQLITE_DONE, SQLITE_ROW); + goto db_find_fail; + } + + if ((cnt = sqlite3_column_count(stmt)) != 3) { + warnx("%s: sqlite3_column_count returned %d, expected 3", __func__, cnt); + goto db_find_fail; + } + + r = job_new(name); + r->url = xstrdup((const char *)sqlite3_column_text(stmt, 0)); + r->date = sqlite3_column_int64(stmt, 1); + r->bufsz = sqlite3_column_bytes(stmt, 2); + r->buf = xmalloc(r->bufsz); + memcpy(r->buf, sqlite3_column_blob(stmt, 2), r->bufsz); + + res = sqlite3_step(stmt); + if (res != SQLITE_DONE) { + job_free(r); + if (res == SQLITE_ROW) + warnx("%s: multiple rows returned, should have been one", __func__); + else + warnx("%s: sqlite3_step returned %d, expected SQLITE_DONE(%d)", + __func__, res, SQLITE_DONE); + goto db_find_fail; + } + + if (retval) + *retval = r; + + ret = 1; + +db_find_fail: + sqlite3_finalize(stmt); + + return ret; +} + +int db_find_all(struct tqh **retval) +{ + int res, ret = -1; + sqlite3_stmt *stmt = NULL; + const char *leftover = NULL; + const char *cmd = FIND_ALL; + int cnt; + struct tqh *r; + struct job *j; + + res = sqlite3_prepare_v2(db, cmd, -1, &stmt, &leftover); + if (res != SQLITE_OK) { + warnx("%s: sqlite3_prepare_v2: %s", __func__, sqlite3_errmsg(db)); + return -1; + } + if (leftover && leftover < cmd+strlen(cmd)) { + warnx("%s: unused text in SQL statement: %s in %s", __func__, leftover, cmd); + goto db_find_all_fail; + } + + r = tq_new(); + + while (1) { + res = sqlite3_step(stmt); + if (res != SQLITE_ROW) { + if (res == SQLITE_DONE) + break; + warnx("%s: sqlite3_step returned %d, expected SQLITE_DONE(%d) or SQLITE_ROW(%d)", + __func__, res, SQLITE_DONE, SQLITE_ROW); + goto db_find_all_fail; + } + + if ((cnt = sqlite3_column_count(stmt)) != 4) { + warnx("%s: sqlite3_column_count returned %d, expected 4", __func__, cnt); + tq_cleanup(r, job_free); + goto db_find_all_fail; + } + + j = job_new((const char *)sqlite3_column_text(stmt, 0)); + j->url = xstrdup((const char *)sqlite3_column_text(stmt, 1)); + j->date = sqlite3_column_int64(stmt, 2); + j->bufsz = sqlite3_column_bytes(stmt, 3); + j->buf = xmalloc(j->bufsz); + memcpy(j->buf, sqlite3_column_blob(stmt, 3), j->bufsz); + + tq_insert_tail(r, tq_elem_new(j)); + } + + if (tq_empty(r)) { + tq_cleanup(r, job_free); + free(r); + ret = 0; + } + else { + *retval = r; + ret = 1; + } + +db_find_all_fail: + sqlite3_finalize(stmt); + + return ret; +} + +void db_cleanup(void) +{ + if (sqlite3_close(db) != SQLITE_OK) + warnx("%s: sqlite3_close failed: %s", sqlite3_errmsg(db)); +} diff --git a/surveil/surveil/db.h b/surveil/surveil/db.h new file mode 100644 index 0000000..44f560a --- /dev/null +++ b/surveil/surveil/db.h @@ -0,0 +1,19 @@ +/* vim: set ts=4 sw=4 sts=4: */ + +#ifndef SURVEIL_DB_H +#define SURVEIL_DB_H + +#include <sqlite3.h> + +struct job; +struct tqh; + +int db_init(const char *); +int db_insert(const struct job *); +int db_update(const struct job *); +int db_delete(const char *); +int db_find(const char *, struct job **); +int db_find_all(struct tqh **); +void db_cleanup(void); + +#endif/*!SURVEIL_DB_H*/ diff --git a/surveil/surveil/dgst.c b/surveil/surveil/dgst.c new file mode 100644 index 0000000..72eb6fa --- /dev/null +++ b/surveil/surveil/dgst.c @@ -0,0 +1,118 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <stdlib.h> +#include <limits.h> +#include <stdio.h> +#include <sys/types.h> +#include <err.h> + +#include <openssl/evp.h> +#include <openssl/bio.h> +#include <openssl/err.h> + +#include "util.h" + +#define LONG_STR_SIZE (((sizeof(long)*CHAR_BIT)/3)+2) + +static const char xdigits[16] = { + "0123456789abcdef" +}; + +char *dgst2asc(unsigned char *dgst, size_t len) +{ + char *s; + size_t i; + int c; + + s = xmalloc((len * 2) + 1); + + for (i = 0; i < len; i++) { + c = xdigits[(dgst[i]&15)]; + s[(i*2)+1] = c ? c : '0'; + dgst[i] >>= 4; + c = xdigits[(dgst[i]&15)]; + s[(i*2)] = c ? c : '0'; + } + s[(i*2)] = '\0'; + + return s; +} + +unsigned char *calcdgst(const char *impl, char *buf, ssize_t sz, unsigned *rlen) +{ + unsigned int len = 0; + EVP_MD_CTX *ctx; + EVP_MD *digestimpl; + unsigned char *outdigest = NULL; + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + warnx("EVP_MD_CTX_new failed"); + goto calcdigest_fail0; + } + + digestimpl = EVP_MD_fetch(NULL, impl, NULL); + if (digestimpl == NULL) { + warnx("EVP_MD_fetch failed"); + goto calcdigest_fail1; + } + + if (!EVP_DigestInit_ex(ctx, digestimpl, NULL)) { + warnx("EVP_DigestInit_ex failed"); + goto calcdigest_fail; + } + + if (!EVP_DigestUpdate(ctx, buf, sz)) { + warnx("EVP_DigestUpdate failed"); + goto calcdigest_fail; + } + + outdigest = OPENSSL_malloc(EVP_MD_get_size(digestimpl)); + if (outdigest == NULL) { + warnx("OPENSSL_malloc failed"); + goto calcdigest_fail; + } + + if (!EVP_DigestFinal_ex(ctx, outdigest, &len)) { + warnx("EVP_DigestFinal_ex failed"); + goto calcdigest_fail; + } + +calcdigest_fail: + EVP_MD_free(digestimpl); +calcdigest_fail1: + EVP_MD_CTX_free(ctx); +calcdigest_fail0: + if (!outdigest) + ERR_print_errors_fp(stderr); + else + *rlen = len; + + return outdigest; +} + + +void print_digest(EVP_MD *md, void *unused __attribute__((unused))) +{ + int sz = EVP_MD_size(md); + + if (sz) + printf("%s (%d bit)\n", EVP_MD_get0_name(md), sz * 8); +} + +void print_digests(void) +{ + EVP_MD_do_all_provided(NULL, print_digest, NULL); +} + +int digest_is_provided(const char *digest) +{ + EVP_MD *md; + int ret = 0; + + if ((md = EVP_MD_fetch(NULL, digest, NULL)) != NULL) { + EVP_MD_free(md); + ret++; + } + + return ret; +} diff --git a/surveil/surveil/dgst.h b/surveil/surveil/dgst.h new file mode 100644 index 0000000..e262d1c --- /dev/null +++ b/surveil/surveil/dgst.h @@ -0,0 +1,13 @@ +/* vim: set ts=4 sw=4 sts=4: */ + +#ifndef SURVEIL_DGST_H +#define SURVEIL_DGST_H + +#define DEFAULT_DIGEST "SHA1" + +char *dgst2asc(unsigned char *, size_t); +unsigned char *calcdgst(const char *, char *, ssize_t, unsigned *); +void print_digests(void); +int digest_is_provided(const char *); + +#endif/*!SURVEIL_DGST_H*/ diff --git a/surveil/surveil/diff.c b/surveil/surveil/diff.c new file mode 100644 index 0000000..1d30600 --- /dev/null +++ b/surveil/surveil/diff.c @@ -0,0 +1,55 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <stdio.h> +#include <err.h> +#include <xdiff/xdiff.h> + +#include "buf.h" + +/*static int out_line(void *arg __attribute__((unused)), mmbuffer_t *mb, int nmb) +{ + int i; + + for (i = 0; i < nmb; i++) + printf("%.*s", (int)mb[i].size, mb[i].ptr); + + return 0; +}*/ + +static int store_line(void *arg, mmbuffer_t *mb, int nmb) +{ + struct buf *b = (struct buf *)arg; + int i; + + for (i = 0; i < nmb; i++) + if (buf_addstr(b, mb[i].ptr, mb[i].size) == -1) + return -1; + + if (buf_addchr(b, '\0') == -1) + return -1; + + return 0; +} + +char *get_diff(char *buf1, size_t buf1sz, char *buf2, size_t buf2sz) +{ + struct buf b; + mmfile_t mf1 = { .ptr = buf1, .size = buf1sz }; + mmfile_t mf2 = { .ptr = buf2, .size = buf2sz }; + xpparam_t param = { .flags = 0, + .ignore_regex = NULL, .ignore_regex_nr = 0, + .anchors = NULL, .anchors_nr = 0 }; + xdemitconf_t ecfg = { .ctxlen = 0, .interhunkctxlen = 0, + .flags = 0, + .find_func = NULL, .find_func_priv = NULL, + .hunk_func = NULL }; + xdemitcb_t ecb = { .priv = &b, .out_hunk = NULL, .out_line = store_line }; + + buf_init(&b, 1); + if (xdl_diff(&mf1, &mf2, ¶m, &ecfg, &ecb) == -1) { + warnx("xdl_diff failed"); + free(b.ptr); + return NULL; + } + + return b.ptr; +} diff --git a/surveil/surveil/diff.h b/surveil/surveil/diff.h new file mode 100644 index 0000000..a0e5207 --- /dev/null +++ b/surveil/surveil/diff.h @@ -0,0 +1,8 @@ +/* vim: set ts=4 sw=4 sts=4: */ + +#ifndef SURVEIL_DIFF_H +#define SURVEIL_DIFF_H + +char *get_diff(char *, size_t, char *, size_t); + +#endif/*!SURVEIL_DIFF_H*/ diff --git a/surveil/surveil/example.conf b/surveil/surveil/example.conf new file mode 100644 index 0000000..08a36a7 --- /dev/null +++ b/surveil/surveil/example.conf @@ -0,0 +1,22 @@ + +[traceroute] +url=https://sourceforge.net/projects/traceroute/rss?path=/traceroute +pat=traceroute-([[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*)\.tar\.gz +rpl=\1 +dig=md5 + +[ awk ] +url=https://github.com/onetrueawk/awk/commits/master.atom +pat=<id>.*Commit/([0-9a-fA-F]*)</id> +rpl=\1 +dig=md5 + +[ nsjail ] +url=https://github.com/google/nsjail/commits/master.atom +pat=<id>.*Commit/([0-9a-fA-F]*)</id> +rpl=\1 +dig=md5 + +[ linux ] +url=https://www.kernel.org +dig=sha1 diff --git a/surveil/surveil/job.c b/surveil/surveil/job.c new file mode 100644 index 0000000..f8eb80b --- /dev/null +++ b/surveil/surveil/job.c @@ -0,0 +1,97 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <sys/stat.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <err.h> +#include <queue/tq.h> + +#include "dgst.h" +#include "job.h" +#include "net.h" +#include "search.h" +#include "util.h" + +int job_is_name(const void *p, const void *s) +{ + const struct job *j; + const char *name; + + j = (const struct job *)p; + name = (const char *)s; + + if (!j || !j->name || !name) + return -1; + + return strcmp(j->name, name); +} + +struct job *job_new(const char *name) +{ + struct job *j; + + j = xmalloc(sizeof(struct job)); + j->name = xstrdup(name); + j->url = j->dtype = NULL; + j->date = 0; + j->npats = 0; + j->pats = j->rpls = NULL; + j->buf = NULL; + j->bufsz = 0; + + return j; +} + +void job_free(void *data) +{ + int i; + struct job*j = (struct job*)data; + + free(j->buf); + for (i = 0; i < j->npats; i++) { + free(j->pats[i]); + free(j->rpls[i]); + } + free(j->pats); + free(j->rpls); + free(j->dtype); + free(j->url); + free(j->name); + free(j); +} + +int run_job(struct job *j) +{ + ssize_t sz; + char *buf; + int i; + struct tqh *s; + + if ((sz = net_get_url(j->url, &buf)) == -1) + return -1; + + for (i = 0; i < j->npats; i++) { + s = search_text(buf, sz, j->pats[i], j->rpls[i]); + free(buf); + if (!s) + return -1; + + sz = get_matches(&buf, s); + if (sz == -1) + return -1; + + tq_cleanup(s, match_free); + free(s); + } + + j->date = time(NULL); + j->buf = buf; + j->bufsz = (size_t)sz; + + return 0; +} diff --git a/surveil/surveil/job.h b/surveil/surveil/job.h new file mode 100644 index 0000000..71e1776 --- /dev/null +++ b/surveil/surveil/job.h @@ -0,0 +1,25 @@ +/* vim: set ts=4 sw=4 sts=4: */ + +#ifndef SURVEIL_JOB_H +#define SURVEIL_JOB_H + +#include <time.h> + +struct job { + char *name; + char *url; + time_t date; + char *dtype; + int npats; + char **pats; + char **rpls; + char *buf; + size_t bufsz; +}; + +int job_is_name(const void *, const void *); +struct job *job_new(const char *); +void job_free(void *); +int run_job(struct job *); + +#endif/*!SURVEIL_JOB_H*/ diff --git a/surveil/surveil/main.c b/surveil/surveil/main.c new file mode 100644 index 0000000..33cc3c3 --- /dev/null +++ b/surveil/surveil/main.c @@ -0,0 +1,489 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <limits.h> +#include <sys/stat.h> +#include <time.h> +#include <errno.h> +#include <pwd.h> +#include <err.h> +#include <ini.h> +#include <queue/tq.h> + +#include "config.h" +#include "dgst.h" +#include "job.h" +#include "db.h" +#include "diff.h" +#include "net.h" +#include "search.h" +#include "util.h" +#include "common.h" + +char *progname; + +#if INI_HANDLER_LINENO +static int chandler(void *user, const char *section, + const char *name, const char *value, int lineno) +#else +static int chandler(void *user, const char *section, + const char *name, const char *value) +#endif +{ + struct context *ctx; + struct tqe *e; + struct job *j; + + ctx = (struct context *)user; + + if (!section || !*section) { + if (config_global(ctx->cfg, name, value) == -1) + return 0; + return 1; + } + + section = trimspace(section); + + if (ctx->cfg->nspecified && !name_is_specified(ctx->cfg, section)) + return 1; + + if (!(e = tq_find_from_data(ctx->jobs, section, job_is_name))) { + e = tq_elem_new(job_new(section)); + if (tq_empty(ctx->jobs)) + tq_insert_head(ctx->jobs, e); + else + tq_insert_tail(ctx->jobs, e); + } + + j = (struct job *)e->data; + + if (strcasecmp(name, "url") == 0 || + strcasecmp(name, "address") == 0) { + if (j->url) { + warnx("url for job '%s' redefined, was '%s', new '%s'", + j->name, j->url, value); + return 0; + } + j->url = xstrdup(value); + } + else if (strcasecmp(name, "dig") == 0 || + strcasecmp(name, "dtype") == 0 || + strcasecmp(name, "digest") == 0 || + strcasecmp(name, "digest_type") == 0) { + if (j->dtype) { + warnx("digest type for job '%s' redefined, was '%s', new '%s'", + j->name, j->dtype, value); + return 0; + } + j->dtype = xstrdup(value); + } + else if (strcasecmp(name, "pat") == 0 || + strcasecmp(name, "pat") == 0) { + j->pats = xrealloc(j->pats, sizeof(char *) * (j->npats+1)); + j->rpls = xrealloc(j->rpls, sizeof(char *) * (j->npats+1)); + j->pats[j->npats] = xstrdup(value); + j->rpls[j->npats] = NULL; + j->npats++; + } + else if (strcasecmp(name, "rpl") == 0 || + strcasecmp(name, "replace") == 0 || + strcasecmp(name, "replacement")== 0) { + if (!j->pats[j->npats-1]) { + warnx("job '%s': replacement pattern '%s' with no regex pattern", + j->name, value); + return 0; + } + if (j->rpls[j->npats-1]) { + warnx("job '%s': replacement pattern for regex pattern '%s' redefined, " + "was '%s', new '%s'", + j->name, value, j->pats[j->npats-1], j->rpls[j->npats-1], value); + return 0; + } + j->rpls[j->npats-1] = xstrdup(value); + } + else { + warnx("job %s: unrecognized key '%s' with value '%s'", + j->name, name, value); + return 0; + } + + return 1; +} + +int job_is_in_jobs(char *name, struct tqh *jobs) +{ + if (tq_find_from_data(jobs, name, job_is_name)) + return 1; + return 0; +} + +void prune_data(struct config *cfg, struct tqh *jobs, int stale) +{ + size_t i; + + if (cfg->nspecified > 0) { + for (i = 0; i < cfg->nspecified; i++) { + if (cfg->test) { + printf("test: prune %s\n", cfg->specified[i]); + continue; + } + if (db_delete(cfg->specified[i]) == -1) { + warnx("purging data for job '%s' failed", + cfg->specified[i]); + } + } + } + else { + struct tqh *indb; + struct tqe *e; + struct job *j; + int res; + + if ((res = db_find_all(&indb)) != 1) + return; + + tq_foreach(e, indb) { + j = e->data; + if (jobs && stale && job_is_in_jobs(j->name, jobs)) + continue; + if (cfg->test) { + printf("test: prune %s\n", j->name); + continue; + } + if (db_delete(j->name) == -1) { + warnx("pruning job '%s' data failed", + j->name); + } + } + + tq_cleanup(indb, job_free); + free(indb); + } +} + +int main(int argc, char *argv[]) +{ + struct context ctx = { 0 }; + struct config *cfg; + struct tqh *jobs; + struct tqe *e; + struct job *j, *prev; + unsigned char *dgst, *prevdgst; + unsigned dlen, prevdlen; + char *dgstasc, *prevdgstasc; + char lasttime[PATH_MAX], thistime[PATH_MAX]; + char *buf; + char *configdir, *configfile, *datadir, *datafile; + int eflag, hflag, pflag, Pflag, qflag, tflag, vflag; + int res, c, ret = EXIT_FAILURE; + size_t i; + + dgstasc = prevdgstasc = NULL; + eflag = hflag = Pflag = pflag = qflag = tflag = vflag = 0; + configdir = configfile = datadir = datafile = NULL; + + progname = argv[0]; + for (i = 0; argv[0][i]; i++) + if (argv[0][i] == '/') + progname = argv[0] + i + 1; + + opterr = 0; + while ((c = getopt(argc, argv, ":C:c:D:d:hpPqtv")) != -1) { + switch (c) { + case 'C': + configdir = optarg; + break; + case 'c': + configfile = optarg; + break; + //case 'd': + // print_digests(); + // exit(EXIT_SUCCESS); + case 'D': + datadir = optarg; + break; + case 'd': + datafile = optarg; + break; + case 'h': + /* help */ + hflag++; + break; + case 'q': + /* quiet */ + qflag++; + vflag = 0; + break; + case 'p': + /* prune */ + pflag++; + break; + case 'P': + /* purge */ + Pflag++; + break; + case 't': + /* test */ + tflag++; + break; + case 'v': + /* verbose */ + vflag++; + qflag = 0; + break; + case ':': + fprintf(stderr, + "Option -%c requires an operand\n", optopt); + eflag++; + break; + case '?': + fprintf(stderr, + "Unrecognized option '-%c'\n", optopt); + eflag++; + break; + default: + eflag++; + break; + } + } + + if (configdir && configfile) { + warnx("-C and -c are mutually exclusive"); + eflag++; + } + if (datadir && datafile) { + warnx("-D and -d are mutually exclusive"); + eflag++; + } + + if (pflag && Pflag) { + warnx("-P (purge) overrides -p (prune)"); + pflag = 0; + } + + if (eflag) { + hflag++; + ret = EXIT_FAILURE; + } + + if (hflag) { + fprintf(stderr, "Usage: " + "%s [-dhpPqtv] [-C dir|-c file] [-D dir|-d file] [job]...\n", progname); + return ret; + } + + cfg = config(configdir, configfile, datadir, datafile); + if (cfg == NULL) + exit(EXIT_FAILURE); + ctx.cfg = cfg; + + if (qflag) + cfg->quiet = qflag; + if (tflag) + cfg->test = tflag; + if (vflag) + cfg->verbose = vflag; + + if (optind < argc) { + cfg->nspecified = argc - optind; + cfg->specified = xmalloc(sizeof(char *) * cfg->nspecified); + for (i = 0; optind < argc; optind++, i++) { + cfg->specified[i] = argv[optind]; + } + } + else { + cfg->nspecified = 0; + cfg->specified = NULL; + } + + jobs = ctx.jobs = tq_new(); + ret = ini_parse(cfg->configfile, chandler, &ctx); + if (ret < 0 ) { + warn("failed reading %s", cfg->configfile); + goto main_done; + } + else if (ret != 0) { + warnx("ini_parse(%s) failed at line %d", cfg->configfile, ret); + goto main_done; + } + + if (cfg->prune && cfg->nspecified) + cfg->prune = 0; + + if (pflag) { + if (cfg->nspecified > 0) { + warnx("-p ignored when jobs are specified"); + cfg->prune = 0; + } + else if (config_global(cfg, "prune", "on") == -1) { + warnx("failed to set prune setting"); + goto main_fail; + } + } + + if (db_init(cfg->datafile) == -1) + goto main_done; + + if (Pflag) { + prune_data(cfg, NULL, 0); + goto main_done; + } + + /* Check that all the command line specified jobs actually exist */ + for (i = 0; i < cfg->nspecified; i++) { + int exists = 0; + tq_foreach(e, jobs) { + j = (struct job *)e->data; + if (strcmp(cfg->specified[i], j->name) == 0) { + exists = 1; + break; + } + } + if (!exists) { + warnx("job '%s' not found", cfg->specified[i]); + goto main_done; + } + } + + if (cfg->prune) + prune_data(cfg, jobs, 1); + + if (net_init() == -1) + goto main_done; + + tq_foreach(e, jobs) { + dgst = prevdgst = NULL; + dgstasc = prevdgstasc = NULL; + j = (struct job *)e->data; + + if (tflag) + printf("test: job '%s'\n", j->name); + + /* Do some sanity checks. */ + if (!j->url) { + warnx("job %s: missing url", j->name); + eflag++; + } + if (!j->dtype) + j->dtype = DEFAULT_DIGEST; + if (!digest_is_provided(j->dtype)) { + warnx("job %s: digest %s not provided\n", j->name, j->dtype); + eflag++; + } + + if (tflag || eflag) + continue; + + if (run_job(j) == -1) { + warnx("job %s failed", j->name); + continue; + } + + res = db_find(j->name, &prev); + if (res == -1) { + warnx("internal db error"); + eflag++; + goto main_fail; + } + if (res == 0) { + if (db_insert(j) == -1) { + warnx("db_insert(%s) failed", j->name); + eflag++; + goto main_fail; + } + continue; + } + if (res == 1 && !prev) { + warnx("db_find(%s) returned FOUND in database, but return value not set"); + eflag++; + goto main_fail; + } + + if (!j->buf) { + warnx("data for job %s missing", j->name); + eflag++; + goto main_fail; + } + if (!prev->buf) { + warnx("data for job %s previous run missing", prev->name); + eflag++; + goto main_fail; + } + + dgst = calcdgst(j->dtype ? j->dtype : DEFAULT_DIGEST, j->buf, j->bufsz, &dlen); + if (!dgst) { + warnx("failed to derive digest for job %s new data", j->name); + eflag++; + goto main_fail; + } + prevdgst = calcdgst(j->dtype ? j->dtype : DEFAULT_DIGEST, prev->buf, prev->bufsz, &prevdlen); + if (!dgst) { + warnx("failed to derive digest for job %s old data", prev->name); + eflag++; + goto main_fail; + } + if (dlen != prevdlen) { + warnx("digest lengths for old and new data don't match for job %s", j->name); + eflag++; + goto main_fail; + } + + if (memcmp(dgst, prevdgst, dlen) == 0) + goto loop_done; + + dgstasc = dgst2asc(dgst, dlen); + prevdgstasc = dgst2asc(prevdgst, prevdlen); + if (strftime(lasttime, sizeof(lasttime), "%c", gmtime(&prev->date)) == 0 || + strftime(thistime, sizeof(thistime), "%c", gmtime(&j->date)) == 0) + warnx("failed to format date strings"); + + if (cfg->verbose != 0) { + printf("%s (%s):\n%s: %s\n%s: %s\n\n", + j->name, j->url, lasttime, prevdgstasc, thistime, dgstasc); + } + + if (db_update(j) == -1) { + warnx("db_update(%s) failed", j->name); + eflag++; + } + + if (cfg->quiet != 0) { + buf = get_diff(prev->buf, prev->bufsz, j->buf, j->bufsz); + if (buf == NULL) { + warnx("failed to get diff with last run for job '%s'", j->name); + eflag++; + } + else { + printf("job %s:\n", j->name); + printf("prev_dgst=%s\n", prevdgstasc); + printf("new_dgst=%s\n", dgstasc); + printf("job: %s\n--- last\n+++ this\n%s", j->name, buf); + } + } + +loop_done: + free(dgst); + free(prevdgst); + free(dgstasc); + free(prevdgstasc); + + job_free(prev); + } + +main_fail: + net_cleanup(); + db_cleanup(); + + if (!eflag) + ret = EXIT_SUCCESS; + +main_done: + tq_cleanup(jobs, job_free); + free(jobs); + config_cleanup(cfg); + + return ret; +} diff --git a/surveil/surveil/net.c b/surveil/surveil/net.c new file mode 100644 index 0000000..0f7988d --- /dev/null +++ b/surveil/surveil/net.c @@ -0,0 +1,107 @@ +/* vim: set ts=4 sw=4 ai: */ + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <fcntl.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> +#include <sys/stat.h> +#include <regex.h> +#include <err.h> +#include <curl/curl.h> + +#include "buf.h" +#include "util.h" + +static CURL *curl; + +static size_t write_cb(void *data, size_t size, size_t nmemb, void *clientp) +{ + size_t sz = size * nmemb; + struct buf *buf = (struct buf *)clientp; + + if (buf_addstr(buf, data, sz) == -1) + return 0; + if (buf_addchr(buf, '\0') == -1) + return 0; + return sz; +} + +ssize_t net_get_url(char *url, char **buf) +{ + CURLcode res; + struct buf b = { + .ptr = NULL, .size = 0, .len = 0, .chunksize = 0 + }; + + curl_easy_reset(curl); + + res = curl_easy_setopt(curl, CURLOPT_URL, url); + if (res != CURLE_OK) { + warnx("curl_easy_seopt(CURLOPT_URL,%s): %s", + url, curl_easy_strerror(res)); + return -1; + } + res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); + if (res != CURLE_OK) { + warnx("curl_easy_seopt(CURLOPT_WRITEFUNCTION): %s", + curl_easy_strerror(res)); + return -1; + } + + res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &b); + if (res != CURLE_OK) { + warnx("curl_easy_seopt(CURLOPT_WRITEDATA): %s", + curl_easy_strerror(res)); + return -1; + } + + if (buf_init(&b, CHNKSZ) == -1) { + warnx("failed to initialize recv buffer"); + return -1; + } + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + warnx("curl_easy_perform: %s", + curl_easy_strerror(res)); + free(b.ptr); + return -1; + } + + if (buf_addstr(&b, "\n", 1) == -1 || + buf_addchr(&b, '\0') == -1) { + free(b.ptr); + return -1; + } + + *buf = b.ptr; + return b.len; +} + +int net_init(void) +{ + CURLcode curlres; + + if ((curlres = curl_global_init(CURL_GLOBAL_DEFAULT)) != 0) { + warnx("curl_global_default: %s", + curl_easy_strerror(curlres)); + return -1; + } + + if ((curl = curl_easy_init()) == NULL) { + warnx("curl_easy_init"); + curl_global_cleanup(); + return -1; + } + + return 0; +} + +void net_cleanup(void) +{ + curl_easy_cleanup(curl); + curl_global_cleanup(); +} diff --git a/surveil/surveil/net.h b/surveil/surveil/net.h new file mode 100644 index 0000000..f9b8723 --- /dev/null +++ b/surveil/surveil/net.h @@ -0,0 +1,11 @@ +/* vim: set ts=4 sw=4 sts=4: */ +#ifndef SURVEIL_NET_H +#define SURVEIL_NET_H + +#include "buf.h" + +ssize_t net_get_url(const char *, char **); +int net_init(void); +void net_cleanup(void); + +#endif/*!SURVEIL_NET_H*/ diff --git a/surveil/surveil/search.c b/surveil/surveil/search.c new file mode 100644 index 0000000..74b7032 --- /dev/null +++ b/surveil/surveil/search.c @@ -0,0 +1,241 @@ +/* vim: set ts=4 sw=4 ai: */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <ctype.h> +#include <regex.h> +#include <err.h> +#include <queue/tq.h> + +#include "buf.h" +#include "search.h" +#include "util.h" + +struct match *match_new(const char *pat, const char *rpl, int line, const char *str, size_t nsubs, const regmatch_t *subs) +{ + struct match *m; + + m = xmalloc(sizeof(struct match)); + m->pat = xstrdup(pat); + if (rpl) + m->rpl = xstrdup(rpl); + else + m->rpl = NULL; + m->line = line; + m->str = xstrdup(str); + m->nsubs = nsubs; + if (nsubs) { + m->subs = xmalloc(nsubs * sizeof(regmatch_t)); + memcpy(m->subs, subs, nsubs * sizeof(regmatch_t)); + } + else { + m->subs = NULL; + } + + return m; +} + +void match_free(void *data) +{ + struct match *m; + + m = (struct match *)data; + + free(m->pat); + free(m->str); + free(m->rpl); + if (m->nsubs) + free(m->subs); + free(m); +} + +struct tqh *search_text(char *buf, size_t sz, char *pat, char *rpl) +{ + int i, ret; + char *l, *s, *e, *nl; + char errbuf[BUFSIZ]; + size_t nm; + regmatch_t *pm; + regex_t res; + struct tqh *srch; + struct tqe *elem; + struct match *m; + + /* XXX consider REG_NEWLINE for cflags */ + if ((ret = regcomp(&res, pat, REG_EXTENDED)) != 0) { + regerror(ret, &res, errbuf, sizeof(errbuf)); + warnx("%s: %s\n", pat, errbuf); + return NULL; + } + + nm = res.re_nsub + 1; + pm = xmalloc(nm * sizeof(regmatch_t)); + + srch = tq_new(); + + for (l = buf, e = l+sz, i = 1;l && *l && l < e; i++) { + if ((nl = strchr(l, '\n')) != NULL) + *nl = '\0'; + + ret = regexec(&res, l, nm, pm, 0); + if (ret == REG_NOMATCH) + goto skip; + m = match_new(pat, rpl, i, l, nm, pm); + elem = tq_elem_new(m); + if (tq_empty(srch)) + tq_insert_head(srch, elem); + else + tq_insert_tail(srch, elem); + + /* XXX Should this be rm_so+1 to start just after + * the start of the last match? Seems like it depends + * on whether overlapping matches are ok. */ + /*s = l + pm[0].rm_so + 1;*/ + s = l + pm[0].rm_eo; + while (s < e) { + ret = regexec(&res, s, nm, pm, REG_NOTBOL); + if (ret == REG_NOMATCH) + break; + m = match_new(pat, rpl, i, l, nm, pm); + elem = tq_elem_new(m); + if (tq_empty(srch)) + tq_insert_head(srch, elem); + else + tq_insert_tail(srch, elem); + + s += pm[0].rm_eo; + /* XXX See above about overlapping matches. */ + } + +skip: + if (nl) + l = nl+1; + else + l = e; + } + + free(pm); + regfree(&res); + + return srch; +} + +/* derived from NetBSD's src/lib/libc/regex/regsub.c: regasub() */ +ssize_t regsub(char **buf, const char *sub, const regmatch_t *pm, const char *str) +{ + ssize_t i; + char c; + struct buf b = { + .ptr = NULL, .len = 0, .size = 0, .chunksize = 0 + }; + + if (buf_init(&b, CHNKSZ) == -1) + return -1; + + while ((c = *sub++) != '\0') { + switch (c) { + case '&': + i = 0; + break; + case '\\': + if (isdigit((unsigned char)*sub)) + i = *sub++ - '0'; + else + i = -1; + break; + default: + i = -1; + break; + } + + if (i == -1) { + if (c == '\\' && (*sub == '\\' || *sub == '&')) + c = *sub++; + if (buf_addchr(&b, c) == -1) + goto regsub_fail; + } + else if (pm[i].rm_so != -1 && pm[i].rm_eo != -1) { + size_t l = (size_t)(pm[i].rm_eo - pm[i].rm_so); + if (buf_addstr(&b, str + pm[i].rm_so, l) == -1) + goto regsub_fail; + } + if (b.len > b.size) + goto regsub_fail; + } + + if (buf_addchr(&b, '\n') == -1 || + buf_addchr(&b, '\0') == -1 || + b.len > b.size) + goto regsub_fail; + + *buf = b.ptr; + return b.len; + +regsub_fail: + free(b.ptr); + return -1; +} + +ssize_t get_matches(char **buf, struct tqh *s) +{ + ssize_t l; + char *rpl; + struct tqe *e; + struct match *m; + struct buf b = { + .ptr = NULL, .len = 0, .size = 0, .chunksize = 0 + }; + + if (buf_init(&b, 1) == -1) + return -1; + + tq_foreach(e, s) { + m = (struct match *)e->data; + if (!m->rpl) + buf_addstr(&b, m->str + m->subs[0].rm_so, + (int)(m->subs[0].rm_eo - m->subs[0].rm_so)); + else { + if ((l = regsub(&rpl, m->rpl, m->subs, m->str)) == -1) { + warnx("regsub failed\n"); + continue; + } + if (buf_addstr(&b, rpl, l) == -1) + goto get_matches_fail; + free(rpl); + } + } + + if (buf_addchr(&b, '\n') == -1 || + buf_addchr(&b, '\0') == -1 || + b.len > b.size) + goto get_matches_fail; + + *buf = b.ptr; + return b.len; + +get_matches_fail: + free(b.ptr); + return -1; +} + +#if 0 +void print_match(struct match *m) +{ + char *rpl; + + if (!m->rpl) + printf("'%.*s'\n", + (int)(m->subs[0].rm_eo - m->subs[0].rm_so), + m->str + m->subs[0].rm_so); + + if (regsub(&rpl, m->rpl, m->subs, m->str) != -1) { + printf("%s\n", rpl); + free(rpl); + } + else { + warnx("regsub failed"); + } +} +#endif diff --git a/surveil/surveil/search.h b/surveil/surveil/search.h new file mode 100644 index 0000000..e1ff993 --- /dev/null +++ b/surveil/surveil/search.h @@ -0,0 +1,26 @@ +/* vim: set ts=4 sw=4 sts=4: */ + +#ifndef SURVEIL_SEARCH_H +#define SURVEIL_SEARCH_H + +#include <regex.h> +#include <queue/tq.h> + +struct match { + char *pat; + char *rpl; + int line; + char *str; + size_t nsubs; + regmatch_t *subs; +}; + + +struct match *match_new(const char *, const char *, int, const char *, size_t, const regmatch_t *); +void match_free(void *); +struct tqh *search_text(char *, size_t, char *, char *); +ssize_t regsub(char **, const char *, const regmatch_t *, const char *); +ssize_t get_matches(char **, struct tqh *); +void print_match(struct match *); + +#endif/*!SURVEIL_SEARCH_H*/ diff --git a/surveil/surveil/surveil.1 b/surveil/surveil/surveil.1 new file mode 100644 index 0000000..bd3d1ee --- /dev/null +++ b/surveil/surveil/surveil.1 @@ -0,0 +1,82 @@ +.Dd August 25, 2023 +.Dt surveil 1 +.Os +.Sh NAME +.Nm surveil +.Nd track changes to remote files +.Sh SYNOPSIS +.Nm +.Op Fl hpqtv +.Op Fl C Ar dir | Fl c Ar file +.Op Fl D Ar dir | Fl d Ar file +.Op Ar jobname ... +.Sh DESCRIPTION +The +.Nm +command tracks changes to remote files. +The options are as follows: +.Bl -tag -width Ds +.It Fl C Ar dir +Use +.Ar dir +to find configuration +.It Fl c Ar file +Use +.Ar file +for configuration +.It Fl D Ar dir +Use +.Ar dir +to store data +.It Fl d Ar file +Use +.Ar file +to store data +.It Fl h +Print help message +.\".It Fl p +.\"Print available digests +.It Fl p +Clean out (prune) data for jobs not in config. +.It Fl P +Clean out (purge) all data, or for command line specified jobs. +.It Fl q +Turns on quiet output +.It Fl t +Test configuration and exit +.It Fl v +Turns on verbose output +.El +.Sh ENVIRONMENT +If the following environment variables exist, they will be used by +.Nm : +.Bl -tag -width EDITOR +.It Ev HOME +.It Ev XDG_CONFIG_HOME +.It Ev XDG_DATA_HOME +.El +.Sh FILES +These directories will be searched, in listed order, to load configuration: +.Bl -tag -width Pa +.It Pa $XDG_CONFIG_HOME/surveil/ +.It Pa $HOME/.config/surveil/ +.It Pa $HOME/.surveil/ +.El + +.Bl -tag -width Pa +These directories will be searched, in listed order, to store data: +.It Pa $XDG_DATA_HOME/surveil/surveil.dat +.It Pa $HOME/.local/share/surveil/surveil.dat +.It Pa $HOME/.surveil/surveil.dat +.El +.\".Sh EXIT STATUS +.\" Actually, currently, the exit status is 0 on success and -1 on error +\".Ex -std +.\".Sh EXAMPLES +.\".Sh DIAGNOSTICS +.Sh SEE ALSO +.Xr openssl-dgst 1 , +.Xr regex 7 +.\".Sh AUTHORS +.\".Sh CAVEATS +\.".Sh BUGS diff --git a/surveil/surveil/util.c b/surveil/surveil/util.c new file mode 100644 index 0000000..c0ac090 --- /dev/null +++ b/surveil/surveil/util.c @@ -0,0 +1,219 @@ +/* vim: set ts=4 sw=4 ai: */ +#include <sys/stat.h> +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <ctype.h> +#include <limits.h> +#include <errno.h> +#include <err.h> + +void *xmalloc(size_t sz) +{ + void *p; + + p = malloc(sz); + if (!p) + err(EXIT_FAILURE, "malloc(%u)", sz); + + return p; +} + +void *xrealloc(void *x, size_t sz) +{ + void *p; + + p = realloc(x, sz); + if (!p) + err(EXIT_FAILURE, "realloc(%x,%u)", x, sz); + + return p; +} + +char *xstrdup(const char *s) +{ + char *p; + + p = strdup(s); + if (!p) + err(EXIT_FAILURE, "strdup(%s)", s); + + return p; +} + +char *trimspace(char *s) +{ + char *p; + + /* skip leading space */ + while (*s && isspace(*s)) + s++; + /* find end of string */ + for (p = s; *(p+1); p++) ; + /* trim trailing space */ + while (isspace(*p)) + *p-- = '\0'; + + return s; +} + + +ssize_t readfile(const char *filename, char **rbuf) +{ + char *buf; + off_t sz; + ssize_t offset; + ssize_t n, ret = -1; + int fd; + + if ((fd = open(filename, O_RDONLY)) == -1) { + warn("%s", filename); + return -1; + } + +#define CHUNKSZ (1024*64) + offset = 0; + sz = 0; + buf = NULL; + while (1) { + if (sz == offset) { + sz += CHUNKSZ; + buf = xrealloc(buf, sz); + } + n = read(fd, buf + (int)offset, sz - offset); + if (n == -1 && errno != EINTR) { + warn("read failed"); + free(buf); + break; + } + if (n == 0) { + *rbuf = buf; + ret = offset; + break; + } + offset += n; + } + + close(fd); + + return ret; +} + +ssize_t writefile(const char *filename, const char *buf, size_t len, int excl) +{ + ssize_t n; + size_t left, total; + int fd, flags; + const char *p; + + flags = O_WRONLY | O_CREAT; + if (excl) + flags |= O_EXCL; + + if ((fd = open(filename, flags, 0600)) == -1) { + warn("%s", filename); + return -1; + } + + total = 0; + left = len; + p = buf; + while (left > 0) { + if ((n = write(fd, p, len)) == -1) { + if (errno != EINTR) { + warn("write(%s)", filename); + close(fd); + return -1; + } + } + if (n == 0) + break; + total += n; + left -= n; + p += n; + } + + close(fd); + + return total; +} + +int s2l(const char *s, long *ret) +{ + char *endp; + long val; + + if (!s || !*s) { + errno = EINVAL; + return -1; + } + + errno = 0; + val = strtol(s, &endp, 10); + if (errno) + return -1; + if (!endp || *endp != '\0') { + errno = EINVAL; + return -1; + } + + *ret = val; + return 0; +} + +int s2i(const char *s, int *ret) +{ + long val; + + if (s2l(s, &val) == -1) + return -1; + + if (val > INT_MAX || val < INT_MIN) { + errno = ERANGE; + return -1; + } + + *ret = val; + return 0; +} + +int s2ul(const char *s, unsigned long *ret) +{ + char *endp; + unsigned long val; + + if (!s || !*s) { + errno = EINVAL; + return -1; + } + + errno = 0; + val = strtoul(s, &endp, 10); + if (errno) + return -1; + if (!endp || *endp != '\0') { + errno = EINVAL; + return -1; + } + + *ret = val; + return 0; +} + +int s2u (const char *s, unsigned *ret) +{ + unsigned long val; + + if (s2ul(s, &val) == -1) + return -1; + + if (val > UINT_MAX) { + errno = ERANGE; + return -1; + } + + *ret = val; + return 0; +} diff --git a/surveil/surveil/util.h b/surveil/surveil/util.h new file mode 100644 index 0000000..a6f903b --- /dev/null +++ b/surveil/surveil/util.h @@ -0,0 +1,18 @@ +/* vim: set ts=4 sw=4 ai: */ +#ifndef SURVEIL_UTIL_H +#define SURVEIL_UTIL_H + +#include <sys/types.h> + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(const char *); +char *trimspace(const char *); +ssize_t readfile(const char *, char **); +ssize_t writefile(const char *, const char *, size_t, int); +int s2ul(const char *, unsigned long *); +int s2l(const char *, long *); +int s2u(const char *, unsigned *); +int s2i(const char *, int *); + +#endif/*!SURVEIL_UTIL_H*/ |
