1 /*
2 * t_renamer.c
3 *
4 * renamer for tagutil.
5 *
6 * This file is big and use a ton of helper, mainly because it handle both
7 * actual rename and pattern parsing / evaluation.
8 */
9 #include <sys/param.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12
13 #include <ctype.h>
14 #include <errno.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18
19 #include "t_config.h"
20 #include "t_toolkit.h"
21 #include "t_renamer.h"
22
23
24 /*
25 * t_rename_pattern definition
26 *
27 * a t_rename_pattern is a list of parsed tokens. Token are either a tag
28 * reference or a string litteral.
29 * */
30 struct t_rename_token {
31 int is_tag; /* 0 means string litteral, != 0 means a tag */
32 const char *value;
33 TAILQ_ENTRY(t_rename_token) entries;
34 };
35 TAILQ_HEAD(t_rename_pattern, t_rename_token);
36
37
38 /*
39 * helper for t_rename() - eval the given pattern in the context of given
40 * t_tune.
41 *
42 * @return
43 * the resulting string or NULL on error. Returned value has to be free()d by
44 * the caller.
45 */
46 static char *t_rename_eval(struct t_tune *tune,
47 const struct t_rename_pattern *pattern);
48
49 /*
50 * helper for t_rename() - rename path to new_path.
51 *
52 * @return
53 * return -1 on error and set and errno, 0 on success.
54 */
55 static int t_rename_safe(const char *oldpath, const char *newpath);
56
57 /*
58 * print the given question, and read user's input. input should match
59 * y|yes|n|no. t_yesno() loops until a valid response is given and then return
IndexOnlyNext(IndexOnlyScanState * node)60 * 1 if the response match y|yes, 0 if it match n|no.
61 * Honor Yflag and Nflag.
62 *
63 * This routine is defined here because only the renamer use it. If another file
64 * needs it, it should be moved back to t_toolkit.
65 */
66 static int t_yesno(const char *question);
67
68 /* helper for t_rename_parse() */
69 static struct t_rename_token *t_rename_token_new(int is_tag, const char *value);
70
71 /* helper for t_rename_safe(), taken from mkdir(3) */
72 static int build(char *path, mode_t omode);
73
74
75 int
76 t_rename(struct t_tune *tune, const struct t_rename_pattern *pattern)
77 {
78 int ret = 0;
79 const char *ext;
80 char *npath = NULL, *rname = NULL, *q = NULL;
81 const char *opath;
82 const char *dirn;
83
84 /* XXX: these are undocumented hacky interfaces for the rename feature. */
85 int t__tune_reload__(struct t_tune *tune, const char *path);
86
87 assert(pattern != NULL);
88 assert(tune != NULL);
89
90 /* ensure a clean file state */
91 if (t_tune_save(tune) == -1)
92 goto error_label;
93
94 ext = strrchr(t_tune_path(tune), '.');
95 if (ext == NULL) {
96 warnx("%s: can not find file extension", t_tune_path(tune));
97 goto error_label;
98 }
99 ext++; /* skip dot */
100 rname = t_rename_eval(tune, pattern);
101 if (rname == NULL)
102 goto error_label;
103
104 /* rname is now OK. store into result the full new path. */
105 dirn = t_dirname(t_tune_path(tune));
106 if (dirn == NULL) {
107 warn("dirname");
108 goto error_label;
109 }
110
111 opath = t_tune_path(tune);
112 /* we dont want foo.flac to be renamed then same name just with a
113 different path like ./foo.flac */
114 if (strcmp(opath, t_basename(opath)) == 0) {
115 if (asprintf(&npath, "%s.%s", rname, ext) < 0)
116 goto error_label;
117 } else {
118 if (asprintf(&npath, "%s/%s.%s", dirn, rname, ext) < 0)
119 goto error_label;
120 }
121
122 /* ask user for confirmation and rename if user want to */
123 if (asprintf(&q, "rename `%s' to `%s'", opath, npath) < 0)
124 goto error_label;
125 if (strcmp(opath, npath) != 0 && t_yesno(q)) {
126 ret = t_rename_safe(opath, npath);
127 if (ret == 0) {
128 if (t__tune_reload__(tune, npath) == -1)
129 goto error_label;
130 }
131 }
132
133 free(q);
134 free(npath);
135 free(rname);
136 return (ret);
137 /* NOTREACHED */
138
139 error_label:
140 free(q);
141 free(npath);
142 free(rname);
143 return (-1);
144 }
145
146
147 #define T_TAG 1
148 #define T_STRING 0
149 struct t_rename_pattern *
150 t_rename_parse(const char *source)
151 {
152 const char sep = '%';
153 const char *c = source;
154 struct t_rename_pattern *pattern = NULL;
155 struct t_rename_token *token;
156 struct sbuf *sb = NULL;
157 enum {
158 PARSING_STRING,
159 PARSING_SIMPLE_TAG,
160 PARSING_BRACE_TAG
161 } state;
162
163 sb = sbuf_new_auto();
164 if (sb == NULL)
165 goto error_label;
166
167 pattern = malloc(sizeof(struct t_rename_pattern));
168 if (pattern == NULL)
169 goto error_label;
170 TAILQ_INIT(pattern);
171
172 state = PARSING_STRING;
173 while (*c != '\0') {
174 /* if we parse a litteral string, check if this is the start of
175 a tag */
176 if (state == PARSING_STRING && *c == sep) {
177 /* avoid to add a empty token. This can happen when
178 when parsing two consecutive tags like `%tag%tag' */
179 if (sbuf_len(sb) > 0) {
180 if (sbuf_finish(sb) == -1)
181 goto error_label;
182 token = t_rename_token_new(T_STRING, sbuf_data(sb));
183 if (token == NULL)
184 goto error_label;
185 TAILQ_INSERT_TAIL(pattern, token, entries);
186 }
187 sbuf_clear(sb);
188 c += 1;
189 if (*c == '{') {
190 c += 1;
191 state = PARSING_BRACE_TAG;
192 } else {
193 state = PARSING_SIMPLE_TAG;
194 }
195 /* if we parse a tag, check for the end of it */
196 } else if ((state == PARSING_SIMPLE_TAG && (*c == sep || !isalnum(*c))) ||
197 (state == PARSING_BRACE_TAG && *c == '}')) {
198 if (sbuf_len(sb) == 0)
199 warnx("empty tag in rename pattern");
200 if (sbuf_finish(sb) == -1)
201 goto error_label;
202 token = t_rename_token_new(T_TAG, sbuf_data(sb));
203 if (token == NULL)
204 goto error_label;
205 sbuf_clear(sb);
206 TAILQ_INSERT_TAIL(pattern, token, entries);
207 if (state == PARSING_BRACE_TAG) {
208 /* eat the closing `}' */
209 c += 1;
210 }
211 state = PARSING_STRING;
212 } else {
213 /* default case for both string and tags. `\' escape
214 everything */
215 if (*c == '\\') {
216 c += 1;
217 if (*c == '\0')
218 break;
219 }
220 sbuf_putc(sb, *c);
221 c += 1;
222 }
223 }
224 /* we've hit the end of the source. Check in which state we are and try
225 to finish cleany */
226 switch (state) {
227 case PARSING_BRACE_TAG:
228 warnx("missing closing `}' at the end of the rename pattern");
229 goto error_label;
230 case PARSING_SIMPLE_TAG:
231 if (sbuf_len(sb) == 0)
232 warnx("empty tag at the end of the rename pattern");
233 case PARSING_STRING: /* FALLTHROUGH */
234 default:
235 /* all is right */;
236 }
237 /* finish the last tag unless it is the empty string */
238 if (state != PARSING_STRING || sbuf_len(sb) > 0) {
239 if (sbuf_finish(sb) == -1)
240 goto error_label;
241 token = t_rename_token_new(
242 (state == PARSING_STRING ? T_STRING : T_TAG),
243 sbuf_data(sb));
244 if (token == NULL)
245 goto error_label;
246 TAILQ_INSERT_TAIL(pattern, token, entries);
247 }
248
249 sbuf_delete(sb);
250 return (pattern);
251 /* NOTREACHED */
252 error_label:
253 sbuf_delete(sb);
254 t_rename_pattern_delete(pattern);
255 return (NULL);
256 }
257 #undef T_TAG
258 #undef T_STRING
259
260
261 char *
262 t_rename_eval(struct t_tune *tune, const struct t_rename_pattern *pattern)
263 {
264 struct t_rename_token *token;
265 struct sbuf *sb = NULL;
266 struct t_taglist *tlist = NULL, *l = NULL;
267 char *s = NULL, *ret;
268
269 assert(tune != NULL);
270 assert(pattern != NULL);
StoreIndexTuple(TupleTableSlot * slot,IndexTuple itup,TupleDesc itupdesc)271
272 sb = sbuf_new_auto();
273 if (sb == NULL)
274 goto error;
275
276 tlist = t_tune_tags(tune);
277 if (tlist == NULL)
278 goto error;
279
280 TAILQ_FOREACH(token, pattern, entries) {
281 if (token->is_tag) {
282 l = t_taglist_find_all(tlist, token->value);
283 if (l == NULL)
284 goto error;
285 if (l->count > 0) {
286 /* tag exist */
287 if ((s = t_taglist_join(l, " + ")) == NULL)
288 goto error;
289 if (l->count > 1) {
290 warnx("%s: has many `%s' tags, joined with `+'",
291 t_tune_path(tune), token->value);
292 }
293 }
294 t_taglist_delete(l);
295 l = NULL;
IndexOnlyRecheck(IndexOnlyScanState * node,TupleTableSlot * slot)296 if (s != NULL) {
297 char *slash = strchr(s, '/');
298 /* check for slash in tag value */
299 if (slash != NULL) {
300 warnx("%s: tag `%s' has / in value, replacing by `-'",
301 t_tune_path(tune), token->value);
302 do {
303 *slash = '-';
304 slash = strchr(slash, '/');
305 } while (slash != NULL);
306 }
307 }
308 }
309 if (s != NULL) {
310 (void)sbuf_cat(sb, s);
311 free(s);
312 s = NULL;
313 } else
314 (void)sbuf_cat(sb, token->value);
315 }
316
317 ret = NULL;
318 if (sbuf_len(sb) > MAXPATHLEN)
319 warnx("t_rename_eval result is too long (>MAXPATHLEN)");
320 else {
321 if (sbuf_finish(sb) != -1)
322 ret = strdup(sbuf_data(sb));
323 }
324
325 sbuf_delete(sb);
326 t_taglist_delete(tlist);
327 return (ret);
328 error:
329 free(s);
330 t_taglist_delete(l);
331 sbuf_delete(sb);
332 t_taglist_delete(tlist);
333 return (NULL);
ExecReScanIndexOnlyScan(IndexOnlyScanState * node)334 }
335
336
337 void
338 t_rename_pattern_delete(struct t_rename_pattern *pattern)
339 {
340 struct t_rename_token *t1, *t2;
341
342 if (pattern == NULL)
343 return;
344
345 t1 = TAILQ_FIRST(pattern);
346 while (t1 != NULL) {
347 t2 = TAILQ_NEXT(t1, entries);
348 free(t1);
349 t1 = t2;
350 }
351 free(pattern);
352 }
353
354
355 static int
356 t_yesno(const char *question)
357 {
358 extern int Yflag, Nflag;
359 char *endl;
360 char buffer[5]; /* strlen("yes\n\0") == 5 */
361
362 for (;;) {
363 if (feof(stdin) && !Yflag && !Nflag)
364 return (0);
365
366 (void)memset(buffer, '\0', sizeof(buffer));
367
368 if (question != NULL) {
ExecEndIndexOnlyScan(IndexOnlyScanState * node)369 (void)printf("%s? [y/n] ", question);
370 (void)fflush(stdout);
371 }
372
373 if (Yflag) {
374 (void)printf("yes\n");
375 return (1);
376 } else if (Nflag) {
377 (void)printf("no\n");
378 return (0);
379 }
380
381 if (fgets(buffer, NELEM(buffer), stdin) == NULL) {
382 if (feof(stdin))
383 return (0);
384 else
385 err(EXIT_FAILURE, "fgets");
386 }
387
388 endl = strchr(buffer, '\n');
389 if (endl == NULL) {
390 /* buffer didn't receive EOL, must still be on stdin */
391 while (getc(stdin) != '\n' && !feof(stdin))
392 continue;
393 } else {
394 *endl = '\0';
395 (void)t_strtolower(buffer);
396 if (strcmp(buffer, "n") == 0 || strcmp(buffer, "no") == 0)
397 return (0);
398 else if (strcmp(buffer, "y") == 0 || strcmp(buffer, "yes") == 0)
399 return (1);
400 }
401 }
402 }
403
404
405 static int
406 t_rename_safe(const char *opath, const char *npath)
407 {
408 extern int pflag;
409 int failed = 0;
410 struct stat st;
411 const char *s;
412 char odir[MAXPATHLEN], ndir[MAXPATHLEN];
413
414 assert(opath != NULL);
415 assert(npath != NULL);
416
417 if ((s = t_dirname(opath)) == NULL) {
418 warn("dirname");
419 return (-1);
ExecIndexOnlyMarkPos(IndexOnlyScanState * node)420 }
421 if (strlcpy(odir, s, sizeof(odir)) >= sizeof(odir)) {
422 warnx("path exceeding MAXPATHLEN");
423 return (-1);
424 }
425 if ((s = t_dirname(npath)) == NULL) {
426 warn("dirname");
427 return (-1);
428 }
429 if (strlcpy(ndir, s, sizeof(odir)) >= sizeof(odir)) {
430 warnx("path exceeding MAXPATHLEN");
431 return (-1);
432 }
433
434 if (strcmp(odir, ndir) != 0) {
435 /* srcdir != destdir, we need to check if destdir is OK */
436 if (pflag) { /* we are asked to create the directory */
437 char *d = strdup(ndir);
438 if (d == NULL)
439 return (-1);
440 (void)build(d, S_IRWXU | S_IRWXG | S_IRWXO);
441 free(d);
442 }
443 if (stat(ndir, &st) != 0) {
444 failed = 1;
445 if (errno == ENOENT && !pflag)
446 warn("`%s' (forgot -p ?)", ndir);
447 } else if (!S_ISDIR(st.st_mode)) {
448 failed = 1;
449 errno = ENOTDIR;
450 warn("%s", ndir);
451 }
452 }
453 if (failed)
454 return (-1);
455
456 if (stat(npath, &st) == 0) {
ExecIndexOnlyRestrPos(IndexOnlyScanState * node)457 errno = EEXIST;
458 warn("%s", npath);
459 return (-1);
460 }
461
462 if (rename(opath, npath) == -1) {
463 warn("rename");
464 return (-1);
465 }
466
467 return (0);
468 }
469
470
471 /* alloc and initialize a new t_rename_token */
472 static struct t_rename_token *
473 t_rename_token_new(int is_tag, const char *value)
474 {
475 struct t_rename_token *token;
476 char *p;
477 size_t len;
478
479 len = strlen(value);
480 token = malloc(sizeof(struct t_rename_token) + len + 1);
481 if (token != NULL) {
482 token->is_tag = is_tag;
483 token->value = p = (char *)(token + 1);
484 memcpy(p, value, len + 1);
485 }
486 return (token);
487 }
488
489
490 /*-
491 * Copyright (c) 1983, 1992, 1993
492 * The Regents of the University of California. All rights reserved.
ExecInitIndexOnlyScan(IndexOnlyScan * node,EState * estate,int eflags)493 *
494 * Redistribution and use in source and binary forms, with or without
495 * modification, are permitted provided that the following conditions
496 * are met:
497 * 1. Redistributions of source code must retain the above copyright
498 * notice, this list of conditions and the following disclaimer.
499 * 2. Redistributions in binary form must reproduce the above copyright
500 * notice, this list of conditions and the following disclaimer in the
501 * documentation and/or other materials provided with the distribution.
502 * 4. Neither the name of the University nor the names of its contributors
503 * may be used to endorse or promote products derived from this software
504 * without specific prior written permission.
505 *
506 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
507 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
508 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
509 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
510 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
511 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
512 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
513 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
514 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
515 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
516 * SUCH DAMAGE.
517 *
518 * $FreeBSD: src/bin/mkdir/mkdir.c,v 1.33 2006/10/10 20:18:20 ru Exp $
519 */
520
521 /*
522 * Returns 1 if a directory has been created,
523 * 2 if it already existed, and 0 on failure.
524 */
525 static int
526 build(char *path, mode_t omode)
527 {
528 struct stat sb;
529 mode_t numask, oumask;
530 int first, last, retval;
531 char *p;
532
533 p = path;
534 oumask = 0;
535 retval = 1;
536 if (p[0] == '/') /* Skip leading '/'. */
537 ++p;
538 for (first = 1, last = 0; !last ; ++p) {
539 if (p[0] == '\0')
540 last = 1;
541 else if (p[0] != '/')
542 continue;
543 *p = '\0';
544 if (!last && p[1] == '\0')
545 last = 1;
546 if (first) {
547 /*
548 * POSIX 1003.2:
549 * For each dir operand that does not name an existing
550 * directory, effects equivalent to those caused by the
551 * following command shall occcur:
552 *
553 * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
554 * mkdir [-m mode] dir
555 *
556 * We change the user's umask and then restore it,
557 * instead of doing chmod's.
558 */
559 oumask = umask(0);
560 numask = oumask & ~(S_IWUSR | S_IXUSR);
561 (void)umask(numask);
562 first = 0;
563 }
564 if (last)
565 (void)umask(oumask);
566 if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
567 if (errno == EEXIST || errno == EISDIR) {
568 if (stat(path, &sb) < 0) {
569 warn("build: %s", path);
570 retval = 0;
571 break;
572 } else if (!S_ISDIR(sb.st_mode)) {
573 if (last)
574 errno = EEXIST;
575 else
576 errno = ENOTDIR;
577 retval = 0;
578 break;
579 }
580 if (last)
581 retval = 2;
582 } else {
583 retval = 0;
584 break;
585 }
586 }
587 if (!last)
588 *p = '/';
589 }
590 if (!first && !last)
591 (void)umask(oumask);
592 return (retval);
593 }
594