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