Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F109244219
D39327.id119634.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D39327.id119634.diff
View Options
diff --git a/usr.bin/Makefile b/usr.bin/Makefile
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -61,6 +61,7 @@
hexdump \
id \
ident \
+ preserve \
ipcrm \
ipcs \
join \
diff --git a/usr.bin/preserve/Makefile b/usr.bin/preserve/Makefile
new file mode 100644
--- /dev/null
+++ b/usr.bin/preserve/Makefile
@@ -0,0 +1,3 @@
+PROG = preserve
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/preserve/preserve.1 b/usr.bin/preserve/preserve.1
new file mode 100644
--- /dev/null
+++ b/usr.bin/preserve/preserve.1
@@ -0,0 +1,112 @@
+.\"-
+.\" Copyright (c) 2023 Dag-Erling Smørgrav
+.\"
+.\" 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 AUTHOR 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 AUTHOR 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.
+.\"
+.Dd March 29, 2023
+.Dt PRESERVE 1
+.Os
+.Sh NAME
+.Nm preserve
+.Nd run a command and replace its output only if it has changed
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl o Ar filename
+.Ar command
+.Op Ar args
+.Sh DESCRIPTION
+The
+.Nm
+utility runs the specified command, which is expected to either write to
+.Va stdout
+or produce a single output file.
+.Pp
+If the
+.Fl o
+option is provided before the command, the command is assumed to write
+its output to
+.Va stdout ,
+and
+.Nm
+redirects it to a temporary file.
+.Pp
+If the
+.Fl o
+option is not provided,
+.Nm
+scans
+.Ar args
+to determine the output file name.
+If
+.Ar args
+includes a word that begins with
+.Dq Fl o ,
+the remainder of that word is taken to be the output file name.
+Otherwise, if it includes the word
+.Dq Fl o
+in a non-terminal position, the next word is taken to be the output
+file name.
+Otherwise, if the last word of
+.Ar args
+does not begin with a hyphen, it is taken to be the output file name.
+In all cases, the actual filename is replaced in
+.Ar args
+with a temporary filename.
+.Pp
+If
+.Nm
+is unable to determine the output file name, the original command is
+executed without further interference.
+.Pp
+Otherwse, if the command succeeds and the final output file either
+does not exist, or exists but its contents differ from those of the
+temporary file, the temporary file is renamed to the final output
+file.
+In all other cases, the temporary file is deleted.
+.Pp
+The following options are available:
+.Bl -tag -width Fl
+.It Fl o Ar filename
+Assume that
+.Ar command
+writes its output to
+.Va stdout
+rather than to a file, and use
+.Ar filename
+as the final output file name.
+.It Fl v
+Write additional information to
+.Va stderr
+explaining whether the output file was retained or replaced and why.
+.El
+.Sh EXIT STATUS
+The exit status of the
+.Nm
+utility is that of the command it ran, unless it failed to run the
+command or an error occurred while handling the output, in which case
+its exit status is 1.
+.Sh AUTHORS
+The
+.Nm
+command and this manual page were written by
+.An Dag-Erling Sm\(/orgrav Aq Mt des@FreeBSD.org .
diff --git a/usr.bin/preserve/preserve.c b/usr.bin/preserve/preserve.c
new file mode 100644
--- /dev/null
+++ b/usr.bin/preserve/preserve.c
@@ -0,0 +1,216 @@
+/*-
+ * Copyright (c) 2023 Dag-Erling Smørgrav
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static bool vflag;
+
+static bool
+files_are_identical(const char *afn, const char *bfn)
+{
+ static char abuf[4096], bbuf[4096];
+ struct stat asb, bsb;
+ ssize_t alen, blen;
+ off_t off;
+ int afd = -1, bfd = -1, i;
+ bool identical = false;
+
+ if ((afd = open(afn, O_RDONLY)) < 0) {
+ if (errno != ENOENT)
+ warn("%s", afn);
+ goto done;
+ }
+ if ((bfd = open(bfn, O_RDONLY)) < 0) {
+ if (errno != ENOENT)
+ warn("%s", bfn);
+ goto done;
+ }
+ if (fstat(afd, &asb) != 0 || fstat(bfd, &bsb) != 0) {
+ /* can't happen */
+ warn("fstat()");
+ goto done;
+ }
+ if (asb.st_size != bsb.st_size) {
+ if (vflag) {
+ warnx("%s and %s differ in length: %zu %c %zu",
+ afn, bfn, (size_t)asb.st_size,
+ asb.st_size < bsb.st_size ? '<' : '>',
+ (size_t)bsb.st_size);
+ }
+ goto done;
+ }
+ off = 0;
+ do {
+ if ((alen = read(afd, abuf, sizeof(abuf))) < 0) {
+ warn("%s", afn);
+ goto done;
+ }
+ if ((blen = read(bfd, bbuf, sizeof(bbuf))) < 0) {
+ warn("%s", bfn);
+ goto done;
+ }
+ if (alen != blen) {
+ /* either or both have changed since fstat() */
+ if (vflag)
+ warnx("%s and %s differ in length\n", afn, bfn);
+ goto done;
+ }
+ if (memcmp(abuf, bbuf, alen) != 0) {
+ if (vflag) {
+ for (i = 0; i < alen; i++)
+ if (abuf[i] != bbuf[i])
+ break;
+ warnx("%s and %s differ at offset %zu\n",
+ afn, bfn, (size_t)(off + i));
+ }
+ goto done;
+ }
+ off += alen;
+ } while (alen > 0);
+ if (vflag)
+ warnx("%s and %s are identical", afn, bfn);
+ identical = true;
+done:
+ if (afd >= 0)
+ close(afd);
+ if (bfd >= 0)
+ close(bfd);
+ return (identical);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: preserve [-v] [-o file] command [args]\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *fn = NULL, *tmp = NULL;
+ pid_t pid;
+ int opt, serrno, wstatus;
+
+ while ((opt = getopt(argc, argv, "o:v")) != -1)
+ switch (opt) {
+ case 'o':
+ fn = optarg;
+ break;
+ case 'v':
+ vflag = true;
+ break;
+ default:
+ usage();
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc == 0)
+ usage();
+
+ if (fn != NULL) {
+ if (asprintf(&tmp, "%s.tmp", fn) < 0)
+ err(1, "asprintf()");
+ if (freopen(tmp, "w", stdout) == NULL)
+ err(1, "%s", tmp);
+ } else if (argc > 1) {
+ /* look for output file name in arguments */
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-' && argv[i][1] == 'o') {
+ if (argv[i][2] != '\0') {
+ fn = &argv[i][2];
+ if (asprintf(&tmp, "-o%s.tmp", fn) < 0)
+ err(1, "asprintf()");
+ argv[i] = tmp;
+ tmp += 2;
+ } else if (i + 1 < argc) {
+ fn = argv[i + 1];
+ if (asprintf(&tmp, "%s.tmp", fn) < 0)
+ err(1, "asprintf()");
+ argv[i + 1] = tmp;
+ }
+ break;
+ }
+ }
+ if (fn == NULL && argv[argc - 1][0] != '-') {
+ fn = argv[argc - 1];
+ if (asprintf(&tmp, "%s.tmp", fn) < 0)
+ err(1, "asprintf()");
+ argv[argc - 1] = tmp;
+ }
+ }
+ if (fn == NULL) {
+ /* still no output file, just run the original command */
+ if (vflag)
+ warnx("unable to determine output file name");
+ execvp(argv[0], argv);
+ err(1, "%s", argv[0]);
+ }
+
+ if ((pid = fork()) < 0)
+ err(1, "fork()");
+ if (pid == 0) {
+ /* child */
+ execvp(argv[0], argv);
+ err(1, "%s", argv[0]);
+ }
+
+ /* parent */
+ setprogname(argv[0]);
+ (void)freopen("/dev/null", "a", stdout);
+
+ if (waitpid(pid, &wstatus, 0) < 0)
+ err(1, "waitpid()");
+ if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
+ (void)unlink(tmp);
+ exit(wstatus & 0xff);
+ }
+ if (files_are_identical(fn, tmp)) {
+ if (vflag)
+ warnx("removing %s", tmp);
+ (void)unlink(tmp);
+ } else {
+ if (vflag)
+ warnx("replacing %s with %s", fn, tmp);
+ if (rename(tmp, fn) != 0) {
+ serrno = errno;
+ (void)unlink(tmp);
+ errno = serrno;
+ err(1, "%s", fn);
+ }
+ }
+ exit(0);
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Feb 3, 1:13 PM (21 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16434529
Default Alt Text
D39327.id119634.diff (9 KB)
Attached To
Mode
D39327: preserve: run a command and replace its output only if it has changed
Attached
Detach File
Event Timeline
Log In to Comment