bmv

Batch file moving utilities
git clone git://jacobedwards.org/bmv
Log | Files | Refs | README

bmv.c (6954B)


      1 /*
      2  * Copyright (c) 2023 Jacob R. Edwards <jacob@jacobedwards.org>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #define const
     18 
     19 #include <sys/stat.h>
     20 
     21 #include <errno.h>
     22 #include <fcntl.h>
     23 #include <libgen.h>
     24 #include <stdio.h>
     25 #include <stdlib.h>
     26 #include <string.h>
     27 #include <unistd.h>
     28 
     29 #include "lib/bprintf.h"
     30 #include "lib/buf.h"
     31 #include "lib/copy.h"
     32 #include "lib/exists.h"
     33 #include "lib/list.h"
     34 #include "lib/mkdirs.h"
     35 #include "lib/slurp.h"
     36 #include "lib/split.h"
     37 
     38 static char *name;
     39 
     40 void
     41 warn(char *msg)
     42 {
     43 	char *p;
     44 
     45 	fprintf(stderr, "%s: %s", name, msg);
     46 	if ((p = strrchr(msg, ':')) && !p[1])
     47 		fprintf(stderr, " %s", strerror(errno));
     48 	fputc('\n', stderr);
     49 }
     50 
     51 void
     52 die(char *msg)
     53 {
     54 	warn(msg);
     55 	exit(1);
     56 }
     57 
     58 void
     59 usage(char *msg)
     60 {
     61 	if (msg)
     62 		warn(msg);
     63 	fprintf(stderr, "usage: %s [-0cdflmnorv] list1 [list2]\n", name);
     64 	exit(1);
     65 }
     66 
     67 int
     68 nothing(char *a, char *b)
     69 {
     70 	return 0;
     71 }
     72 
     73 char **
     74 makepair(char *a, char *b)
     75 {
     76 	char **pair;
     77 
     78 	pair = calloc(sizeof(*pair), 2);
     79 	if (!pair)
     80 		return NULL;
     81 	pair[0] = a;
     82 	pair[1] = b;
     83 	return pair;
     84 }
     85 
     86 char *
     87 from(char **pair)
     88 {
     89 	return pair[0];
     90 }
     91 
     92 char *
     93 to(char **pair)
     94 {
     95 	return pair[1];
     96 }
     97 
     98 char *
     99 combine(struct list *list1, struct list *list2, struct list **list3p)
    100 {
    101 	struct list *list3;
    102 	struct item *a, *b;
    103 	struct item *na, *nb;
    104 	char **pair;
    105 
    106 	list3 = newlist(free);
    107 	if (!list3)
    108 		return "Unable to create list";
    109 	*list3p = list3;
    110 
    111 	for (a = list1->items, b = list2->items; a && b;
    112 	    a = na, b = nb) {
    113 		pair = makepair(a->data, b->data);
    114 		if (!pair)
    115 			return "Unable to make pair";
    116 
    117 		a->data = pair;
    118 		na = a->next;
    119 		nb = b->next;
    120 		removeitem(a, list1);
    121 		deleteitem(b, list2);
    122 		additem(a, list3, list3->last);
    123 	}
    124 
    125 	if (a)
    126 		return "Too many items in the first list";
    127 	else if (b)
    128 		return "Too many items in the second list";
    129 
    130 	freelist(list1);
    131 	freelist(list2);
    132 	return NULL;
    133 }
    134 
    135 char *
    136 filter(struct list *list)
    137 {
    138 	struct item *i, *j, *next;
    139 
    140 	for (i = list->items; i; i = next) {
    141 		if (!*from(i->data))
    142 			return "Zero-length source";
    143 
    144 		for (j = list->items; j; j = j->next) {
    145                         if (i != j) {
    146 				if (strcmp(from(i->data), from(j->data)) == 0)
    147 					return "Duplicate source";
    148 				else if (strcmp(to(i->data), to(j->data)) == 0)
    149 					return "Duplicate destination";
    150 			}
    151 		}
    152 
    153 		next = i->next;
    154 		if (strcmp(from(i->data), to(i->data)) == 0)
    155 			deleteitem(i, list);
    156 	}
    157 
    158 
    159 	return NULL;
    160 }
    161 
    162 struct list *
    163 concatenate(struct list *a, struct list *b)
    164 {
    165 	struct item *item, *next;
    166 
    167 	for (item = b->items; item; item = next) {
    168 		next = item->next;
    169 		removeitem(item, b);
    170 		additem(item, a, a->last);
    171 	}
    172 	freelist(b);
    173 
    174 	return a;
    175 }
    176 
    177 struct item *
    178 find(char *name, struct list *list, char *(*getstr)(char **))
    179 {
    180 	struct item *i;
    181 
    182 	for (i = list->items; i; i = i->next)
    183 		if (strcmp(getstr(i->data), name) == 0)
    184 			return i;
    185 	return NULL;
    186 }
    187 
    188 char *
    189 sort(struct list *primary)
    190 {
    191 	struct item *a, *b, *next;
    192 	struct list *secondary;
    193 
    194 	secondary = newlist(primary->freedata);
    195 	if (!secondary)
    196 		return "Unable to make secondary list";
    197 
    198 	for (a = primary->items; a; a = next) {
    199 		next = a->next;
    200 		if (!(b = find(from(a->data), primary, to)))
    201 			b = find(from(a->data), secondary, to);
    202 
    203 		if (b) {
    204 			removeitem(a, primary);
    205 			additem(a, secondary, NULL);
    206 			removeitem(b, primary);
    207 			additem(b, secondary, a);
    208 		}
    209 	}
    210 
    211 	concatenate(primary, secondary);
    212 
    213 	return NULL;
    214 }
    215 
    216 char *
    217 getpairs(struct buf *buf1, struct buf *buf2, int delim, struct list **list)
    218 {
    219 	char *err;
    220 	struct list *a, *b;
    221 
    222 	if (!(a = split(buf1, delim)) || !(b = split(buf2, delim)))
    223 		return "Unable to split buffers";
    224 
    225 	err = combine(a, b, list);
    226 	if (err) {
    227 		freelist(a);
    228 		freelist(b);
    229 		freelist(*list);
    230 	}
    231 	return err;
    232 }
    233 
    234 int
    235 main(int argc, char *argv[])
    236 {
    237 	enum options {
    238 		Remove = 1,
    239 		CreateDirs = 1 << 1,
    240 		Force = 1 << 2,
    241 		Overwrite = 1 << 3,
    242 		Verbose = 1 << 4
    243 	} options = 0;
    244 	int (*action)(char *, char *) = rename;
    245 	void (*error)(char *) = die;
    246 	int delim = '\n';
    247 
    248 	char *err;
    249 	char *flags;
    250 	char *p;
    251 	int fds[2];
    252 	int i;
    253 	struct buf *bufs[2];
    254 	struct item *pair;
    255 	struct list *list;
    256 
    257 	name = *argv;
    258 
    259 	++argv;
    260 	--argc;
    261 	if (!argc || argv[0][0] != '-') {
    262 		flags = "";
    263 	} else {
    264 		if (argv[0][1] != '-' || !argv[0][2])
    265 			flags = argv[0] + 1;
    266 		++argv;
    267 		--argc;
    268 	}
    269 
    270 	if (!argc || argc > 2)
    271 		usage(NULL);
    272 
    273 	for (i = 0; flags[i]; ++i) {
    274 		switch (flags[i]) {
    275 		case '0':
    276 			delim = 0;
    277 			break;
    278 		case 'c':
    279 			action = copy;
    280 			break;
    281 		case 'd':
    282 			options |= CreateDirs;
    283 			break;
    284 		case 'f':
    285 			error = warn;
    286 			break;
    287 		case 'l':
    288 			action = link;
    289 			break;
    290 		case 'm':
    291 			action = rename;
    292 			break;
    293 		case 'n':
    294 			action = nothing;
    295 			break;
    296 		case 'o':
    297 			options |= Overwrite;
    298 			break;
    299 		case 'r':
    300 			options |= Remove;
    301 			break;
    302 		case 'v':
    303 			options |= Verbose;
    304 			break;
    305 		default:
    306 			usage(bprintf("%c: invalid flag", flags[i]));
    307 		}
    308 	}
    309 
    310 	for (i = 0; i < 2; ++i) {
    311 		if (i && !argv[i]) {
    312 			fds[i] = 0;
    313 		} else {
    314 			fds[i] = open(argv[i], O_RDONLY);
    315 			if (fds[i] < 0)
    316 				die(bprintf("unable to open '%s':", argv[i]));
    317 		}
    318 		bufs[i] = slurp(fds[i]);
    319 		if (!bufs[i])
    320 			die(bprintf("unable to read '%s':", argv[i] ? argv[i] : "/dev/stdin"));
    321 	}
    322 
    323 	if ((err = getpairs(bufs[0], bufs[1], delim, &list)) ||
    324 	    (err = filter(list)) || (err = sort(list)))
    325 		die(err);
    326 
    327 	for (pair = list->items; pair; pair = pair->next) {
    328 		if (!*to(pair->data)) {
    329 			if (action == nothing)
    330 				;
    331 			else if (!(options & Remove))
    332 				error("Removal not permitted");
    333 			else if (remove(from(pair->data)))
    334 				error(bprintf("remove '%s':", from(pair->data)));
    335 			if (options & Verbose)
    336 				printf("remove '%s'%c", from(pair->data), delim);
    337 		} else {
    338 			if (exists(to(pair->data))) {
    339 				if (!(options & Overwrite))
    340 					error(bprintf("'%s' already exists", to(pair->data)));
    341 			} else if (options & CreateDirs) {
    342 				if (!(p = dirname(to(pair->data))))
    343 					error(bprintf("dirname '%s':", to(pair->data)));
    344 				else if (mkdirs(p, S_IRWXU | S_IRWXG | S_IRWXO))
    345 					error(bprintf("create directories '%s':", to(pair->data)));
    346 			}
    347 			if (action(from(pair->data), to(pair->data)))
    348 				error(bprintf("'%s' to '%s':", from(pair->data), to(pair->data)));
    349 			else if (options & Verbose)
    350 				printf("'%s' to '%s'%c", from(pair->data), to(pair->data), delim);
    351 			else
    352 				puts(to(pair->data));
    353 		}
    354 	}
    355 
    356 	return 0;
    357 }