bmv

Batch move
Log | Files | Refs | README

commit 38eb668d0df6532c29a2c4a7f6709507776fddfe
parent d30cdb930cf585d78231fb78eea295588bb30764
Author: Jacob R. Edwards <jacob@jacobedwards.org>
Date:   Sun, 16 Apr 2023 17:08:24 -0700

Add the first version of bmv and pmv

They're not quite as described in the initial README (which has now
been updated to reflect their actual behavior), but to have bmv
undo it's progress on a failure would be ugly and prone to error.
Better to fail in a consistent way.

Diffstat:
AMakefile | 38++++++++++++++++++++++++++++++++++++++
MREADME | 25+++++++------------------
Abmv.1 | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abmv.c | 357+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/Makefile | 20++++++++++++++++++++
Alib/bprintf.c | 31+++++++++++++++++++++++++++++++
Alib/bprintf.h | 1+
Alib/buf.c | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/buf.h | 12++++++++++++
Alib/copy.c | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/copy.h | 1+
Alib/exists.c | 25+++++++++++++++++++++++++
Alib/exists.h | 1+
Alib/list.c | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/list.h | 19+++++++++++++++++++
Alib/mkdirs.c | 43+++++++++++++++++++++++++++++++++++++++++++
Alib/mkdirs.h | 1+
Alib/slurp.c | 42++++++++++++++++++++++++++++++++++++++++++
Alib/slurp.h | 1+
Alib/split.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/split.h | 1+
Apmv.1 | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Apmv.sh | 30++++++++++++++++++++++++++++++
23 files changed, 1131 insertions(+), 18 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,38 @@ +cc = ${CC} +cflags = -Wall -Wno-write-strings ${CFLAGS} +ldflags = ${LDFLAGS} +prefix = /usr/local +manprefix = ${prefix}/man + +names = bmv pmv +obj = bmv.o + +all: ${names} + +${names}: lib ${obj} + +lib: + make -C lib + +.c.o: + ${cc} ${cflags} -c -o $@ $< + +pmv: + ln -f $@.sh $@ + +${names}: ${obj} + ${cc} -o $@ ${@}.o lib/*.o ${ldflags} + +clean: + rm -f ${names} ${obj} + +install: ${names} + cp -f ${names} ${prefix}/bin + cp -f ${names:=.1} ${manprefix}/man1 + +uninstall: + cd ${prefix}/bin && rm -f ${names} + cd ${manprefix}/man1 && rm -f ${names:=.1} + +.SUFFIXES: .c .o +.PHONY: clean install uninstall lib diff --git a/README b/README @@ -1,24 +1,13 @@ This is the bmv ("Batch Move") project. -bmv ("Batch Move") is a program which takes two lists of names (in -the form of two files of line delimited names) and moves each name -from the first list to it's corresponding name in the second list. - -While the process cannot be atomic to my knowledge (with the syscalls -commonly provided), this program attempts to either succeed fully, -or fail fully; never halfway. It does this by first link(2)ing each -file to it's new name, then by removing all the old references -unless an error ocurrs during the process in which case it instead -removes the new references. One issue I can see with this method -is that if permissions change during the operation, the process may -be forced to stop midway. - -This program may provide an option to separate names by nul bytes -so as to fully support any valid filename the system does. It may -also provide an option to create any missing directories before the -link(3) occurs, or this feature may be handed off to a separate -program. +bmv ("Batch Move") is a program which takes two lists of names +delimited by lines and moves each name from the first list to it's +corresponding name in the second list. The program provides an +option to separate names by nul bytes so as to fully support any +valid filename the system does. pmv ("Pipe Move") is another program included in the project. It takes only a single list and uses it along with a filter to produce a second list, providing both to bmv. + +See the bmv(1) and pmv(1) manuals for more information. diff --git a/bmv.1 b/bmv.1 @@ -0,0 +1,89 @@ +.\" +.\" Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> +.\" +.\" 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. +.\" +.Dd April 13, 2023 +.Dt BMV 1 +.Os +.Sh NAME +.Nm bmv +.Nd batch move +.Sh SYNOPSIS +.Nm +.Op Fl 0cdflmnor +.Ar list1 +.Op Ar list2 +.Sh DESCRIPTION +The +.Nm +utility is a program which takes two lists of names delimited by +lines and moves each name from the first list to it's corresponding +name in the second list. As it is moved, each destination is printed +to the standard output. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl 0 +Use nul to delimit names instead of newline. (This also changes the +output delimiter to nul.) +.It Fl c +Copy files using +.Xr cp 1 +instead of +using +.Xr rename 2 . +.It Fl d +Create parent directories of destination files if they are missing. +.It Fl f +Only print a warning and otherwise ignore errors with file moving +or directory creation. +.It Fl l +Use +.Xr link 2 +instead of +.Xr rename 2 . +.It Fl m +Use +.Xr rename 2 . +This is the default. +.It Fl n +The null action; do nothing instead of +.Xr rename 2 . +.It Fl o +Allow overwriting of files. +.Fl ( f +would also allow this, but would raise errors.) +.It Fl r +Allow removal of files by specifying zero-length destination names. +.It Fl v +Shows source and destination names, but in a less useful format +than the default. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Pp +Prefix files in the current directory with 'prefix-'. +.Pp +.Dl $ ls > /tmp/a && sed s/^/prefix-/ < /tmp/a | bmv /tmp/a +.Pp +Remove files with 'bad' contained within their name and replace +'tipo' with 'typo' in the rest. +.Pp +.Dl $ mkfifo /tmp/x && tee /tmp/x | sed -e /bad/d -e s/tipo/typo/g | bmv -r /tmp/x +.Sh SEE ALSO +.Xr pmv 1 , +.Xr cp 1 +.Sh AUTHORS +.An Jacob R. Edwards Aq Mt jacob@jacobedwards.org diff --git a/bmv.c b/bmv.c @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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. + */ + +#define const + +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "lib/bprintf.h" +#include "lib/buf.h" +#include "lib/copy.h" +#include "lib/exists.h" +#include "lib/list.h" +#include "lib/mkdirs.h" +#include "lib/slurp.h" +#include "lib/split.h" + +static char *name; + +void +warn(char *msg) +{ + char *p; + + fprintf(stderr, "%s: %s", name, msg); + if ((p = strrchr(msg, ':')) && !p[1]) + fprintf(stderr, " %s", strerror(errno)); + fputc('\n', stderr); +} + +void +die(char *msg) +{ + warn(msg); + exit(1); +} + +void +usage(char *msg) +{ + if (msg) + warn(msg); + fprintf(stderr, "usage: %s [-0cdflmnorv] list1 [list2]\n", name); + exit(1); +} + +int +nothing(char *a, char *b) +{ + return 0; +} + +char ** +makepair(char *a, char *b) +{ + char **pair; + + pair = calloc(sizeof(*pair), 2); + if (!pair) + return NULL; + pair[0] = a; + pair[1] = b; + return pair; +} + +char * +from(char **pair) +{ + return pair[0]; +} + +char * +to(char **pair) +{ + return pair[1]; +} + +char * +combine(struct list *list1, struct list *list2, struct list **list3p) +{ + struct list *list3; + struct item *a, *b; + struct item *na, *nb; + char **pair; + + list3 = newlist(free); + if (!list3) + return "Unable to create list"; + *list3p = list3; + + for (a = list1->items, b = list2->items; a && b; + a = na, b = nb) { + pair = makepair(a->data, b->data); + if (!pair) + return "Unable to make pair"; + + a->data = pair; + na = a->next; + nb = b->next; + removeitem(a, list1); + deleteitem(b, list2); + additem(a, list3, list3->last); + } + + if (a) + return "Too many items in the first list"; + else if (b) + return "Too many items in the second list"; + + freelist(list1); + freelist(list2); + return NULL; +} + +char * +filter(struct list *list) +{ + struct item *i, *j, *next; + + for (i = list->items; i; i = next) { + if (!*from(i->data)) + return "Zero-length source"; + + for (j = list->items; j; j = j->next) { + if (i != j) { + if (strcmp(from(i->data), from(j->data)) == 0) + return "Duplicate source"; + else if (strcmp(to(i->data), to(j->data)) == 0) + return "Duplicate destination"; + } + } + + next = i->next; + if (strcmp(from(i->data), to(i->data)) == 0) + deleteitem(i, list); + } + + + return NULL; +} + +struct list * +concatenate(struct list *a, struct list *b) +{ + struct item *item, *next; + + for (item = b->items; item; item = next) { + next = item->next; + removeitem(item, b); + additem(item, a, a->last); + } + freelist(b); + + return a; +} + +struct item * +find(char *name, struct list *list, char *(*getstr)(char **)) +{ + struct item *i; + + for (i = list->items; i; i = i->next) + if (strcmp(getstr(i->data), name) == 0) + return i; + return NULL; +} + +char * +sort(struct list *primary) +{ + struct item *a, *b, *next; + struct list *secondary; + + secondary = newlist(primary->freedata); + if (!secondary) + return "Unable to make secondary list"; + + for (a = primary->items; a; a = next) { + next = a->next; + if (!(b = find(from(a->data), primary, to))) + b = find(from(a->data), secondary, to); + + if (b) { + removeitem(a, primary); + additem(a, secondary, NULL); + removeitem(b, primary); + additem(b, secondary, a); + } + } + + concatenate(primary, secondary); + + return NULL; +} + +char * +getpairs(struct buf *buf1, struct buf *buf2, int delim, struct list **list) +{ + char *err; + struct list *a, *b; + + if (!(a = split(buf1, delim)) || !(b = split(buf2, delim))) + return "Unable to split buffers"; + + err = combine(a, b, list); + if (err) { + freelist(a); + freelist(b); + freelist(*list); + } + return err; +} + +int +main(int argc, char *argv[]) +{ + enum options { + Remove = 1, + CreateDirs = 1 << 1, + Force = 1 << 2, + Overwrite = 1 << 3, + Verbose = 1 << 4 + } options = 0; + int (*action)(char *, char *) = rename; + void (*error)(char *) = die; + int delim = '\n'; + + char *err; + char *flags; + char *p; + int fds[2]; + int i; + struct buf *bufs[2]; + struct item *pair; + struct list *list; + + name = *argv; + + ++argv; + --argc; + if (argv[0][0] != '-') { + flags = ""; + } else { + if (argv[0][1] != '-' || !argv[0][2]) + flags = argv[0] + 1; + ++argv; + --argc; + } + + if (!argc || argc > 2) + usage(NULL); + + for (i = 0; flags[i]; ++i) { + switch (flags[i]) { + case '0': + delim = 0; + break; + case 'c': + action = copy; + break; + case 'd': + options |= CreateDirs; + break; + case 'f': + error = warn; + break; + case 'l': + action = link; + break; + case 'm': + action = rename; + break; + case 'n': + action = nothing; + break; + case 'o': + options |= Overwrite; + break; + case 'r': + options |= Remove; + break; + case 'v': + options |= Verbose; + break; + default: + usage(bprintf("%c: invalid flag", flags[i])); + } + } + + for (i = 0; i < 2; ++i) { + if (i && !argv[i]) { + fds[i] = 0; + } else { + fds[i] = open(argv[i], O_RDONLY); + if (fds[i] < 0) + die(bprintf("unable to open '%s':", argv[i])); + } + bufs[i] = slurp(fds[i]); + if (!bufs[i]) + die(bprintf("unable to read '%s':", argv[i] ? argv[i] : "/dev/stdin")); + } + + if ((err = getpairs(bufs[0], bufs[1], delim, &list)) || + (err = filter(list)) || (err = sort(list))) + die(err); + + for (pair = list->items; pair; pair = pair->next) { + if (!*to(pair->data)) { + if (action == nothing) + ; + else if (!(options & Remove)) + error("Removal not permitted"); + else if (remove(from(pair->data))) + error(bprintf("remove '%s':", from(pair->data))); + if (options & Verbose) + printf("remove '%s'%c", from(pair->data), delim); + } else { + if (exists(to(pair->data))) { + if (!(options & Overwrite)) + error(bprintf("'%s' already exists", to(pair->data))); + } else if (options & CreateDirs) { + if (!(p = dirname(to(pair->data)))) + error(bprintf("dirname '%s':", to(pair->data))); + else if (mkdirs(p, S_IRWXU | S_IRWXG | S_IRWXO)) + error(bprintf("create directories '%s':", to(pair->data))); + } + if (action(from(pair->data), to(pair->data))) + error(bprintf("'%s' to '%s':", from(pair->data), to(pair->data))); + else if (options & Verbose) + printf("'%s' to '%s'%c", from(pair->data), to(pair->data), delim); + else + puts(to(pair->data)); + } + } + + return 0; +} diff --git a/lib/Makefile b/lib/Makefile @@ -0,0 +1,20 @@ +cc = ${CC} +cflags = -Wall -Wno-write-strings ${CFLAGS} +ldflags = ${LDFLAGS} + +src = buf.c slurp.c list.c split.c bprintf.c mkdirs.c copy.c exists.c +obj = ${src:.c=.o} +hdr = ${obj:.o=.h} + +all: ${obj} + +.c.o: + ${cc} ${cflags} -c -o $@ $< + +${obj}: Makefile ${hdr} + +clean: + rm -f ${obj} + +.SUFFIXES: .c .o +.PHONY: clean diff --git a/lib/bprintf.c b/lib/bprintf.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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 <stdarg.h> +#include <stdio.h> + +char * +bprintf(char *fmt, ...) +{ + static char buf[1024]; + va_list args; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + return buf; +} diff --git a/lib/bprintf.h b/lib/bprintf.h @@ -0,0 +1 @@ +static char *bprintf(char *fmt, ...); diff --git a/lib/buf.c b/lib/buf.c @@ -0,0 +1,110 @@ +/* + * Copyright 2021, 2022 Jacob R. Edwards + * + * ap -- audio player + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/* + * This was copied from the aps project on 2023-04-08 + * The bufword, bufline, and bufgetline functions were + * removed as they wouldn't ever find purpose here. + */ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <ctype.h> + +#include "buf.h" + +struct buf * +bufnew(void) +{ + return calloc(1, sizeof(struct buf)); +} + +void +buffree(struct buf *buf) +{ + if (buf == NULL) + return; + free(buf->data); + free(buf); +} + +int +bufresize(struct buf *buf, size_t newsize) +{ + char *tmp; + + if (buf == NULL || newsize < buf->len) { + errno = EINVAL; + return 1; + } + + tmp = realloc(buf->data, newsize); + if (tmp == NULL) + return 1; + + buf->data = tmp; + buf->size = newsize; + return 0; +} + +int +bufenlarge(struct buf *buf, size_t needed) +{ + size_t newsize; + + for (newsize = buf->size ? buf->size : (1024 * 8); + newsize != SIZE_MAX && needed > (newsize - buf->len);) { + if (newsize > SIZE_MAX / 4) + newsize = SIZE_MAX; + else + newsize *= 4; + } + + if (needed > (newsize - buf->len)) { + errno = EOVERFLOW; + return 1; + } + if (newsize == buf->size) + return 0; + return bufresize(buf, newsize); +} + +int +bufappend(struct buf *buf, void *data, size_t len) +{ + if (buf == NULL || bufenlarge(buf, len)) + return 1; + memcpy(buf->data + buf->len, data, len); + buf->len += len; + return 0; +} + +int +bufshift(struct buf *buf, size_t n) +{ + if (n > buf->len) { + errno = EOVERFLOW; + return 1; + } + memmove(buf->data, buf->data + n, buf->len - n); + buf->len -= n; + return 0; +} diff --git a/lib/buf.h b/lib/buf.h @@ -0,0 +1,12 @@ +struct buf { + char *data; + size_t len; + size_t size; +}; + +struct buf *bufnew(void); +void buffree(struct buf *); +int bufresize(struct buf *, size_t); +int bufenlarge(struct buf *, size_t); +int bufappend(struct buf *, void *, size_t); +int bufshift(struct buf *, size_t); diff --git a/lib/copy.c b/lib/copy.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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/wait.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int +copy(char *from, char *to) +{ + char *argv[5]; + int status; + pid_t pid; + + argv[0] = "/bin/cp"; + argv[1] = "-a"; + argv[2] = from; + argv[3] = to; + argv[4] = NULL; + + pid = fork(); + if (pid < 0) + return pid; + + if (!pid) { + execv(*argv, argv); + perror("Unable to execute /bin/cp"); + _exit(1); + } + + if (waitpid(pid, &status, 0) < 0) { + perror("Unable to wait for /bin/cp"); + return -1; + } + return WEXITSTATUS(status); +} diff --git a/lib/copy.h b/lib/copy.h @@ -0,0 +1 @@ +int copy(char *from, char *to); diff --git a/lib/exists.c b/lib/exists.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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/stat.h> + +int +exists(char *name) +{ + struct stat sb; + + return !stat(name, &sb); +} diff --git a/lib/exists.h b/lib/exists.h @@ -0,0 +1 @@ +int exists(char *name); diff --git a/lib/list.c b/lib/list.c @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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 <stdlib.h> + +#include "list.h" + +struct item * +newitem(void *data) +{ + struct item *item; + + item = calloc(1, sizeof(*item)); + if (!item) + return NULL; + + item->data = data; + return item; +} + +void +freeitem(struct item *item) +{ + free(item); +} + +struct list * +newlist(void (*freedata)(void *)) +{ + struct list *list; + + list = calloc(1, sizeof(*list)); + if (!list) + return NULL; + + list->freedata = freedata; + return list; +} + +void +freelist(struct list *list) +{ + struct item *item, *nextitem; + + if (!list) + return; + + for (item = list->items; item; item = nextitem) { + nextitem = item->next; + if (list->freedata) + list->freedata(item->data); + freeitem(item); + } + free(list); +} + +/* + * The linkitem and unlinkitem functions are provided so that you + * may choose not to use the list struct. Unfortunatly I couldn't + * think of sane names which tell of this difference from the add + * and remove item functions. + */ +void +linkitem(struct item *item, struct item *to) +{ + /* This function could check + * if (item->next || item->prev) + * and + * abort(); + * or return an error, but it does not. Maybe it should? + */ + + item->next = to->next; + if (item->next) + item->next->prev = item; + item->prev = to; + if (to) + to->next = item; +} + +void +unlinkitem(struct item *item) +{ + if (item->next) + item->next->prev = item->prev; + if (item->prev) + item->prev->next = item->next; + item->next = item->prev = NULL; +} + +void +additem(struct item *item, struct list *list, struct item *pos) +{ + if (pos) { + linkitem(item, pos); + if (pos == list->last) + list->last = item; + } else { + if (!list->items) + list->last = item; + item->prev = NULL; + item->next = list->items; + if (item->next) + item->next->prev = item; + list->items = item; + } +} + +void +removeitem(struct item *item, struct list *list) +{ + if (item == list->items) + list->items = item->next; + if (item == list->last) + list->last = item->prev; + unlinkitem(item); +} + +void +deleteitem(struct item *item, struct list *list) +{ + removeitem(item, list); + if (list->freedata) + list->freedata(item->data); + freeitem(item); +} diff --git a/lib/list.h b/lib/list.h @@ -0,0 +1,19 @@ +struct list { + struct item *items, *last; + void (*freedata)(void *); +}; + +struct item { + struct item *next, *prev; + void *data; +}; + +struct item *newitem(void *data); +void freeitem(struct item *item); +struct list *newlist(void (*freedata)(void *)); +void freelist(struct list *list); +void linkitem(struct item *item, struct item *to); +void unlinkitem(struct item *item); +void additem(struct item *item, struct list *list, struct item *pos); +void removeitem(struct item *item, struct list *list); +void deleteitem(struct item *item, struct list *list); diff --git a/lib/mkdirs.c b/lib/mkdirs.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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/stat.h> + +#include <errno.h> +#include <libgen.h> +#include <limits.h> +#include <string.h> + +int +mkdirs(char *path, mode_t mode) +{ + char *parent; + char copy[PATH_MAX]; + + if (mkdir(path, mode) == 0 || errno == EEXIST) + return 0; + if (errno != ENOENT) + return -1; + + /* mkdir(2) tests path length */ + strcpy(copy, path); + if (!(parent = dirname(path))) + return -1; + + if (mkdirs(parent, mode)) + return -1; + return mkdir(copy, mode); +} diff --git a/lib/mkdirs.h b/lib/mkdirs.h @@ -0,0 +1 @@ +int mkdirs(char *, mode_t); diff --git a/lib/slurp.c b/lib/slurp.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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 <unistd.h> + +#include "buf.h" + +struct buf * +slurp(int fd) +{ + struct buf *buf; + char tb[1024 * 8]; + int len; + + buf = bufnew(); + if (!buf) + return NULL; + + while ((len = read(fd, tb, sizeof(tb))) > 0) + if (bufappend(buf, tb, len)) + goto errfreebuf; + + if (len < 0) { +errfreebuf: + buffree(buf); + return NULL; + } + return buf; +} diff --git a/lib/slurp.h b/lib/slurp.h @@ -0,0 +1 @@ +struct buf *slurp(int); diff --git a/lib/split.c b/lib/split.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> + * + * 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 <string.h> + +#include "buf.h" +#include "list.h" + +struct list * +split(struct buf *buf, int delim) +{ + struct list *list; + struct item *item; + size_t l; + char *c, *n; + + list = newlist(NULL); + if (!list) + return NULL; + + c = buf->data; + l = buf->len; + while (c && l) { + n = memchr(c, delim, l); + if (!n) { + if (bufenlarge(buf, 1)) { + freelist(list); + return NULL; + } + n = c + l + 1; + } + + *n++ = 0; + if (n - c > l) + l = 0; + else + l -= n - c; + + item = newitem(c); + if (!item) { + freelist(list); + return NULL; + } + additem(item, list, list->last); + c = n; + } + + return list; +} diff --git a/lib/split.h b/lib/split.h @@ -0,0 +1 @@ +struct list *split(struct buf *buf, int delim); diff --git a/pmv.1 b/pmv.1 @@ -0,0 +1,50 @@ +.\" +.\" Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org> +.\" +.\" 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. +.\" +.Dd April 13, 2023 +.Dt PMV 1 +.Os +.Sh NAME +.Nm pmv +.Nd pipe move +.Sh SYNOPSIS +.Nm +.Op Fl bmv-flags +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +utility is a program which takes a list of names and produces a second list using the given +.Ar command , +passing both to +.Xr bmv . +.Pp +The options are the same as those of +.Xr bmv. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +.Pp +Replace all instances of 'bmv' within the files in the current +directory with 'pmv': +.Pp +.Dl $ ls | pmv sed s/bmv/pmv/g +.Pp +.Sh SEE ALSO +.Xr pmv 1 , +.Xr cp 1 +.Sh AUTHORS +.An Jacob R. Edwards Aq Mt jacob@jacobedwards.org diff --git a/pmv.sh b/pmv.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# Copyright 2023 Jacob R. Edwards <jacob@jacobedwards.org> +# Pipe move +# +# Uses bmv to rename the files provided through the standard input +# by filtering them through the given command. + +fifo=/tmp/pipe$$ + +usage() { + echo 'usage: pmv [-bmv-flags] command [arguments ...]' 1>&2 + exit 1 +} + +set -e + +case "$1" in +(-*) + flags="$1" + shift ;; +esac + +case $# in +(0) usage ;; +esac + +mkfifo $fifo +trap 'rm -f $fifo' 0 INT TERM +tee $fifo | "$@" | bmv $flags $fifo || + usage