sdhcp

simple dhcp client
Log | Files | Refs

commit 640ff93b4819f63a909d29fc21f0cdf00033034f
Author: Naveen Narayanan <zerous@nocebo.space>
Date:   Thu, 23 Sep 2021 21:22:20 +0200

Import from http://git.2f30.org/sdhcp/

Diffstat:
AMakefile | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aarg.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 12++++++++++++
Asdhcp.1 | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asdhcp.c | 584+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil.h | 10++++++++++
Autil/eprintf.c | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil/strlcpy.c | 32++++++++++++++++++++++++++++++++
8 files changed, 882 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,65 @@ +include config.mk + +.POSIX: +.SUFFIXES: .c .o + +HDR = util.h arg.h +LIB = \ + util/strlcpy.o \ + util/eprintf.o + +SRC = sdhcp.c + +OBJ = $(SRC:.c=.o) $(LIB) +BIN = $(SRC:.c=) +MAN = $(SRC:.c=.1) + +all: options binlib + +options: + @echo sdhcp build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +binlib: util.a + $(MAKE) bin + +bin: $(BIN) + +$(OBJ): $(HDR) 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) + @ranlib $@ + +install: all + @echo installing executables to $(DESTDIR)$(PREFIX)/sbin + @mkdir -p $(DESTDIR)$(PREFIX)/sbin + @cp -f $(BIN) $(DESTDIR)$(PREFIX)/sbin + @cd $(DESTDIR)$(PREFIX)/sbin && chmod 755 $(BIN) + @echo installing manual pages to $(DESTDIR)$(MANPREFIX)/man1 + @mkdir -p $(DESTDIR)$(MANPREFIX)/man1 + @for m in $(MAN); do sed "s/VERSION/$(VERSION)/g" < "$$m" > $(DESTDIR)$(MANPREFIX)/man1/"$$m"; done + @cd $(DESTDIR)$(MANPREFIX)/man1 && chmod 644 $(MAN) + +uninstall: + @echo removing executables from $(DESTDIR)$(PREFIX)/sbin + @cd $(DESTDIR)$(PREFIX)/sbin && rm -f $(BIN) + @echo removing manual pages from $(DESTDIR)$(MANPREFIX)/man1 + @cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN) + +clean: + @echo cleaning + @rm -f $(BIN) $(OBJ) util.a + +.PHONY: all options clean install uninstall 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,12 @@ +# sdhcp version +VERSION = 0.1 + +PREFIX = /usr/local +DESTDIR = +MANPREFIX = $(PREFIX)/share/man + +CC = cc +LD = $(CC) +CPPFLAGS = -D_DEFAULT_SOURCE +CFLAGS = -Wall -Wextra -pedantic -std=c99 $(CPPFLAGS) +LDFLAGS = -s diff --git a/sdhcp.1 b/sdhcp.1 @@ -0,0 +1,51 @@ +.Dd April 27, 2015 +.Dt SDHCP 1 +.Os +.Sh NAME +.Nm sdhcp +.Nd a simple DHCP client +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl e Ar program +.Op Fl f +.Op Fl i +.Op Ar interface +.Op Ar client-id +.Sh DESCRIPTION +.Nm +is a simple, tiny DHCP client. It runs until it enters the "Bound" +state, then forks to the background and runs as a daemon to keep +the lease alive. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl d +don't change DNS in +.Pa /etc/resolv.conf . +.It Fl e Ar program +run +.Ar program . +Variables will be set, see VARIABLES. +.It Fl f +run in foreground. +.It Fl i +don't change interface information such as an IP address. +.El +.Sh VARIABLES +The following variables are set: +.Bl -tag -width Ds +.It Ev SERVER +DHCP IP. +.It Ev DNS +DNS IP. +.It Ev ROUTER +router IP. +.It Ev MASK +network mask. +.It Ev CLIENT +your client IP. +.El +.Sh BUGS +I'm sure there are plenty. It only currently supports a small subset of +DHCP options, and has been untested on larger networks. It ignores most of +the DHCP options it understands. diff --git a/sdhcp.c b/sdhcp.c @@ -0,0 +1,584 @@ +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/timerfd.h> + +#include <netinet/in.h> +#include <net/if.h> +#include <net/route.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "arg.h" +#include "util.h" + +typedef struct bootp { + unsigned char op [1]; + unsigned char htype [1]; + unsigned char hlen [1]; + unsigned char hops [1]; + unsigned char xid [4]; + unsigned char secs [2]; + unsigned char flags [2]; + unsigned char ciaddr [4]; + unsigned char yiaddr [4]; + unsigned char siaddr [4]; + unsigned char giaddr [4]; + unsigned char chaddr [16]; + unsigned char sname [64]; + unsigned char file [128]; + unsigned char magic [4]; + unsigned char optdata [312-4]; +} Bootp; + +enum { + DHCPdiscover = 1, + DHCPoffer, + DHCPrequest, + DHCPdecline, + DHCPack, + DHCPnak, + DHCPrelease, + DHCPinform, + Timeout0 = 200, + Timeout1, + Timeout2, + + Bootrequest = 1, + Bootreply = 2, + /* bootp flags */ + Fbroadcast = 1 << 15, + + OBpad = 0, + OBmask = 1, + OBrouter = 3, + OBnameserver = 5, + OBdnsserver = 6, + OBhostname = 12, + OBbaddr = 28, + ODipaddr = 50, /* 0x32 */ + ODlease = 51, + ODoverload = 52, + ODtype = 53, /* 0x35 */ + ODserverid = 54, /* 0x36 */ + ODparams = 55, /* 0x37 */ + ODmessage = 56, + ODmaxmsg = 57, + ODrenewaltime = 58, + ODrebindingtime = 59, + ODvendorclass = 60, + ODclientid = 61, /* 0x3d */ + ODtftpserver = 66, + ODbootfile = 67, + OBend = 255, +}; + +enum { Broadcast, Unicast }; + +static Bootp bp; +static unsigned char magic[] = { 99, 130, 83, 99 }; + +/* conf */ +static unsigned char xid[sizeof(bp.xid)]; +static unsigned char hwaddr[16]; +static char hostname[HOST_NAME_MAX + 1]; +static time_t starttime; +static char *ifname = "eth0"; +static unsigned char cid[16]; +static char *program = ""; +static int sock, timers[3]; +/* sav */ +static unsigned char server[4]; +static unsigned char client[4]; +static unsigned char mask[4]; +static unsigned char router[4]; +static unsigned char dns[4]; + +static int dflag = 1; /* change DNS in /etc/resolv.conf ? */ +static int iflag = 1; /* set IP ? */ +static int fflag = 0; /* run in foreground */ + +#define IP(a, b, c, d) (unsigned char[4]){ a, b, c, d } + +static void +hnput(unsigned char *dst, uint32_t src, size_t n) +{ + unsigned int i; + + for (i = 0; n--; i++) + dst[i] = (src >> (n * 8)) & 0xff; +} + +static struct sockaddr * +iptoaddr(struct sockaddr *ifaddr, unsigned char ip[4], int port) +{ + struct sockaddr_in *in = (struct sockaddr_in *)ifaddr; + + in->sin_family = AF_INET; + in->sin_port = htons(port); + memcpy(&(in->sin_addr), ip, sizeof(in->sin_addr)); + + return ifaddr; +} + +/* sendto UDP wrapper */ +static ssize_t +udpsend(unsigned char ip[4], int fd, void *data, size_t n) +{ + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + ssize_t sent; + + iptoaddr(&addr, ip, 67); /* bootp server */ + if ((sent = sendto(fd, data, n, 0, &addr, addrlen)) == -1) + eprintf("sendto:"); + + return sent; +} + +/* recvfrom UDP wrapper */ +static ssize_t +udprecv(unsigned char ip[4], int fd, void *data, size_t n) +{ + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + ssize_t r; + + iptoaddr(&addr, ip, 68); /* bootp client */ + if ((r = recvfrom(fd, data, n, 0, &addr, &addrlen)) == -1) + eprintf("recvfrom:"); + + return r; +} + +static void +setip(unsigned char ip[4], unsigned char mask[4], unsigned char gateway[4]) +{ + struct ifreq ifreq; + struct rtentry rtreq; + int fd; + + memset(&ifreq, 0, sizeof(ifreq)); + memset(&rtreq, 0, sizeof(rtreq)); + + strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE); + iptoaddr(&(ifreq.ifr_addr), ip, 0); + if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) == -1) + eprintf("can't set ip, socket:"); + ioctl(fd, SIOCSIFADDR, &ifreq); + iptoaddr(&(ifreq.ifr_netmask), mask, 0); + ioctl(fd, SIOCSIFNETMASK, &ifreq); + ifreq.ifr_flags = IFF_UP | IFF_RUNNING | IFF_BROADCAST | IFF_MULTICAST; + ioctl(fd, SIOCSIFFLAGS, &ifreq); + /* gw */ + rtreq.rt_flags = (RTF_UP | RTF_GATEWAY); + iptoaddr(&(rtreq.rt_gateway), gateway, 0); + iptoaddr(&(rtreq.rt_genmask), IP(0, 0, 0, 0), 0); + iptoaddr(&(rtreq.rt_dst), IP(0, 0, 0, 0), 0); + ioctl(fd, SIOCADDRT, &rtreq); + + close(fd); +} + +static void +cat(int dfd, char *src) +{ + char buf[BUFSIZ]; + int n, fd; + + if ((fd = open(src, O_RDONLY)) == -1) + return; /* can't read, but don't error out */ + while ((n = read(fd, buf, sizeof(buf))) > 0) + write(dfd, buf, n); + close(fd); +} + +static void +setdns(unsigned char dns[4]) +{ + char buf[128]; + int fd; + + if ((fd = creat("/etc/resolv.conf", 0644)) == -1) { + weprintf("can't change /etc/resolv.conf:"); + return; + } + cat(fd, "/etc/resolv.conf.head"); + if (snprintf(buf, sizeof(buf) - 1, "\nnameserver %d.%d.%d.%d\n", + dns[0], dns[1], dns[2], dns[3]) > 0) + write(fd, buf, strlen(buf)); + cat(fd, "/etc/resolv.conf.tail"); + close(fd); +} + +static void +optget(Bootp *bp, void *data, int opt, int n) +{ + unsigned char *p = bp->optdata; + unsigned char *top = ((unsigned char *)bp) + sizeof(*bp); + int code, len; + + while (p < top) { + code = *p++; + if (code == OBpad) + continue; + if (code == OBend || p == top) + break; + len = *p++; + if (len > top - p) + break; + if (code == opt) { + memcpy(data, p, MIN(len, n)); + break; + } + p += len; + } +} + +static unsigned char * +optput(unsigned char *p, int opt, unsigned char *data, size_t len) +{ + *p++ = opt; + *p++ = (unsigned char)len; + memcpy(p, data, len); + + return p + len; +} + +static unsigned char * +hnoptput(unsigned char *p, int opt, uint32_t data, size_t len) +{ + *p++ = opt; + *p++ = (unsigned char)len; + hnput(p, data, len); + + return p + len; +} + +static void +dhcpsend(int type, int how) +{ + unsigned char *ip, *p; + + memset(&bp, 0, sizeof(bp)); + hnput(bp.op, Bootrequest, 1); + hnput(bp.htype, 1, 1); + hnput(bp.hlen, 6, 1); + memcpy(bp.xid, xid, sizeof(xid)); + hnput(bp.flags, Fbroadcast, sizeof(bp.flags)); + hnput(bp.secs, time(NULL) - starttime, sizeof(bp.secs)); + memcpy(bp.magic, magic, sizeof(bp.magic)); + memcpy(bp.chaddr, hwaddr, sizeof(bp.chaddr)); + p = bp.optdata; + p = hnoptput(p, ODtype, type, 1); + p = optput(p, ODclientid, cid, sizeof(cid)); + p = optput(p, OBhostname, (unsigned char *)hostname, strlen(hostname)); + + switch (type) { + case DHCPdiscover: + break; + case DHCPrequest: + /* memcpy(bp.ciaddr, client, sizeof bp.ciaddr); */ + p = optput(p, ODipaddr, client, sizeof(client)); + p = optput(p, ODserverid, server, sizeof(server)); + break; + case DHCPrelease: + memcpy(bp.ciaddr, client, sizeof(client)); + p = optput(p, ODipaddr, client, sizeof(client)); + p = optput(p, ODserverid, server, sizeof(server)); + break; + } + *p++ = OBend; + + ip = (how == Broadcast) ? IP(255, 255, 255, 255) : server; + udpsend(ip, sock, &bp, p - (unsigned char *)&bp); +} + +static int +dhcprecv(void) +{ + unsigned char type; + struct pollfd pfd[] = { + { .fd = sock, .events = POLLIN }, + { .fd = timers[0], .events = POLLIN }, + { .fd = timers[1], .events = POLLIN }, + { .fd = timers[2], .events = POLLIN }, + }; + uint64_t n; + + if (poll(pfd, LEN(pfd), -1) == -1) + eprintf("poll:"); + if (pfd[0].revents) { + memset(&bp, 0, sizeof(bp)); + udprecv(IP(255, 255, 255, 255), sock, &bp, sizeof(bp)); + optget(&bp, &type, ODtype, sizeof(type)); + return type; + } + if (pfd[1].revents) { + type = Timeout0; + read(timers[0], &n, sizeof(n)); + } + if (pfd[2].revents) { + type = Timeout1; + read(timers[1], &n, sizeof(n)); + } + if (pfd[3].revents) { + type = Timeout2; + read(timers[2], &n, sizeof(n)); + } + return type; +} + +static void +acceptlease(void) +{ + char buf[128]; + + if (iflag) + setip(client, mask, router); + if (dflag) + setdns(dns); + if (*program) { + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", server[0], server[1], server[2], server[3]); + setenv("SERVER", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", client[0], client[1], client[2], client[3]); + setenv("CLIENT", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3]); + setenv("MASK", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", router[0], router[1], router[2], router[3]); + setenv("ROUTER", buf, 1); + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", dns[0], dns[1], dns[2], dns[3]); + setenv("DNS", buf, 1); + system(program); + } +} + +static void +settimeout(int n, const struct itimerspec *ts) +{ + if (timerfd_settime(timers[n], 0, ts, NULL) < 0) + eprintf("timerfd_settime:"); +} + +/* sets ts to expire halfway to the expiration of timer n, minimum of 60 seconds */ +static void +calctimeout(int n, struct itimerspec *ts) +{ + if (timerfd_gettime(timers[n], ts) < 0) + eprintf("timerfd_gettime:"); + ts->it_value.tv_nsec /= 2; + if (ts->it_value.tv_sec % 2) + ts->it_value.tv_nsec += 500000000; + ts->it_value.tv_sec /= 2; + if (ts->it_value.tv_sec < 60) { + ts->it_value.tv_sec = 60; + ts->it_value.tv_nsec = 0; + } +} + +static void +run(void) +{ + int forked = 0, t; + struct itimerspec timeout = { 0 }; + uint32_t renewaltime, rebindingtime, lease; + +Init: + dhcpsend(DHCPdiscover, Broadcast); + timeout.it_value.tv_sec = 1; + timeout.it_value.tv_nsec = 0; + settimeout(0, &timeout); + goto Selecting; +Selecting: + for (;;) { + switch (dhcprecv()) { + case DHCPoffer: + memcpy(client, bp.yiaddr, sizeof(client)); + optget(&bp, server, ODserverid, sizeof(server)); + goto Requesting; + case Timeout0: + goto Init; + } + } +Requesting: + for (t = 4; t <= 64; t *= 2) { + dhcpsend(DHCPrequest, Broadcast); + timeout.it_value.tv_sec = t; + settimeout(0, &timeout); + for (;;) { + switch (dhcprecv()) { + case DHCPack: + goto Bound; + case DHCPnak: + goto Init; + case Timeout0: + break; + default: + continue; + } + break; + } + } + /* no response from DHCPREQUEST after several attempts, go to INIT */ + goto Init; +Bound: + optget(&bp, mask, OBmask, sizeof(mask)); + optget(&bp, router, OBrouter, sizeof(router)); + optget(&bp, dns, OBdnsserver, sizeof(dns)); + optget(&bp, &renewaltime, ODrenewaltime, sizeof(renewaltime)); + optget(&bp, &rebindingtime, ODrebindingtime, sizeof(rebindingtime)); + optget(&bp, &lease, ODlease, sizeof(lease)); + renewaltime = ntohl(renewaltime); + rebindingtime = ntohl(rebindingtime); + lease = ntohl(lease); + acceptlease(); + fputs("Congrats! You should be on the 'net.\n", stdout); + if (!fflag && !forked) { + if (fork()) + exit(0); + forked = 1; + } + timeout.it_value.tv_sec = renewaltime; + settimeout(0, &timeout); + timeout.it_value.tv_sec = rebindingtime; + settimeout(1, &timeout); + timeout.it_value.tv_sec = lease;; + settimeout(2, &timeout); + for (;;) { + switch (dhcprecv()) { + case Timeout0: /* t1 elapsed */ + goto Renewing; + case Timeout1: /* t2 elapsed */ + goto Rebinding; + case Timeout2: /* lease expired */ + goto Init; + } + } +Renewing: + dhcpsend(DHCPrequest, Unicast); + calctimeout(1, &timeout); + settimeout(0, &timeout); + for (;;) { + switch (dhcprecv()) { + case DHCPack: + goto Bound; + case Timeout0: /* resend request */ + goto Renewing; + case Timeout1: /* t2 elapsed */ + goto Rebinding; + case Timeout2: + case DHCPnak: + goto Init; + } + } +Rebinding: + calctimeout(2, &timeout); + settimeout(0, &timeout); + dhcpsend(DHCPrequest, Broadcast); + for (;;) { + switch (dhcprecv()) { + case DHCPack: + goto Bound; + case Timeout0: /* resend request */ + goto Rebinding; + case Timeout2: /* lease expired */ + case DHCPnak: + goto Init; + } + } +} + +static void +cleanexit(int unused) +{ + (void)unused; + dhcpsend(DHCPrelease, Unicast); + _exit(0); +} + +static void +usage(void) +{ + eprintf("usage: %s [-d] [-e program] [-f] [-i] [ifname] [clientid]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + int bcast = 1; + struct ifreq ifreq; + struct sockaddr addr; + int rnd; + size_t i; + + ARGBEGIN { + case 'd': /* don't update DNS in /etc/resolv.conf */ + dflag = 0; + break; + case 'e': /* run program */ + program = EARGF(usage()); + break; + case 'f': /* run in foreground */ + fflag = 1; + break; + case 'i': /* don't set ip */ + iflag = 0; + break; + default: + usage(); + break; + } ARGEND; + + if (argc) + ifname = argv[0]; /* interface name */ + if (argc >= 2) + strlcpy((char *)cid, argv[1], sizeof(cid)); /* client-id */ + + memset(&ifreq, 0, sizeof(ifreq)); + signal(SIGTERM, cleanexit); + + if (gethostname(hostname, sizeof(hostname)) == -1) + eprintf("gethostname:"); + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + eprintf("socket:"); + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast)) == -1) + eprintf("setsockopt:"); + + strlcpy(ifreq.ifr_name, ifname, IF_NAMESIZE); + ioctl(sock, SIOCGIFINDEX, &ifreq); + if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq)) == -1) + eprintf("setsockopt:"); + iptoaddr(&addr, IP(255, 255, 255, 255), 68); + if (bind(sock, (void*)&addr, sizeof(addr)) != 0) + eprintf("bind:"); + ioctl(sock, SIOCGIFHWADDR, &ifreq); + memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(ifreq.ifr_hwaddr.sa_data)); + if (!cid[0]) + memcpy(cid, hwaddr, sizeof(cid)); + + if ((rnd = open("/dev/urandom", O_RDONLY)) == -1) + eprintf("can't open /dev/urandom tidentifier:"); + read(rnd, xid, sizeof(xid)); + close(rnd); + + for (i = 0; i < LEN(timers); ++i) { + timers[i] = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC); + if (timers[i] == -1) + eprintf("timerfd_create:"); + } + + starttime = time(NULL); + run(); + + return 0; +} diff --git a/util.h b/util.h @@ -0,0 +1,10 @@ +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define LEN(a) (sizeof(a) / sizeof((a)[0])) +#define bpdump(p,n) 1 + +#undef strlcpy +size_t strlcpy(char *, const char *, size_t); + +void weprintf(const char *, ...); +void eprintf(const char *, ...); +void enprintf(int, const char *, ...); diff --git a/util/eprintf.c b/util/eprintf.c @@ -0,0 +1,65 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../util.h" + +char *argv0; + +static void venprintf(int, const char *, va_list); + +void +eprintf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(1, fmt, ap); + va_end(ap); +} + +void +enprintf(int status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + venprintf(status, fmt, ap); + va_end(ap); +} + +void +venprintf(int status, const char *fmt, va_list ap) +{ + if (strncmp(fmt, "usage", strlen("usage"))) + fprintf(stderr, "%s: ", argv0); + + vfprintf(stderr, fmt, ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } + + exit(status); +} + +void +weprintf(const char *fmt, ...) +{ + va_list ap; + + if (strncmp(fmt, "usage", strlen("usage"))) + fprintf(stderr, "%s: ", argv0); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } +} diff --git a/util/strlcpy.c b/util/strlcpy.c @@ -0,0 +1,32 @@ +/* Taken from OpenBSD */ +#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 */ +}