[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

dirconv update



dirconv: 轉換系統裡文件名,目錄名的編碼。
==================================================================
Changes:
    Reorganize a bit.
    Fix a couple of mem leak on symlinks.
    Louder error report.

dirconv is a program to convert encoding of a give directory/file to
another encoding.

WARNING: The action is highly dangerous. You might have your system
converted to something you can not read/recognize. And even worse, the
program has not been intensively tested. Never tested on a vfat system.
Use it on your own risk. Read the file first.

Why it is writen? Because it can be writen and it might be useful
in the future.

-- 
hashao|                     知人者智,自知者明。
hashao|                     勝人者有力,自勝者強。
hashao|                     知足者富。強行者有志。
hashao|                     不失其所者久。死而不亡者壽。
-- 
| This message was re-posted from debian-chinese-gb@lists.debian.org
| and converted from gb2312 to big5 by an automatic gateway.
/* dirconv: encoding convert for directries. Ver: 20020920.
 * Released under the Gnu Public License (GPL). 
 * It might make your file system unusable. Use it on your own risk. 
 * 	-- hashao 
 *
 * Please visit: http://www.debian.org/intl/zh/
 */

/* Convert the encoding of a directory name recursively. 
 *   + content of symbolic links are also converted. (good/bad?)
 */

/* Usage: dirconv -f encode -t encode [-w|-m] filename 
 *        -w:	Warn off. Do not warn at the beginning.
 *        -m:	Also follow other mounted file system (horror!)
 */

/* To compile: 
 *    gcc -lreadline -o dirconv dirconv.c 
 * You need libreadline-dev to compile it, of course.
 */

/* ChangeLog:
 *
 * 2002-09-20:
 *   + Make config structure global so we can use it. C++ is better here.
 *   + Add -m option to also do mounted file system if asked.
 *   + Plug a couple of mem leak for rename symlink. (do_symlink())
 *   + Better error display. Many perror()'s, printf()'s.
 *
 * 2002-09-15:
 *   + first release. Only limited test.
 *   + content of symbolic links are also converted. (good/bad?)
 *   + Don't know what will happen on vfat systems. I will not
 *     try it on my own vfat system!
 *   + Use it on your own risk. (hashao)
 */

#define _GNU_SOURCE 1	/* Need for nftw() in ftw.h */

#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <ftw.h>
#include <iconv.h>
#include <stdio.h>
#include <readline/readline.h>

#define SYMLEN 	4096 /* Symbolic link buffer length. */

static iconv_t icd = (iconv_t)(-1);	/* global iconv handler. */
/* buff hold the converted file name. Avoid too much malloc. */
static char *convstr = NULL;
static int maxlen = 256;	/* max len of the convstr. */

/* Hold configuration for this program. */
struct conf {
    char* from;
    char* to;
    char* root; /* Root diretory to convert. */
    int warn;	/* warning flag. */
    int mount;
};

/* Gobal variable holding our configuration. */
static struct conf gcf;

/* Handy iconv function use a global output buffer. */
int do_iconv(const char *filename)
{
    char *inbuf, *outbuf;
    size_t inleft, outleft;
    size_t retval;

    memset(convstr, 0, maxlen);
    inbuf = (char*)filename;
    outbuf  = convstr;
    inleft = strlen(inbuf);
    outleft = maxlen;
    retval = iconv(icd, NULL, NULL, NULL, NULL);
    retval = iconv(icd, &inbuf, &inleft, &outbuf, &outleft);

    /* convert ok. */
    if (retval != (size_t)(-1))
	return retval;

    /* encoding error. */
    if (errno != E2BIG){
	perror(filename);
	return retval;
    }

    /* No enough output space. expend and tail recursive. */
    free(convstr);
    maxlen = maxlen + MB_LEN_MAX*inleft;
    convstr = (char*)malloc(maxlen);
    return do_iconv(filename);
}

/* Function to re-encode plain filename. */
int do_rename(const char* file, char* newfile, struct FTW *s)
{
    int retval;
    char *newfullpath;

    /* Construct the new fullpath. */
    newfullpath = (char*)malloc(sizeof(newfile)+ s->base + 1);
    memset(newfullpath, 0, sizeof(newfile) + s->base + 1);
    strncat(newfullpath, file, s->base);
    strcat(newfullpath, newfile);

    retval = rename(file, newfullpath);
    if (!retval){
	printf("Converted %s to %s\n", file, newfullpath);
    }
    else {
	printf("!!! Failed to convert %s\n", file);
	perror(file);
    }
    free(newfullpath);
}

/* Convert a symbolic link and its link content. */
int do_symlink(const char* file, char* newfile, struct FTW *s)
{
    int retval;
    char *newfullpath;
    char symbuf[SYMLEN];
    char *newlink;

    /* Construct the new fullpath. */
    newfullpath = (char*)malloc(sizeof(newfile)+ s->base + 1);
    memset(newfullpath, 0, sizeof(newfile) + s->base + 1);
    strncat(newfullpath, file, s->base);
    strcat(newfullpath, newfile);
    memset(symbuf, 0, SYMLEN);

    /* Get the content of symbolic link and convert it too. */
    retval = readlink(file, symbuf, SYMLEN);
    if (retval == -1){
	printf("!!! Failed to read symbolic link: %s\n", file);
	perror(file);
    }else{
	retval = do_iconv(symbuf);
	if (retval == (size_t)(-1)){
	    printf("!!! Cannot iconv symlink content %s for %s\n",
		    symbuf, file);
	}else{
	    /* Save convstr. */
	    newlink = strdup(convstr);
	    /* Relink to the new symlink. */
	    retval = symlink(newlink, newfullpath);
	    if (retval == -1){
		printf("!!! Cannot create symlink %s to %s\n", 
			newfullpath, newlink);
		perror(newlink);
	    }else {
		printf("Converted symlink %s to %s points to \n", file, 
			newfullpath, newlink);
		retval = unlink(file);
		if (retval == -1){
		    printf("!!! Cannot unlink symlink %s\n", file);
		    perror(file);
		}
	    }
	    free(newlink);
	}
    }
    free(newfullpath);
    return retval;
}

/* Function past to nftw(). called for every file. */
int walk_func(const char* file, const struct stat *sb, int flag, struct FTW *s)
{
    int retval;
    char *basefile;
    char *newfile;

    if(flag & FTW_DNR) {
	printf("!!! %s is a directory but its content cannot be read.\n", file);
    }

    /* Only convert the basename. we do depth first. */
    basefile = (char*)(file + s->base);
    retval = do_iconv(basefile);

    if (retval == (size_t)(-1)){
	printf("!!! Cannot iconv %s, skip.\n", basefile);
	if (S_ISDIR(sb->st_mode))
	    printf("In dir %s\n", file);
	return 0;
    }

    /* Save a copy of converted string. */
    newfile = strdup(convstr);

    if S_ISLNK(sb->st_mode) {
	do_symlink(file, newfile, s);
    }else {
	do_rename(file, newfile, s);
    }
	
    if (S_ISDIR(sb->st_mode))
	printf("In dir %s\n", file);

    free(newfile);
    /* Return non-0 will stop ftw(). */
    return 0;

}

/* Start waling a dir tree from the 'root'. */
int do_walk(char* root, struct conf *cf)
{
    int retval;
    int flag;

    /* Do not |FTW_CHDIR. It will stop when hit by permission denies. */
    /* Do depth first, do no follow symbolic directory. */
    flag = FTW_DEPTH|FTW_PHYS;
    if (cf->mount){
	flag |= FTW_MOUNT;
    }

    retval = nftw(root, walk_func, 100, flag);
    if (retval == -1) {
	perror("nftw Error");
    }
    return retval;
}

int do_init(struct conf *cf)
{
    int retval = 0;

    convstr = (char*)malloc(maxlen);
    if (!convstr)
    {
	perror("convstr");
	retval = -1;
    }
    icd = iconv_open(cf->to, cf->from);
    if(icd == (iconv_t) -1) {
	printf("Cannot convert from %s to %s.\n", cf->from, cf->to);
	perror("Iconv");
	retval = -1;
    }
    return retval;
}

/* Read command line options. */
int do_opt(int argc, char* argv[], struct conf *cf)
{
    int retval;
    int c;

    cf->to = cf->from = cf->root = NULL;
    cf->warn = 1;
    cf->mount = 0;
    while(1) {
	int curoptind = optind ? optind : 1;
	c = getopt(argc, argv, "-wmt:f:");
	if (c == -1)
	    break;
	switch (c) {
	    case 1:
		if (!cf->root)
		    cf->root = optarg;
		break;
	    case 't':
		cf->to = optarg;
		break;
	    case 'f':
		cf->from = optarg;
		break;
	    case 'w':
		cf->warn = 0;
		break;
	    case 'm':
		cf->mount = 1;
		break;
	    case '?':
		return -1;
		break;
	}
    }
    if(!(cf->to && cf->from && cf->root)) {
	printf("Usage: %s [-w|-m] -f encode -t encode filename \n", argv[0]);
	return -1;
    }
    return 0;
}

/* confirm if user want to continue. */
int print_confirm(struct conf *cf)
{
    /* Warn the user. */
    char* c;
    int retval;

    retval = 0;
    printf ("I am going to convert \"%s\" and its subdirectory (if any) \n"
	    "from [%s] to [%s]. It is **dangerous**!!! It might mess \n"
	    "up all your file system!\n\n"
	    "=== \"%s:\" [%s] -> [%s] ===\n",
	    cf->root, cf->from, cf->to, cf->root, cf->from, cf->to);
    if (cf->mount){
	printf("Will follow links in other mounted file systems!!!\n");
    }else{
	printf("Will stay in the same mounted file systems.\n");
    }

    c = readline("Are you sure?! [N/y]: ");
    if (!strcasecmp(c, "y")) {
	free(c);
	c = readline("Really? [N/y]: ");
	if (!strcasecmp(c, "y"))
	    retval = 1;
    }
    free(c);
    return retval;
}

int main(int argc, char* argv[])
{
    int retval;
    struct stat *myst;
    struct conf *cf;

    cf = &gcf;
    
    /* parse command line. */
    retval = do_opt(argc, argv, cf);
    if(retval != 0)
	return 1;

    /* init global stuff. */
    retval = do_init(cf);
    if(retval == -1)
    {
	printf("Failed to initialize.\n");
	return 2;
    }

    /* Make sure the 'root' is a valid file or directory. */
    myst = (struct stat*) malloc(sizeof(struct stat));
    retval = stat(cf->root, myst);
    free(myst);
    if (retval == -1) {
	printf("%s cannot be converted.\n", cf->root);
	perror(cf->root);
	return 3;
    }

    /* Warning user of the danger. */
    if (cf->warn){
	retval = print_confirm(cf);
	if(!retval) /* not confirmed. */
	    return 0;
    }
    
    /* Real action. */
    retval = do_walk(cf->root, cf);
    return retval;
}

Reply to: