/* $Header: /CVSROOT/filecache/filecache.c,v 1.4 2006/11/15 04:26:54 tino Exp $ * * filecache: Create a cache entry for a filename * Copyright (C)2006 Valentin Hilbig, webmaster@scylla-charybdis.com * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * $Log: filecache.c,v $ * Revision 1.4 2006/11/15 04:26:54 tino * Options -x, -l and a little bugfix for NUL terminated output lines. * * Revision 1.3 2006/11/15 03:33:53 tino * More NULL improvements (fatals or ignores) * * Revision 1.2 2006/11/15 02:34:26 tino * Options -b -d -e and -z * * Revision 1.1 2006/11/13 04:44:18 tino * first checkin */ #include "tino/filetool.h" #include "tino/slist.h" #include "tino/ex.h" #include "tino/buf_line.h" #include "tino/getopt.h" #include "tino/dir.h" #include "tino/str.h" #include #include "filecache_version.h" #if 0 static int ignorecase, utf8; #endif struct filecache_user { TINO_BUF namebuf, nameout; const char *found; }; static char codepage[256]; static char codechar[256]; static char in_termchar, out_termchar; static int null_termchar, blanks_termchar, maxdir, maxpath, readonly, readfiles, debug_mode, inverse_mode, linemode; static void deb(const char *s, ...) { va_list list; if (!debug_mode) return; fprintf(stderr, "filecache debug: "); va_start(list, s); vfprintf(stderr, s, list); va_end(list); fprintf(stderr, "\n"); } static void out(TINO_BUF *buf) { fputs(tino_buf_get_s(buf), stdout); putchar(out_termchar ? out_termchar : null_termchar ? 0 : 10); if (linemode && fflush(stdout)) tino_exit("EOF on stdout"); } #define FILECACHE_SPECIAL_CHAR '$' static int mapchar(int i) { switch (i) { case '#': case '%': case '+': case ',': case '-': #if 0 case '.': /* Dots are directory special, see . and .. */ #endif case '@': case '_': case '~': return i; } if ((i>='0' && i<='9') || (i>='a' && i<='z')) return i; if (i>=32 && i<64) { i &= 31; if (i<16) return 'a'+i; return 'a'+i-10; } i &= 31; if (!i) return '0'; if (i>26) return '0'+i-26; return 'a'+i-1; } static int filecache_pathpart(const char *s, void *u) { struct filecache_user *user=(void *)u; size_t len; if (!s || !*s || tino_strprefixcmp(tino_buf_get(&user->namebuf), s)) return 0; len = strlen(s); if (s[len-1]==FILECACHE_SPECIAL_CHAR && lennamebuf)) return 0; deb("found entry %s", s); return len; } static void filecache_init(void) { int i; for (i=256; --i>=0; ) { codepage[i] = i/32; codechar[i] = mapchar(i); #if 0 printf("%d%c\n", codepage[i], codechar[i]); #endif } } static int filecache_demangle(const char *dir, const char *name, struct filecache_user *user) { int err; const char *ptr; int cp, i; char c; tino_buf_reset(&user->namebuf); err = 0; if ((ptr=tino_strprefixcmp2_const(name, dir))==0) { tino_err("entry %s is not prefixed by cachedir", name); err = 1; ptr = name; } cp = codepage['a']*32; for (; *ptr; ptr++) { switch (c= *ptr) { /* Ignore common directory characters */ case '/': case '\\': continue; case FILECACHE_SPECIAL_CHAR: switch (c= *++ptr) { /* $codepage */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': cp = 32*(c-'0'); continue; /* $ with EOL or $. */ case 0: case '.': return err; /* $something invalid */ default: tino_err("wrong escape sequence at %s", ptr-1); return 1; } default: if (c>='A' && c<='Z') c += 'a'-'A'; for (i=32;; ) { if (--i<0) { deb("ignore character at %s", ptr); err = 1; break; } else if (codechar[cp+i]==c) { tino_buf_add_c(&user->namebuf, cp+i); break; } } continue; } } tino_err("missing trailing %c for %s", FILECACHE_SPECIAL_CHAR, name); return 1; } static void filecache_mangle(const char *name, struct filecache_user *user) { int last_cp; deb("mangling %s", name); tino_buf_reset(&user->namebuf); last_cp = codepage['a']; while (*name) { unsigned char c; int cp, code; c = *name++; c &= 0xff; cp = codepage[c]; code = codechar[c]; if (cp!=last_cp) { tino_buf_add_c(&user->namebuf, FILECACHE_SPECIAL_CHAR); tino_buf_add_c(&user->namebuf, '0'+cp); last_cp = cp; } tino_buf_add_c(&user->namebuf, code); } /* end name on $ to differ from directory names * * Else we might hit a problem in creating a cache entry for 'ab' * where 'a' already exists (it's expected to be a directory then). * * This way 'a' is 'a$' and 'ab' is 'ab$' */ tino_buf_add_c(&user->namebuf, FILECACHE_SPECIAL_CHAR); deb("mangled %s", tino_buf_get_s(&user->namebuf)); } static void filecache_advance(int found, struct filecache_user *user) { tino_buf_add_c(&user->nameout, TINO_PATH_SEP_CHAR); tino_buf_add_n(&user->nameout, tino_buf_get(&user->namebuf), found); tino_buf_advance(&user->namebuf, found); } static void filecache_new(struct filecache_user *user) { int max; max = tino_buf_get_len(&user->namebuf); if (max<=maxpath) { filecache_advance(max, user); return; } max = maxpath; if (tino_buf_get_s(&user->namebuf)[max-1]==FILECACHE_SPECIAL_CHAR) max--; filecache_advance(max, user); if (!readonly && tino_file_mkdir(tino_buf_get_s(&user->nameout))) tino_exit("corrupt_cache, cannot create directory: %s", tino_buf_get_s(&user->nameout)); } static void filecache_rearrange(TINO_SLIST list, struct filecache_user *user) { #if 0 tino_exit("rearrange not yet implemented"); #else filecache_new(user); #endif } static int filecache(const char *dir, const char *name) { static struct filecache_user user; TINO_SLIST list; if (inverse_mode) { int ret; ret = filecache_demangle(dir, name, &user); out(&user.namebuf); return ret; } filecache_mangle(name, &user); /* Special case: No directory. Just print out the mangled name * without path translation. */ if (!*dir) { out(&user.namebuf); return 0; } tino_buf_reset(&user.nameout); tino_buf_add_s(&user.nameout, dir); /* make sure user.namebuf is 0-terminated */ for (; *tino_buf_get_s(&user.namebuf); tino_slist_destroy(list)) { int found; deb("reading dir %s", tino_buf_get_s(&user.nameout)); list = tino_dir_read_err(tino_buf_get_s(&user.nameout)); if (list) { found = tino_slist_iterate_0(list, filecache_pathpart, &user); if (found) { filecache_advance(found, &user); continue; } } else if (!readonly) tino_exit("corrupt cache, directory read error: %s", tino_buf_get_s(&user.nameout)); if (!readonly && tino_glist_count(list)>=maxdir) filecache_rearrange(list, &user); else filecache_new(&user); } out(&user.nameout); return 0; } static void range_check(int nr, int min, int max, const char *what) { if (maxmax) tino_exit("%s out of bounds: %d (%d..%d)", what, nr, min, max); } int main(int argc, char **argv) { int argn, ret, emptydir; const char *s; TINO_BUF buf; argn = tino_getopt(argc, argv, 1, 2, TINO_GETOPT_VERSION(FILECACHE_VERSION) " cachedir name...\n" " Prints a suitable path to store name into cachedir." , TINO_GETOPT_HELP "h This help" , /* This option is mainly for clean expected default behavior. * If no options are preset the program shall read lines, * as expected by everybody. I do not like surprises like * separating on blanks instead of lines. However there is * a 'blanks' capability, and I also dislike not to support * all possibilities which are there. That's why option -b. */ TINO_GETOPT_FLAG "b Line terminator on read defaults to any blanks.\n" " Obviously this only has affect on option -f" , &null_termchar, TINO_GETOPT_FLAG "d Debug mode" , &debug_mode, TINO_GETOPT_FLAG "e Allow empty cachedir '' (only mangles name)" , &emptydir, TINO_GETOPT_FLAG "f read (filter) filenames from stdin\n" " The name... argument must be missing for this." , &readfiles, #if 0 TINO_GETOPT_FLAG "i ignore character case (works only for ASCII)" , &ignorecase, #endif TINO_GETOPT_FLAG "l output in line mode (flush after each line)" , &linemode, TINO_GETOPT_INT TINO_GETOPT_DEFAULT "m max entries in cachedir or subdirs below" , &maxdir, 200, TINO_GETOPT_INT TINO_GETOPT_DEFAULT "n max path length where new subdirectory is started" , &maxpath, 200, TINO_GETOPT_CHAR "o code output line termination character\n" " (default LF or NUL for option -z)" , &out_termchar, TINO_GETOPT_FLAG "r read only mode, do not create or reorder cachedir" , &readonly, TINO_GETOPT_CHAR "t code line termination character (NUL always is one)\n" " (default LF of not option -z or blanks for option -b)\n" " Obviously this only has affect on option -f" , &in_termchar, #if 0 TINO_GETOPT_FLAG "u UTF-8 filenames" , &utf8, #endif TINO_GETOPT_FLAG "x Inverse mode, demangle names (implies -r)\n" " A prefixed cachedir is ignored, demangling stops at $.\n" " This is, extensions given to the cache name are ignored." " Use with option -d to see all possible warnings." , &inverse_mode, TINO_GETOPT_FLAG "z Allow NUL as line terminator (changes defaults)." , &null_termchar, NULL ); if (argn<=0) return 1; if (readfiles && argc>argn+1) tino_exit("name argument not allowed on option -f"); if (!readfiles && argc<=argn+1) tino_exit("missing argument, for read from stdin use option -f"); if (!inverse_mode) { if (argv[argn][0]) { if (tino_file_notdir(argv[argn])) tino_exit("directory does not exist: %s", argv[argn]); range_check(maxpath, 8, pathconf(argv[argn], _PC_NAME_MAX), "max path length"); range_check(maxdir, 8, pathconf(argv[argn], _PC_LINK_MAX)-50, "max directory entries"); } else if (!emptydir) tino_exit("directory empty string, option -e missing?"); } filecache_init(); if (!readfiles) return filecache(argv[argn], argv[argn+1]); ret = 0; tino_buf_init(&buf); while ((s=tino_buf_line_read(&buf, 0, in_termchar ? in_termchar : blanks_termchar ? -1 : null_termchar ? 0 : 10))!=0) if (filecache(argv[argn], s)) ret = 1; return ret; }