2 uniconv - convert volume encodings
3 Copyright (C) Bjoern Fernhomberg 2004
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 #endif /* HAVE_CONFIG_H */
34 #include <sys/types.h>
35 #include <sys/param.h>
38 #include <atalk/afp.h>
39 #include <atalk/unicode.h>
40 #include <atalk/util.h>
41 #include <atalk/logger.h>
43 #include "atalk/cnid.h"
45 #define MAXPATHLEN 4096
48 static struct _cnid_db *cdb;
49 static char curpath[MAXPATHLEN];
50 static cnid_t cdir_id;
51 static char db_stamp[ADEDLEN_PRIVSYN];
53 static charset_t ch_from;
55 static charset_t ch_to;
57 static charset_t ch_mac;
59 static int usedots = 0;
60 static u_int16_t conv_flags = 0;
61 static int dry_run = 0;
65 char Cnid_srv[256] = "localhost";
68 extern struct charset_functions charset_iso8859_adapted;
71 #define MAX(a,b) (((a)>(b))?(a):(b))
75 #define VETO "./../.AppleDB/.AppleDouble/.AppleDesktop/.Parent/"
77 static int veto(const char *path)
80 char *veto_str = VETO;
86 for(i=0, j=0; veto_str[i] != '\0'; i++) {
87 if (veto_str[i] == '/') {
88 if ((j>0) && (path[j] == '\0'))
92 if (veto_str[i] != path[j]) {
93 while ((veto_str[i] != '/')
94 && (veto_str[i] != '\0'))
106 static int do_rename( char* src, char *dst, struct stat *st)
108 char adsrc[ MAXPATHLEN + 1];
111 if (!stat(dst, &tmp_st)) {
112 fprintf (stderr, "error: cannot rename %s to %s, destination exists\n", src, dst);
116 if ( rename( src, dst ) < 0 ) {
117 fprintf (stderr, "error: cannot rename %s to %s, %s\n", src, dst, strerror(errno));
121 if (S_ISDIR(st->st_mode))
124 strcpy( adsrc, ad_path( src, 0 ));
126 if (rename( adsrc, ad_path( dst, 0 )) < 0 ) {
129 if (errno == ENOENT) {
130 if (stat(adsrc, &ad_st)) /* source has no ressource fork, */
134 fprintf (stderr, "failed to rename resource fork, error: %s\n", strerror(errno));
143 static char *convert_name(char *name, struct stat *st, cnid_t cur_did)
145 static char buffer[MAXPATHLEN +2]; /* for convert_charset dest_len parameter +2 */
148 int require_conversion = 0;
149 u_int16_t flags = conv_flags;
152 p = (unsigned char *)name;
153 q = (unsigned char *)buffer;
155 /* optimize for ascii case */
157 if ( *p >= 0x80 || *p == ':') {
158 require_conversion = 1;
164 if (!require_conversion) {
166 fprintf(stdout, "no conversion required\n");
170 /* convert charsets */
171 q=(unsigned char *)buffer;
172 p=(unsigned char *)name;
174 outlen = convert_charset(ch_from, ch_to, ch_mac, (char *)p, strlen((char *)p), (char *)q, sizeof(buffer) -2, &flags);
175 if ((size_t)-1 == outlen) {
176 if ( ch_to == CH_UTF8) {
177 /* maybe name is already in UTF8? */
179 q = (unsigned char *)buffer;
180 p = (unsigned char *)name;
181 outlen = convert_charset(ch_to, ch_to, ch_mac, (char *)p, strlen((char *)p), (char *)q, sizeof(buffer) -2, &flags);
182 if ((size_t)-1 == outlen) {
183 /* it's not UTF8... */
184 fprintf(stderr, "ERROR: conversion from '%s' to '%s' for '%s' in DID %u failed!!!\n",
185 from_charset, to_charset, name, ntohl(cur_did));
189 if (!strcmp(buffer, name)) {
193 fprintf(stderr, "ERROR: conversion from '%s' to '%s' for '%s' in DID %u failed. Please check this!\n",
194 from_charset, to_charset, name, ntohl(cur_did));
198 if (strcmp (name, buffer)) {
200 fprintf(stdout, "dry_run: would rename %s to %s.\n", name, buffer);
202 else if (!do_rename(name, buffer, st)) {
203 if (CNID_INVALID != (id = cnid_add(cdb, st, cur_did, buffer, strlen(buffer), 0)))
204 fprintf(stdout, "converted '%s' to '%s' (ID %u, DID %u).\n",
205 name, buffer, ntohl(id), ntohl(cur_did));
208 else if (verbose > 1)
209 fprintf(stdout, "no conversion required\n");
214 static int check_dirent(char** name, cnid_t cur_did)
222 if (stat(*name, &st) != 0) {
232 if (S_ISDIR(st.st_mode)){
237 fprintf(stdout, "Checking: '%s' - ", *name);
239 *name = convert_name(*name, &st, cur_did);
244 static int check_adouble(DIR *curdir, char * path _U_)
247 struct dirent* entry;
248 struct dirent* ad_entry;
251 strlcat(curpath, "/", sizeof(curpath));
252 strlcat(curpath, ".AppleDouble", sizeof(curpath));
254 if (NULL == (adouble = opendir(curpath))) {
258 while (NULL != (ad_entry=readdir(adouble)) ) {
259 if (veto(ad_entry->d_name))
263 while (NULL != (entry=readdir(curdir)) ) {
264 if (!strcmp(ad_entry->d_name, entry->d_name)) {
270 fprintf (stderr, "found orphaned resource file %s", ad_entry->d_name);
279 static cnid_t add_dir_db(char *name, cnid_t cur_did)
284 if (CNID_INVALID != ( id = cnid_get(cdb, cur_did, name, strlen(name))) )
292 if (stat(name, &st)) {
293 fprintf( stderr, "dir '%s' cannot be stat'ed, error %u\n", name, errno);
297 id = cnid_add(cdb, &st, did, name, strlen(name), 0);
299 fprintf (stderr, "added '%s' to DID %u as %u\n", name, ntohl(did), ntohl(id));
303 static int getdir(DIR *curdir, char ***names)
305 struct dirent* entry;
306 char **tmp = NULL, **new=NULL;
310 while ((entry=readdir(curdir)) != NULL) {
311 new = (char **) realloc (tmp, (i+1) * sizeof(char*));
313 fprintf(stderr, "out of memory");
317 name = strdup(entry->d_name);
319 fprintf(stderr, "out of memory");
322 tmp[i]= (char*) name;
330 static int checkdir(DIR *curdir, char *path, cnid_t cur_did)
337 size_t len=strlen(curpath);
343 check_adouble(curdir, path);
347 fprintf( stdout, "\nchecking DIR '%s' with ID %u\n", path, ntohl(cur_did));
349 n = getdir(curdir, &names);
354 ret = check_dirent(&name, cur_did);
356 id = add_dir_db(name, cur_did);
357 if ( id == 0 && !dry_run )
358 continue; /* skip, no ID */
361 strlcat(curpath, "/", sizeof(curpath));
362 strlcat(curpath, name, sizeof(curpath));
363 cdir = opendir(curpath);
365 fprintf( stderr, "ERROR: cannot open DIR '%s' with ID %u\n", curpath, ntohl(cur_did));
368 checkdir(cdir, curpath, id);
373 fprintf( stdout, "returned to DIR '%s' with ID %u\n", path, ntohl(cur_did));
380 fprintf( stdout, "leaving DIR '%s' with ID %u\n\n", path, ntohl(cur_did));
385 static int init(char* path)
389 if (NULL == (cdb = cnid_open (path, 0, cnid_type, 0, NULL, NULL)) ) {
390 fprintf (stderr, "ERROR: cannot open CNID database in '%s'\n", path);
391 fprintf (stderr, "ERROR: check the logs for reasons, aborting\n");
394 cnid_getstamp(cdb, db_stamp, sizeof(db_stamp));
397 startdir = opendir(path);
398 strlcpy(curpath, path, sizeof(curpath));
399 checkdir (startdir, path, cdir_id);
407 static void usage( char * name )
409 fprintf( stderr, "usage:\t%s [-ndv] -c cnid -f fromcode -t tocode [-m maccode] path\n", name );
410 fprintf( stderr, "Try `%s -h' for more information.\n", name );
414 static void print_version (void)
416 fprintf( stderr, "uniconv - Netatalk %s\n", VERSION );
419 static void help (void)
421 fprintf (stdout, "\nuniconv, a tool to convert between various Netatalk volume encodings\n");
422 fprintf (stdout, "\nUsage: uniconv [-ndv] -c cnid -f fromcode -t tocode [-m maccode] path\n\n");
423 fprintf (stdout, "Examples:\n");
424 fprintf (stdout, " uniconv -c dbd -f ASCII -t UTF8 -m MAC_ROMAN /path/to/share\n");
425 fprintf (stdout, " uniconv -c cdb -f ISO-8859-1 -t UTF8 -m MAC_ROMAN /path/to/share\n");
426 fprintf (stdout, " uniconv -c cdb -f ISO-8859-ADAPTED -t ASCII -m MAC_ROMAN /path/to/share\n");
427 fprintf (stdout, " uniconv -f UTF8 -t ASCII -m MAC_ROMAN /path/to/share\n\n");
428 fprintf (stdout, "Options:\n");
429 fprintf (stdout, "\t-f\tencoding to convert from, use ASCII for CAP encoded volumes\n");
430 fprintf (stdout, "\t-t\tvolume encoding to convert to, e.g. UTF8.\n");
431 fprintf (stdout, "\t-m\tMacintosh client codepage, required for CAP encoded volumes.\n");
432 fprintf (stdout, "\t\tDefaults to `MAC_ROMAN'\n");
433 fprintf (stdout, "\t-n\t`dry run', don't change anything.\n");
434 fprintf (stdout, "\t-d\tDon't CAP encode leading dots (:2e).\n");
435 fprintf (stdout, "\t-c\tCNID backend used on this volume, usually cdb or dbd.\n");
436 fprintf (stdout, "\t\tIf not specified, the default cnid backend `%s' is used\n", DEFAULT_CNID_SCHEME);
437 fprintf (stdout, "\t-v\tVerbose output, use twice for maximum logging.\n");
438 fprintf (stdout, "\t-V\tPrint version and exit\n");
439 fprintf (stdout, "\t-h\tThis help screen\n\n");
440 fprintf (stdout, "WARNING:\n");
441 fprintf (stdout, " Setting the wrong options might render your data unusable!!!\n");
442 fprintf (stdout, " Make sure you know what you are doing. Always backup your data first.\n\n");
443 fprintf (stdout, " It is *strongly* recommended to do a `dry run' first and to check the\n");
444 fprintf (stdout, " output for conversion errors.\n");
445 fprintf (stdout, " USE AT YOUR OWN RISK!!!\n\n");
450 int main(int argc, char *argv[])
452 char path[MAXPATHLEN];
456 conv_flags = CONV_UNESCAPEHEX | CONV_ESCAPEHEX | CONV_ESCAPEDOTS;
458 #ifdef HAVE_SETLINEBUF
462 while ((c = getopt (argc, argv, "f:m:t:c:dnvVh")) != -1)
466 from_charset = strdup(optarg);
469 to_charset = strdup(optarg);
472 mac_charset = strdup(optarg);
475 conv_flags &= ~CONV_ESCAPEDOTS;
479 fprintf (stderr, "doing dry run, volume will *not* be changed\n");
483 cnid_type = strdup(optarg);
484 fprintf (stderr, "CNID backend set to: %s\n", cnid_type);
501 if ( argc - optind != 1 ) {
505 set_processname("uniconv");
507 if ( from_charset == NULL || to_charset == NULL) {
508 fprintf (stderr, "required charsets not specified\n");
512 if ( mac_charset == NULL )
513 mac_charset = "MAC_ROMAN";
515 if ( cnid_type == NULL)
516 cnid_type = DEFAULT_CNID_SCHEME;
520 strlcpy(path, argv[optind], sizeof(path));
522 /* deal with relative path */
524 fprintf (stderr, "ERROR: cannot chdir to '%s'\n", path);
528 if (NULL == (getcwd(path, sizeof(path))) ) {
529 fprintf (stderr, "ERROR: getcwd failed\n");
534 atalk_register_charset(&charset_iso8859_adapted);
536 if ( (charset_t) -1 == ( ch_from = add_charset(from_charset)) ) {
537 fprintf( stderr, "Setting codepage %s as source codepage failed", from_charset);
541 if ( (charset_t) -1 == ( ch_to = add_charset(to_charset)) ) {
542 fprintf( stderr, "Setting codepage %s as destination codepage failed", to_charset);
546 if ( (charset_t) -1 == ( ch_mac = add_charset(mac_charset)) ) {
547 fprintf( stderr, "Setting codepage %s as mac codepage failed", mac_charset);