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 }