aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2026-09-06 00:52:48 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2026-09-26 23:30:45 +0200
commitbd7e4225d18207250cc312be601ae1615f62f0ad (patch)
tree8378788f0f6d42ea0612011f5adeba6bccb50ae7
parent3c0819236e4f07c8f57aaf1ca46d0d68aa761558 (diff)
downloadprc-bd7e4225d18207250cc312be601ae1615f62f0ad.tar.gz
Add prc front-end
-rw-r--r--src/prc/CMakeLists.txt14
-rw-r--r--src/prc/posix.c106
-rw-r--r--src/prc/prc.c695
-rw-r--r--src/prc/prc.h12
-rw-r--r--src/prc1/CMakeLists.txt3
-rw-r--r--src/prc1/kw.c2
-rw-r--r--src/prc1/prc1.c9
7 files changed, 836 insertions, 5 deletions
diff --git a/src/prc/CMakeLists.txt b/src/prc/CMakeLists.txt
new file mode 100644
index 0000000..631417d
--- /dev/null
+++ b/src/prc/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_executable(${PROJECT_NAME} ${PROJECT_NAME}.c)
+include(GNUInstallDirs)
+target_compile_definitions(${PROJECT_NAME} PRIVATE
+ PREFIX="${CMAKE_INSTALL_PREFIX}"
+ LIBEXECDIR="${CMAKE_INSTALL_LIBEXECDIR}/${PROJECT_NAME}")
+target_compile_options(${PROJECT_NAME} PRIVATE -g -Wall -pedantic)
+set_target_properties(${PROJECT_NAME} PROPERTIES C_STANDARD 99 C_EXTENSIONS OFF)
+if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ target_compile_options(${PROJECT_NAME} PRIVATE -Og)
+endif()
+if(UNIX)
+ target_sources(${PROJECT_NAME} PRIVATE posix.c)
+endif()
+install(TARGETS ${PROJECT_NAME})
diff --git a/src/prc/posix.c b/src/prc/posix.c
new file mode 100644
index 0000000..5510130
--- /dev/null
+++ b/src/prc/posix.c
@@ -0,0 +1,106 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include "prc.h"
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+struct spawn
+{
+ pid_t pid;
+};
+
+void spawn_free(struct spawn *s)
+{
+ free(s);
+}
+
+int spawn_wait(struct spawn *s)
+{
+ int stat;
+ pid_t pid = waitpid(s->pid, &stat, 0);
+
+ if (pid < 0)
+ {
+ perror("waitpid(2)");
+ return -1;
+ }
+ else if (WIFEXITED(stat) && WEXITSTATUS(stat) != EXIT_SUCCESS)
+ return -1;
+ else if (WIFSTOPPED(stat))
+ return -1;
+
+ return 0;
+}
+
+static int setupfd(FILE *f, int dst)
+{
+ int fd;
+
+ if (!f)
+ return 0;
+ else if ((fd = fileno(f)) < 0)
+ {
+ perror("fileno(3)");
+ return -1;
+ }
+ else if (dup2(fd, dst) < 0)
+ {
+ perror("dup(2)");
+ return -1;
+ }
+
+ return 0;
+}
+
+struct spawn *spawnvi(const char *path, char *const argv[], FILE *in, FILE *out)
+{
+ struct spawn *ret = malloc(sizeof *ret);
+
+ if (!ret)
+ {
+ perror("malloc(3)");
+ goto failure;
+ }
+
+ switch ((ret->pid = fork()))
+ {
+ case -1:
+ fprintf(stderr, "fork(2): %s: %s\n", path, strerror(errno));
+ goto failure;
+ case 0:
+ if (setupfd(in, STDIN_FILENO))
+ goto failure;
+ else if (setupfd(out, STDOUT_FILENO))
+ goto failure;
+ else if (execvp(path, argv))
+ {
+ fprintf(stderr, "execv(2): %s: %s\n", path, strerror(errno));
+ goto failure;
+ }
+ default:
+ break;
+ }
+
+ return ret;
+
+failure:
+ free(ret);
+ return NULL;
+}
+
+struct spawn *spawnv(const char *path, char *const argv[], FILE *out)
+{
+ return spawnvi(path, argv, NULL, out);
+}
+
+struct spawn *spawni(const char *path, FILE *in, FILE *out)
+{
+ char *v[] = {(char *)path, NULL};
+
+ return spawnvi(path, v, in, out);
+}
diff --git a/src/prc/prc.c b/src/prc/prc.c
new file mode 100644
index 0000000..fa2e751
--- /dev/null
+++ b/src/prc/prc.c
@@ -0,0 +1,695 @@
+#include "prc.h"
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct input
+{
+ const char **v;
+ size_t n;
+};
+
+struct rwinput
+{
+ char **v;
+ size_t n;
+};
+
+struct dstr
+{
+ char *s;
+};
+
+struct proc
+{
+ struct spawn *s;
+ char *path;
+ FILE *out;
+};
+
+struct procs
+{
+ struct proc *p;
+ size_t n;
+};
+
+struct ldflags
+{
+ const char **v;
+ size_t n;
+};
+
+struct state
+{
+ const char *as, *qbe, *ld, *prefix, *libexecdir, *name, *out;
+ const struct arg *arg;
+ struct input sources, incs, libs, ldirs;
+ struct procs srcprocs, qbeprocs, asprocs;
+ struct ldflags ldflags;
+ int componly, debug, verbose;
+};
+
+struct arg
+{
+ const char *s;
+ int (*f)(struct state *, const char *);
+
+ enum
+ {
+ NOVAL,
+ SPACEVAL = 1,
+ EQVAL = 1 << 1,
+ JOINVAL = 1 << 2
+ } v;
+};
+
+static int comp(struct state *s, const char *val)
+{
+ s->componly = 1;
+ return 0;
+}
+
+static int debug(struct state *s, const char *val)
+{
+ s->debug = 1;
+ return 0;
+}
+
+static int verbose(struct state *s, const char *val)
+{
+ s->verbose = 1;
+ return 0;
+}
+
+static int ldflag(struct state *s, const char *val)
+{
+ struct ldflags *lf = &s->ldflags;
+ size_t n = lf->n + 1;
+ const char **v = realloc(lf->v, n * sizeof *v);
+
+ if (!v)
+ {
+ perror("realloc(3)");
+ return -1;
+ }
+
+ v[lf->n++] = val;
+ lf->v = v;
+ return 0;
+}
+
+static int optim(struct state *s, const char *val)
+{
+ return 0;
+}
+
+static int out(struct state *s, const char *val)
+{
+ s->out = val;
+ return 0;
+}
+
+static int addinput(struct state *s, struct input *i, const char *val)
+{
+ size_t n = i->n + 1;
+ const char **v = realloc(i->v, n * sizeof *v);
+
+ if (!v)
+ {
+ perror("realloc(3)");
+ return -1;
+ }
+
+ v[i->n++] = val;
+ i->v = v;
+ s->arg = NULL;
+ return 0;
+}
+
+static int inc(struct state *s, const char *val)
+{
+ return addinput(s, &s->incs, val);
+}
+
+static int lib(struct state *s, const char *val)
+{
+ return addinput(s, &s->libs, val);
+}
+
+static int ldir(struct state *s, const char *val)
+{
+ return addinput(s, &s->ldirs, val);
+}
+
+static int opt(struct state *s, const struct arg *a, const char *arg)
+{
+ const struct arg *prev = s->arg;
+ const char *v = arg + strlen(a->s);
+
+ if (prev && prev->v)
+ {
+ fprintf(stderr, "%s: option '%s' requires a value\n", s->name, prev->s);
+ return -1;
+ }
+ else if (*v)
+ {
+ if (!(a->v & JOINVAL))
+ {
+ fprintf(stderr, "%s: unrecognized command-line option '%s'\n",
+ s->name, arg);
+ return -1;
+ }
+ }
+ else if (a->v & SPACEVAL)
+ {
+ s->arg = a;
+ return 0;
+ }
+ else
+ v = NULL;
+
+ return a->f(s, v);
+}
+
+static int addfile(struct state *s, const char *arg)
+{
+ return addinput(s, &s->sources, arg);
+}
+
+static int arg(struct state *s, const char *arg)
+{
+ static const struct arg args[] =
+ {
+ {"-c", comp},
+ {"-d", debug},
+ {"-v", verbose},
+ {"-O", optim, JOINVAL},
+ {"-Wl,", ldflag, JOINVAL},
+ {"-o", out, SPACEVAL | JOINVAL},
+ {"-I", inc, SPACEVAL | JOINVAL},
+ {"-l", lib, SPACEVAL | JOINVAL},
+ {"-L", ldir, SPACEVAL | JOINVAL}
+ };
+
+ const struct arg *prev = s->arg;
+
+ for (size_t i = 0; i < sizeof args / sizeof *args; i++)
+ {
+ const struct arg *a = &args[i];
+
+ if (!strncmp(arg, a->s, strlen(a->s)))
+ return opt(s, a, arg);
+ }
+
+ if (prev)
+ {
+ if (*arg != '-' && prev->f(s, arg))
+ return -1;
+
+ s->arg = NULL;
+ }
+ else
+ return addfile(s, arg);
+
+ return 0;
+}
+
+static int append(struct dstr *d, const char *fmt, ...)
+{
+ int ret = 1, n;
+ char *s;
+ va_list ap;
+ size_t len = d->s ? strlen(d->s) : 0;
+
+ va_start(ap, fmt);
+
+ if ((n = vsnprintf(NULL, 0, fmt, ap)) < 0)
+ {
+ fprintf(stderr, "vsnprintf(3) failed\n");
+ goto end;
+ }
+ else if (!(s = realloc(d->s, len + n + 1)))
+ {
+ perror("realloc(3)");
+ goto end;
+ }
+
+ va_end(ap);
+ va_start(ap, fmt);
+ vsprintf(s + len, fmt, ap);
+ d->s = s;
+ ret = 0;
+
+end:
+ va_end(ap);
+ return ret;
+}
+
+static void free_dstr(struct dstr *d)
+{
+ free(d->s);
+}
+
+static int as(struct state *s, const char *src)
+{
+ fprintf(stderr, "%s: TODO\n", __func__);
+ return -1;
+}
+
+static void printcmd(const struct dstr *path, const struct dstr *args)
+{
+ fprintf(stderr, "%s ", path->s);
+
+ while (args->s)
+ fprintf(stderr, "%s ", (args++)->s);
+
+ fputc('\n', stderr);
+}
+
+static void printargv(char *const argv[])
+{
+ while (*argv)
+ fprintf(stderr, "%s ", *argv++);
+
+ fputc('\n', stderr);
+}
+
+static int prc1(struct state *s, const char *src)
+{
+ int ret = -1;
+ const struct input *incs = &s->incs;
+ struct procs *srcprocs = &s->srcprocs;
+ FILE *out = NULL;
+ struct dstr path = {0}, *args = NULL;
+ /* mandatory arguments:
+ * 1: argv[0]
+ * 2: src
+ * [-d]
+ * <includes>
+ * 3: argv[n]
+ */
+ size_t n = incs->n + 3, npr = srcprocs->n + 1, argi = 0;
+ struct spawn *proc;
+ struct proc *sp;
+
+ if (s->debug)
+ n++;
+
+ if (!(args = malloc(n * sizeof *args)))
+ {
+ perror("malloc(3)");
+ goto end;
+ }
+
+ for (size_t i = 0; i < n; i++)
+ args[i] = (struct dstr){0};
+
+ if (append(&args[argi++], "prc1")
+ || append(&args[argi++], src)
+ || (s->debug && append(&args[argi++], "-d")))
+ goto end;
+ else if (!(sp = realloc(srcprocs->p, npr * sizeof *sp)))
+ {
+ perror("realloc(3)");
+ goto end;
+ }
+
+ srcprocs->p = sp;
+
+ if (append(&path, "%s/%s/prc1", s->prefix, s->libexecdir))
+ goto end;
+
+ for (size_t i = 0; i < incs->n; i++)
+ if (append(&args[argi++], "-I%s", incs->v[i]))
+ goto end;
+
+ if (!(out = tmpfile()))
+ {
+ perror("tmpfile(3)");
+ goto end;
+ }
+ else if (s->verbose)
+ printcmd(&path, args);
+
+ /* HACK: struct dstr is ultimately syntax sugar for char *. */
+ if (!(proc = spawnv(path.s, (char **)args, out)))
+ goto end;
+
+ sp[srcprocs->n++] = (struct proc){.out = out, .s = proc};
+ ret = 0;
+
+end:
+ if (args)
+ for (size_t i = 0; i < n; i++)
+ free_dstr(&args[i]);
+
+ if (ret && out && fclose(out))
+ perror("fclose(3)");
+
+ free(args);
+ free_dstr(&path);
+ return ret;
+}
+
+static int qbe(struct state *s, const char *src)
+{
+ fprintf(stderr, "%s: TODO\n", __func__);
+ return -1;
+}
+
+static int runsuffix(struct state *s, const char *src, const char *suffix,
+ int (*f)(struct state *, const char *))
+{
+ const char *sep = strchr(src, '.');
+
+ if (sep)
+ return strcmp(++sep, suffix) ? 0 : f(s, src);
+
+ return f(s, src);
+}
+
+static int waitall(const struct procs *pr)
+{
+ for (size_t i = 0; i < pr->n; i++)
+ {
+ const struct proc *p = &pr->p[i];
+
+ if (spawn_wait(p->s))
+ return -1;
+ /* prepare stream for read. */
+ else if (p->out && fseek(p->out, 0, SEEK_SET))
+ {
+ perror("fseek(3)");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int runprc1(struct state *s)
+{
+ const struct input *srcs = &s->sources;
+
+ for (size_t i = 0; i < srcs->n; i++)
+ if (runsuffix(s, srcs->v[i], "prose", prc1))
+ return -1;
+
+ return waitall(&s->srcprocs);
+}
+
+static int redirect(const char *path, FILE *in, struct procs *prs)
+{
+ FILE *out = NULL;
+ size_t n = prs->n + 1;
+ struct spawn *sp = NULL;
+ struct proc *procs = realloc(prs->p, n * sizeof *procs), *p;
+
+ if (!procs)
+ {
+ perror("realloc(3)");
+ goto failure;
+ }
+
+ p = &procs[prs->n++];
+ *p = (struct proc){0};
+ prs->p = procs;
+
+ if (!(out = tmpfile()))
+ {
+ perror("tmpfile(3)");
+ goto failure;
+ }
+ else if (!(sp = spawni(path, in, out)))
+ goto failure;
+
+ *p = (struct proc){.s = sp, .out = out};
+ return 0;
+
+failure:
+
+ if (out && fclose(out))
+ perror("fclose(3)");
+
+ return -1;
+}
+
+static int runqbe(struct state *s)
+{
+ const struct input *srcs = &s->sources;
+ const struct procs *srcpr = &s->srcprocs;
+ struct procs *qbepr = &s->qbeprocs;
+
+ for (size_t i = 0; i < srcs->n; i++)
+ if (runsuffix(s, srcs->v[i], "qbe", qbe))
+ return -1;
+
+ for (size_t i = 0; i < srcpr->n; i++)
+ if (redirect(s->qbe, srcpr->p[i].out, qbepr))
+ return -1;
+
+ return waitall(qbepr);
+}
+
+static int dump(const char *path, FILE *in, struct procs *prs)
+{
+ struct spawn *s = NULL;
+ struct proc *p, *np;
+ size_t n = prs->n + 1;
+ char *argv[4] = {(char *)path, "-o"}, *tmp = malloc(L_tmpnam);
+
+ if (!tmp)
+ {
+ perror("malloc(3)");
+ goto failure;
+ }
+ else if (!(argv[2] = tmpnam(tmp)))
+ {
+ fprintf(stderr, "%s: tmpnam(3) failed\n", __func__);
+ goto failure;
+ }
+ else if (!(p = realloc(prs->p, n * sizeof *p)))
+ {
+ perror("realloc(3)");
+ goto failure;
+ }
+
+ np = &p[prs->n++];
+ *np = (struct proc){0};
+ prs->p = p;
+
+ if (!(s = spawnvi(path, argv, in, NULL)))
+ goto failure;
+
+ *np = (struct proc){.s = s, .path = tmp};
+ return 0;
+
+failure:
+ free(tmp);
+ spawn_free(s);
+ return -1;
+}
+
+static int runas(struct state *s)
+{
+ const struct input *srcs = &s->sources;
+ const struct procs *qbepr = &s->qbeprocs;
+ struct procs *aspr = &s->asprocs;
+
+ for (size_t i = 0; i < srcs->n; i++)
+ if (runsuffix(s, srcs->v[i], "s", as))
+ return -1;
+
+ for (size_t i = 0; i < qbepr->n; i++)
+ if (dump(s->as, qbepr->p[i].out, aspr))
+ return -1;
+
+ return waitall(aspr);
+}
+
+static int isobj(const char *path)
+{
+ static const char ext[] = ".o";
+ const char *src = path, *r = strstr(src, ext);
+
+ return r && strlen(r) == strlen(ext);
+}
+
+static int ld(struct state *s)
+{
+ int ret = -1;
+ const struct input *srcs = &s->sources;
+ const struct procs *asp = &s->asprocs;
+ const struct ldflags *lf = &s->ldflags;
+ /*
+ * argv[0]
+ * -o
+ * <out>
+ * ...
+ * <ldflags>
+ * -lc
+ * argv[n]
+ */
+ size_t n = asp->n + lf->n + 5, i = 0;
+ struct spawn *sp = NULL;
+ char **argv = NULL;
+
+ for (size_t i = 0; i < srcs->n; i++)
+ if (isobj(srcs->v[i]))
+ n++;
+
+ if (!(argv = malloc(n * sizeof *argv)))
+ {
+ perror("malloc(3)");
+ goto end;
+ }
+
+ argv[i++] = (char *)s->ld;
+ argv[i++] = "-o";
+ argv[i++] = (char *)s->out;
+
+ for (size_t j = 0; j < asp->n; j++)
+ argv[i++] = asp->p[j].path;
+
+ for (size_t j = 0; j < srcs->n; j++)
+ {
+ const char *path = srcs->v[j];
+
+ if (isobj(path))
+ argv[i++] = (char *)path;
+ }
+
+ for (size_t j = 0; j < lf->n; j++)
+ argv[i++] = (char *)lf->v[j];
+
+ argv[i++] = "-lc";
+ argv[i++] = NULL;
+
+ if (s->verbose)
+ printargv(argv);
+
+ if (!(sp = spawnv(s->ld, argv, NULL)) || spawn_wait(sp))
+ goto end;
+
+ ret = 0;
+end:
+ free(argv);
+ spawn_free(sp);
+ return ret;
+}
+
+static int reconcile(struct state *s)
+{
+ if (runprc1(s) || runqbe(s) || runas(s))
+ return -1;
+
+ return s->componly ? 0 : ld(s);
+}
+
+static void free_input(struct input *i)
+{
+ free(i->v);
+}
+
+static void free_procs(struct procs *v)
+{
+ for (size_t i = 0; i < v->n; i++)
+ {
+ struct proc *p = &v->p[i];
+ FILE *out = p->out;
+ char *path = p->path;
+
+ if (out && fclose(out))
+ perror("fclose(3)");
+
+ if (path && remove(path))
+ fprintf(stderr, "%s: remove(3) %s: %s\n", __func__, path,
+ strerror(errno));
+
+ free(path);
+ spawn_free(p->s);
+ }
+
+ free(v->p);
+}
+
+static void free_state(struct state *s)
+{
+ free_input(&s->sources);
+ free_input(&s->incs);
+ free_input(&s->libs);
+ free_input(&s->ldirs);
+ free_procs(&s->srcprocs);
+ free_procs(&s->qbeprocs);
+ free_procs(&s->asprocs);
+ free(s->ldflags.v);
+}
+
+static void setdefaults(struct state *s)
+{
+ if (!s->ld)
+ s->ld = "ld";
+
+ if (!s->as)
+ s->as = "as";
+
+ if (!s->qbe)
+ s->qbe = "qbe";
+
+ if (!s->prefix)
+ s->prefix = PREFIX;
+
+ if (!s->libexecdir)
+ s->libexecdir = LIBEXECDIR;
+
+ s->out = "a.out";
+}
+
+int main(int argc, char *argv[])
+{
+ int ret = EXIT_FAILURE;
+ struct state s =
+ {
+ .name = *argv,
+ .out = "a.out",
+ .ld = getenv("LD"),
+ .as = getenv("AS"),
+ .qbe = getenv("PRCQBE"),
+ .prefix = getenv("PRCPREFIX"),
+ .libexecdir = getenv("PRCLIBEXECDIR")
+ };
+
+ if (argc == 1)
+ {
+ fprintf(stderr, "%s [options ...] file...\n", *argv);
+ goto end;
+ }
+
+ setdefaults(&s);
+
+ for (int i = 1; i < argc; i++)
+ if (arg(&s, argv[i]))
+ goto end;
+
+ if (s.arg)
+ {
+ fprintf(stderr, "%s: command-line argument '%s' requires an argument\n",
+ s.name, s.arg->s);
+ goto end;
+ }
+ else if (!s.sources.n)
+ {
+ fprintf(stderr, "%s: missing input files\n", __func__);
+ goto end;
+ }
+ else if (reconcile(&s))
+ goto end;
+
+ ret = EXIT_SUCCESS;
+end:
+ free_state(&s);
+ return ret;
+}
diff --git a/src/prc/prc.h b/src/prc/prc.h
new file mode 100644
index 0000000..56f7807
--- /dev/null
+++ b/src/prc/prc.h
@@ -0,0 +1,12 @@
+#ifndef PRC_H
+#define PRC_H
+
+#include <stdio.h>
+
+struct spawn *spawni(const char *s, FILE *in, FILE *out);
+struct spawn *spawnv(const char *s, char *const argv[], FILE *out);
+struct spawn *spawnvi(const char *s, char *const argv[], FILE *in, FILE *out);
+int spawn_wait(struct spawn *s);
+void spawn_free(struct spawn *s);
+
+#endif
diff --git a/src/prc1/CMakeLists.txt b/src/prc1/CMakeLists.txt
index 3579ca6..e2b9ad4 100644
--- a/src/prc1/CMakeLists.txt
+++ b/src/prc1/CMakeLists.txt
@@ -36,4 +36,5 @@ set_target_properties(prc1 PROPERTIES C_STANDARD 99 C_EXTENSIONS OFF)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(prc1 PRIVATE -Og)
endif()
-# install(TARGETS prc1)
+include(GNUInstallDirs)
+install(TARGETS prc1 DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/${PROJECT_NAME})
diff --git a/src/prc1/kw.c b/src/prc1/kw.c
index d5e22ca..c72ac97 100644
--- a/src/prc1/kw.c
+++ b/src/prc1/kw.c
@@ -18,7 +18,7 @@ static const char *kws[] =
/* built-in functions */
"display", "warn",
/* flow control */
- "while", "if", "else", "end", "return", "go", "label", "default",
+ "while", "if", "else", "end", "return", "go", "label", /*"default",*/
/* prepositions */
"by", "from", "to", "than", "of", "etc",
/* comparators */
diff --git a/src/prc1/prc1.c b/src/prc1/prc1.c
index 69165ba..6378907 100644
--- a/src/prc1/prc1.c
+++ b/src/prc1/prc1.c
@@ -14,16 +14,19 @@ int main(int argc, char *argv[])
FILE *f = stdin;
int opened = 0, ret = EXIT_FAILURE;
- if (argc != 2)
+ for (int i = 1; i < argc; i++)
+ fprintf(stderr, "[%d]=%s\n", i, argv[i]);
+
+ if (argc < 2)
{
- fprintf(stderr, "%s: error: missing input file\n", *argv);
+ fprintf(stderr, "%s [options ...] file\n", *argv);
goto end;
}
else if (strcmp(argv[1], "-"))
{
if (!(f = fopen(argv[1], "rb")))
{
- fprintf(stderr, "failed to open %s: %s\n", argv[1],
+ fprintf(stderr, "%s: failed to open %s: %s\n", *argv, argv[1],
strerror(errno));
goto end;
}