commit 4399a3ba705f912991571fb02eee94553fe523c9
Author: sin <sin@2f30.org>
Date: Thu, 29 May 2014 14:18:50 +0100
Initial commit
Diffstat:
A | LICENSE | | | 21 | +++++++++++++++++++++ |
A | Makefile | | | 53 | +++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | arg.h | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.mk | | | 9 | +++++++++ |
A | installpkg.c | | | 293 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | removepkg.c | | | 201 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | searchpkg | | | 26 | ++++++++++++++++++++++++++ |
A | strlcat.c | | | 51 | +++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | strlcpy.c | | | 47 | +++++++++++++++++++++++++++++++++++++++++++++++ |
A | util.h | | | 12 | ++++++++++++ |
10 files changed, 776 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2014 sin <sin@2f30.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,53 @@
+include config.mk
+
+.POSIX:
+.SUFFIXES: .c .o
+
+LIB = \
+ strlcat.o \
+ strlcpy.o
+
+SRC = \
+ installpkg.c \
+ removepkg.c
+
+SHPROG = \
+ searchpkg
+
+OBJ = $(SRC:.c=.o) $(LIB)
+BIN = $(SRC:.c=)
+
+all: binlib
+
+binlib: util.a
+ $(MAKE) bin
+
+bin: $(BIN)
+
+$(OBJ): util.h config.mk
+
+.o:
+ @echo LD $@
+ @$(LD) -o $@ $< util.a $(LDFLAGS)
+
+.c.o:
+ @echo CC $<
+ @$(CC) -c -o $@ $< $(CFLAGS)
+
+util.a: $(LIB)
+ @echo AR $@
+ @$(AR) -r -c $@ $(LIB)
+
+install: all
+ @echo installing executables to $(DESTDIR)$(PREFIX)/bin
+ @mkdir -p $(DESTDIR)$(PREFIX)/bin
+ @cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
+ @cp -f $(SHPROG) $(DESTDIR)$(PREFIX)/bin
+
+uninstall:
+ @echo removing executables from $(DESTDIR)$(PREFIX)/bin
+ @cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN) $(SHPROG)
+
+clean:
+ @echo cleaning
+ @rm -f $(BIN) $(OBJ) $(LIB) util.a
diff --git a/arg.h b/arg.h
@@ -0,0 +1,63 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+/* Handles obsolete -NUM syntax */
+#define ARGNUM case '0':\
+ case '1':\
+ case '2':\
+ case '3':\
+ case '4':\
+ case '5':\
+ case '6':\
+ case '7':\
+ case '8':\
+ case '9'
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base)))
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/config.mk b/config.mk
@@ -0,0 +1,9 @@
+VERSION = 0.1
+
+PREFIX = /usr/local
+
+CC = gcc
+LD = $(CC)
+CPPFLAGS = -D_BSD_SOURCE -D_GNU_SOURCE
+CFLAGS = -std=c99 -Wall -Wextra -pedantic $(CPPFLAGS)
+LDFLAGS = -s -larchive
diff --git a/installpkg.c b/installpkg.c
@@ -0,0 +1,293 @@
+/* See LICENSE file for copyright and license details. */
+#include <archive.h>
+#include <archive_entry.h>
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "util.h"
+
+static void checkdb(const char *);
+static void updatedb(const char *, const char *);
+static int collisions(const char *, const char *);
+static int extract(const char *, const char *);
+
+char *argv0;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-f] [-p prefix] pkg...\n", argv0);
+ fprintf(stderr, " -f Override filesystem checks and force installation\n");
+ fprintf(stderr, " -p Set the installation prefix\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ char *prefix = "/";
+ int fflag = 0;
+
+ ARGBEGIN {
+ case 'f':
+ fflag = 1;
+ break;
+ case 'p':
+ prefix = ARGF();
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ checkdb(prefix);
+ for (i = 0; i < argc; i++) {
+ if (fflag == 0) {
+ if (collisions(prefix, argv[i]) != 0) {
+ fprintf(stderr, "aborting, use -f to override\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ updatedb(prefix, argv[i]);
+ extract(prefix, argv[i]);
+ printf("installed %s\n", argv[i]);
+ }
+ return EXIT_SUCCESS;
+}
+
+static void
+checkdb(const char *prefix)
+{
+ char cwd[PATH_MAX];
+ char *dbpath = "var/pkg";
+ int r;
+
+ getcwd(cwd, sizeof(cwd));
+
+ r = chdir(prefix);
+ if (r < 0) {
+ fprintf(stderr, "chdir %s: %s\n", prefix,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ r = access(dbpath, R_OK | W_OK | X_OK);
+ if (r < 0) {
+ fprintf(stderr, "access %s: %s\n", dbpath, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ chdir(cwd);
+}
+
+static void
+updatedb(const char *prefix, const char *f)
+{
+ char cwd[PATH_MAX];
+ char path[PATH_MAX], filename[PATH_MAX];
+ FILE *fp;
+ struct archive *in;
+ struct archive_entry *entry;
+ int r;
+
+ getcwd(cwd, sizeof(cwd));
+
+ in = archive_read_new();
+
+ archive_read_support_filter_gzip(in);
+ archive_read_support_format_tar(in);
+
+ r = archive_read_open_filename(in, f, BUFSIZ);
+ if (r < 0) {
+ fprintf(stderr, "%s\n", archive_error_string(in));
+ exit(EXIT_FAILURE);
+ }
+
+ r = chdir(prefix);
+ if (r < 0) {
+ fprintf(stderr, "chdir %s: %s\n", prefix,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ strlcpy(path, "var/pkg/", sizeof(path));
+ strlcpy(filename, f, sizeof(filename));
+ strlcat(path, basename(filename), sizeof(path));
+
+ fp = fopen(path, "w");
+ if (!fp) {
+ fprintf(stderr, "fopen %s: %s\n", path,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ while (1) {
+ r = archive_read_next_header(in, &entry);
+ if (r == ARCHIVE_EOF)
+ break;
+ if (r != ARCHIVE_OK) {
+ fprintf(stderr, "%s\n", archive_error_string(in));
+ exit(EXIT_FAILURE);
+ }
+ fputs(archive_entry_pathname(entry), fp);
+ fputc('\n', fp);
+ }
+
+ fflush(fp);
+ r = fsync(fileno(fp));
+ if (r < 0)
+ fprintf(stderr, "fsync: %s\n", strerror(errno));
+ fclose(fp);
+
+ archive_read_close(in);
+ archive_read_free(in);
+
+ chdir(cwd);
+}
+
+static int
+collisions(const char *prefix, const char *f)
+{
+ char cwd[PATH_MAX];
+ struct archive *in;
+ struct archive_entry *entry;
+ struct stat sb;
+ int ok = 0;
+ int r;
+
+ getcwd(cwd, sizeof(cwd));
+
+ in = archive_read_new();
+
+ archive_read_support_filter_gzip(in);
+ archive_read_support_format_tar(in);
+
+ r = archive_read_open_filename(in, f, BUFSIZ);
+ if (r < 0) {
+ fprintf(stderr, "%s\n", archive_error_string(in));
+ exit(EXIT_FAILURE);
+ }
+
+ r = chdir(prefix);
+ if (r < 0) {
+ fprintf(stderr, "chdir %s: %s\n", prefix,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ while (1) {
+ r = archive_read_next_header(in, &entry);
+ if (r == ARCHIVE_EOF)
+ break;
+ if (r != ARCHIVE_OK) {
+ fprintf(stderr, "%s\n", archive_error_string(in));
+ exit(EXIT_FAILURE);
+ }
+ if (access(archive_entry_pathname(entry), F_OK) == 0) {
+ r = stat(archive_entry_pathname(entry), &sb);
+ if (r < 0) {
+ fprintf(stderr, "stat %s: %s\n",
+ archive_entry_pathname(entry),
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (S_ISDIR(sb.st_mode) == 0) {
+ fprintf(stderr, "%s/%s exists\n", prefix,
+ archive_entry_pathname(entry));
+ ok = 1;
+ }
+ }
+ }
+
+ archive_read_close(in);
+ archive_read_free(in);
+
+ chdir(cwd);
+ return ok;
+}
+
+static int
+extract(const char *prefix, const char *f)
+{
+ char cwd[PATH_MAX];
+ const void *buf;
+ size_t size;
+ int64_t offset;
+ struct archive *in;
+ struct archive *out;
+ struct archive_entry *entry;
+ int r;
+
+ getcwd(cwd, sizeof(cwd));
+
+ in = archive_read_new();
+ out = archive_write_disk_new();
+
+ archive_write_disk_set_options(out,
+ ARCHIVE_EXTRACT_TIME |
+ ARCHIVE_EXTRACT_PERM |
+ ARCHIVE_EXTRACT_ACL |
+ ARCHIVE_EXTRACT_FFLAGS |
+ ARCHIVE_EXTRACT_SECURE_NODOTDOT);
+
+ archive_read_support_filter_gzip(in);
+ archive_read_support_format_tar(in);
+
+ r = archive_read_open_filename(in, f, BUFSIZ);
+ if (r < 0) {
+ fprintf(stderr, "%s\n", archive_error_string(in));
+ exit(EXIT_FAILURE);
+ }
+
+ r = chdir(prefix);
+ if (r < 0) {
+ fprintf(stderr, "chdir %s: %s\n", prefix,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ while (1) {
+ r = archive_read_next_header(in, &entry);
+ if (r == ARCHIVE_EOF)
+ break;
+ if (r != ARCHIVE_OK) {
+ fprintf(stderr, "%s\n", archive_error_string(in));
+ exit(EXIT_FAILURE);
+ }
+ r = archive_write_header(out, entry);
+ if (r != ARCHIVE_OK) {
+ fprintf(stderr, "%s\n", archive_error_string(in));
+ } else {
+ while (1) {
+ r = archive_read_data_block(in, &buf,
+ &size, &offset);
+ if (r == ARCHIVE_EOF)
+ break;
+ if (r != ARCHIVE_OK)
+ break;
+ r = archive_write_data_block(out, buf, size,
+ offset);
+ if (r != ARCHIVE_OK)
+ break;
+ }
+ }
+ }
+
+ archive_read_close(in);
+ archive_read_free(in);
+ archive_write_close(out);
+ archive_write_free(out);
+
+ chdir(cwd);
+ return 0;
+}
diff --git a/removepkg.c b/removepkg.c
@@ -0,0 +1,201 @@
+/* See LICENSE file for copyright and license details. */
+#define _XOPEN_SOURCE 500
+#include <dirent.h>
+#include <errno.h>
+#include <ftw.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "util.h"
+
+static int rmemptydir(const char *, const struct stat *, int, struct FTW *);
+static int removepkg(const char *);
+
+char *argv0;
+static int fflag;
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [-f] [-p prefix] pkg...\n", argv0);
+ fprintf(stderr, " -f Force the removal of empty directories and symlinks\n");
+ fprintf(stderr, " -p Set the installation prefix\n");
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ DIR *dir;
+ struct dirent *dp;
+ char filename[PATH_MAX];
+ char *prefix = "/";
+ int found = 0;
+ int r;
+ int i;
+
+ ARGBEGIN {
+ case 'f':
+ fflag = 1;
+ break;
+ case 'p':
+ prefix = ARGF();
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 1)
+ usage();
+
+ r = chdir(prefix);
+ if (r < 0) {
+ fprintf(stderr, "chdir %s: %s\n", prefix,
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ for (i = 0; i < argc; i++) {
+ strlcpy(filename, argv[i], sizeof(filename));
+ found = 0;
+
+ dir = opendir("var/pkg");
+ if (!dir) {
+ fprintf(stderr, "opendir %s: %s\n", "var/pkg",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ while ((dp = readdir(dir))) {
+ if (strcmp(dp->d_name, ".") == 0 ||
+ strcmp(dp->d_name, "..") == 0)
+ continue;
+ if (strcmp(dp->d_name, basename(filename)) == 0) {
+ if (removepkg(argv[i]) != 0)
+ return EXIT_FAILURE;
+ found = 1;
+ break;
+ }
+ }
+
+ closedir(dir);
+ if (found == 0) {
+ fprintf(stderr, "package %s not installed\n", argv[i]);
+ return EXIT_FAILURE;
+ }
+ }
+ return EXIT_SUCCESS;
+}
+
+static int
+rmemptydir(const char *f, const struct stat *sb, int typeflag,
+ struct FTW *ftwbuf)
+{
+ (void) sb;
+ (void) ftwbuf;
+
+ if (typeflag == FTW_DP)
+ rmdir(f);
+ return 0;
+}
+
+static int
+removepkg(const char *f)
+{
+ FILE *fp;
+ struct stat sb;
+ char buf[BUFSIZ], *p;
+ char path[PATH_MAX], filename[PATH_MAX];
+ int r;
+
+ strlcpy(path, "var/pkg/", sizeof(path));
+ strlcpy(filename, f, sizeof(filename));
+ strlcat(path, basename(filename), sizeof(path));
+
+ fp = fopen(path, "r");
+ if (!fp) {
+ fprintf(stderr, "fopen %s: %s\n", path,
+ strerror(errno));
+ return 1;
+ }
+
+ while (fgets(buf, sizeof(buf), fp)) {
+ p = strrchr(buf, '\n');
+ if (p)
+ *p = '\0';
+
+ if (buf[0] == '\0') {
+ fprintf(stderr, "nil file entry in %s, skipping\n",
+ path);
+ continue;
+ }
+
+ r = lstat(buf, &sb);
+ if (r < 0) {
+ fprintf(stderr, "stat %s: %s\n",
+ buf, strerror(errno));
+ continue;
+ }
+
+ if (S_ISDIR(sb.st_mode)) {
+ if (fflag == 0)
+ printf("ignoring directory %s\n", buf);
+ continue;
+ }
+
+ if (S_ISLNK(sb.st_mode)) {
+ if (fflag == 0) {
+ printf("ignoring link %s\n", buf);
+ continue;
+ }
+ }
+
+ r = remove(buf);
+ if (r < 0) {
+ fprintf(stderr, "remove %s: %s\n", buf,
+ strerror(errno));
+ continue;
+ }
+ }
+ if (ferror(fp)) {
+ fprintf(stderr, "I/O error while processing %s\n", path);
+ fclose(fp);
+ return 1;
+ }
+
+ if (fflag == 1) {
+ /* prune empty directories as well */
+ rewind(fp);
+ while (fgets(buf, sizeof(buf), fp)) {
+ p = strrchr(buf, '\n');
+ if (p)
+ *p = '\0';
+ if (buf[0] == '\0')
+ continue;
+ nftw(buf, rmemptydir, 1, FTW_DEPTH);
+ }
+ if (ferror(fp)) {
+ fprintf(stderr, "I/O error while processing %s\n", path);
+ fclose(fp);
+ return 1;
+ }
+ }
+
+ fclose(fp);
+
+ /* nuke db entry for this package */
+ r = remove(path);
+ if (r < 0) {
+ fprintf(stderr, "remove %s: %s\n", path,
+ strerror(errno));
+ return 1;
+ }
+
+ sync();
+ return 0;
+}
diff --git a/searchpkg b/searchpkg
@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# To list all packages in the mirror try searchpkg "\.tgz$"
+# To download packages try searchpkg pkg... | xargs wget
+
+release=0.0
+arch=x86_64
+mirror="http://morpheus.2f30.org/$release/packages/$arch"
+
+usage() {
+ echo "usage: $(basename $0) pkg..." 1>&2
+ exit 1
+}
+
+if test -z "$1"; then
+ usage
+fi
+
+curl -s "$mirror/PACKAGES" | while read pkg; do
+ for i in "$@"; do
+ echo "$pkg" | grep -q "$i"
+ if test "$?" -eq 0; then
+ echo "$mirror/$pkg" | sed 's/#/%23/'
+ fi
+ done
+done | sort | uniq
diff --git a/strlcat.c b/strlcat.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include "util.h"
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+ return(dlen + (s - src)); /* count does not include NUL */
+}
diff --git a/strlcpy.c b/strlcpy.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include "util.h"
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0')
+ break;
+ }
+ }
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/util.h b/util.h
@@ -0,0 +1,12 @@
+/* See LICENSE file for copyright and license details. */
+#include <stddef.h>
+#include "arg.h"
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+
+extern char *argv0;
+
+#undef strlcat
+size_t strlcat(char *, const char *, size_t);
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);