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 <arpa/inet.h>
40 #include <atalk/afp.h>
41 #include <atalk/unicode.h>
42 #include <atalk/util.h>
43 #include <atalk/logger.h>
44 #include <atalk/compat.h>
46 #include "atalk/cnid.h"
48 #define MAXPATHLEN 4096
51 static struct _cnid_db *cdb;
52 static char curpath[MAXPATHLEN];
53 static cnid_t cdir_id;
54 static char db_stamp[ADEDLEN_PRIVSYN];
56 static charset_t ch_from;
58 static charset_t ch_to;
60 static charset_t ch_mac;
62 static int usedots = 0;
63 static uint16_t conv_flags = 0;
64 static int dry_run = 0;
68 char Cnid_srv[256] = "localhost";
71 extern struct charset_functions charset_iso8859_adapted;
74 #define MAX(a,b) (((a)>(b))?(a):(b))
78 #define VETO "./../.AppleDB/.AppleDouble/.AppleDesktop/.Parent/"
80 static int veto(const char *path)
83 char *veto_str = VETO;
89 for(i=0, j=0; veto_str[i] != '\0'; i++) {
90 if (veto_str[i] == '/') {
91 if ((j>0) && (path[j] == '\0'))
95 if (veto_str[i] != path[j]) {
96 while ((veto_str[i] != '/')
97 && (veto_str[i] != '\0'))
109 static int do_rename( char* src, char *dst, struct stat *st)
111 char adsrc[ MAXPATHLEN + 1];
114 if (!stat(dst, &tmp_st)) {
115 fprintf (stderr, "error: cannot rename %s to %s, destination exists\n", src, dst);
119 if ( rename( src, dst ) < 0 ) {
120 fprintf (stderr, "error: cannot rename %s to %s, %s\n", src, dst, strerror(errno));
124 if (S_ISDIR(st->st_mode))
127 strcpy( adsrc, ad_path( src, 0 ));
129 if (rename( adsrc, ad_path( dst, 0 )) < 0 ) {
132 if (errno == ENOENT) {
133 if (stat(adsrc, &ad_st)) /* source has no ressource fork, */
137 fprintf (stderr, "failed to rename resource fork, error: %s\n", strerror(errno));
146 static char *convert_name(char *name, struct stat *st, cnid_t cur_did)
148 static char buffer[MAXPATHLEN +2]; /* for convert_charset dest_len parameter +2 */
151 int require_conversion = 0;
152 uint16_t flags = conv_flags;
155 p = (unsigned char *)name;
156 q = (unsigned char *)buffer;
158 /* optimize for ascii case */
160 if ( *p >= 0x80 || *p == ':') {
161 require_conversion = 1;
167 if (!require_conversion) {
169 fprintf(stdout, "no conversion required\n");
173 /* convert charsets */
174 q=(unsigned char *)buffer;
175 p=(unsigned char *)name;
177 outlen = convert_charset(ch_from, ch_to, ch_mac, (char *)p, strlen((char *)p), (char *)q, sizeof(buffer) -2, &flags);
178 if ((size_t)-1 == outlen) {
179 if ( ch_to == CH_UTF8) {
180 /* maybe name is already in UTF8? */
182 q = (unsigned char *)buffer;
183 p = (unsigned char *)name;
184 outlen = convert_charset(ch_to, ch_to, ch_mac, (char *)p, strlen((char *)p), (char *)q, sizeof(buffer) -2, &flags);
185 if ((size_t)-1 == outlen) {
186 /* it's not UTF8... */
187 fprintf(stderr, "ERROR: conversion from '%s' to '%s' for '%s' in DID %u failed!!!\n",
188 from_charset, to_charset, name, ntohl(cur_did));
192 if (!strcmp(buffer, name)) {
196 fprintf(stderr, "ERROR: conversion from '%s' to '%s' for '%s' in DID %u failed. Please check this!\n",
197 from_charset, to_charset, name, ntohl(cur_did));
201 if (strcmp (name, buffer)) {
203 fprintf(stdout, "dry_run: would rename %s to %s.\n", name, buffer);
205 else if (!do_rename(name, buffer, st)) {
206 if (CNID_INVALID != (id = cnid_add(cdb, st, cur_did, buffer, strlen(buffer), 0)))
207 fprintf(stdout, "converted '%s' to '%s' (ID %u, DID %u).\n",
208 name, buffer, ntohl(id), ntohl(cur_did));
211 else if (verbose > 1)
212 fprintf(stdout, "no conversion required\n");
217 static int check_dirent(char** name, cnid_t cur_did)
225 if (stat(*name, &st) != 0) {
235 if (S_ISDIR(st.st_mode)){
240 fprintf(stdout, "Checking: '%s' - ", *name);
242 *name = convert_name(*name, &st, cur_did);
247 static int check_adouble(DIR *curdir, char * path _U_)
250 struct dirent* entry;
251 struct dirent* ad_entry;
254 strlcat(curpath, "/", sizeof(curpath));
255 strlcat(curpath, ".AppleDouble", sizeof(curpath));
257 if (NULL == (adouble = opendir(curpath))) {
261 while (NULL != (ad_entry=readdir(adouble)) ) {
262 if (veto(ad_entry->d_name))
266 while (NULL != (entry=readdir(curdir)) ) {
267 if (!strcmp(ad_entry->d_name, entry->d_name)) {
273 fprintf (stderr, "found orphaned resource file %s", ad_entry->d_name);
282 static cnid_t add_dir_db(char *name, cnid_t cur_did)
287 if (CNID_INVALID != ( id = cnid_get(cdb, cur_did, name, strlen(name))) )
295 if (stat(name, &st)) {
296 fprintf( stderr, "dir '%s' cannot be stat'ed, error %u\n", name, errno);
300 id = cnid_add(cdb, &st, did, name, strlen(name), 0);
302 fprintf (stderr, "added '%s' to DID %u as %u\n", name, ntohl(did), ntohl(id));
306 static int getdir(DIR *curdir, char ***names)
308 struct dirent* entry;
309 char **tmp = NULL, **new=NULL;
313 while ((entry=readdir(curdir)) != NULL) {
314 new = (char **) realloc (tmp, (i+1) * sizeof(char*));
316 fprintf(stderr, "out of memory");
320 name = strdup(entry->d_name);
322 fprintf(stderr, "out of memory");
325 tmp[i]= (char*) name;
333 static int checkdir(DIR *curdir, char *path, cnid_t cur_did)
340 size_t len=strlen(curpath);
346 check_adouble(curdir, path);
350 fprintf( stdout, "\nchecking DIR '%s' with ID %u\n", path, ntohl(cur_did));
352 n = getdir(curdir, &names);
357 ret = check_dirent(&name, cur_did);
359 id = add_dir_db(name, cur_did);
360 if ( id == 0 && !dry_run )
361 continue; /* skip, no ID */
364 strlcat(curpath, "/", sizeof(curpath));
365 strlcat(curpath, name, sizeof(curpath));
366 cdir = opendir(curpath);
368 fprintf( stderr, "ERROR: cannot open DIR '%s' with ID %u\n", curpath, ntohl(cur_did));
371 checkdir(cdir, curpath, id);
376 fprintf( stdout, "returned to DIR '%s' with ID %u\n", path, ntohl(cur_did));
383 fprintf( stdout, "leaving DIR '%s' with ID %u\n\n", path, ntohl(cur_did));
388 static int init(char* path)
392 if (NULL == (cdb = cnid_open (path, 0, cnid_type, 0, "localhost", "4700")) ) {
393 fprintf (stderr, "ERROR: cannot open CNID database in '%s'\n", path);
394 fprintf (stderr, "ERROR: check the logs for reasons, aborting\n");
397 cnid_getstamp(cdb, db_stamp, sizeof(db_stamp));
400 startdir = opendir(path);
401 strlcpy(curpath, path, sizeof(curpath));
402 checkdir (startdir, path, cdir_id);
410 static void usage( char * name )
412 fprintf( stderr, "usage:\t%s [-ndv] -c cnid -f fromcode -t tocode [-m maccode] path\n", name );
413 fprintf( stderr, "Try `%s -h' for more information.\n", name );
417 static void print_version (void)
419 fprintf( stderr, "uniconv - Netatalk %s\n", VERSION );
422 static void help (void)
424 fprintf (stdout, "\nuniconv, a tool to convert between various Netatalk volume encodings\n");
425 fprintf (stdout, "\nUsage: uniconv [-ndv] -c cnid -f fromcode -t tocode [-m maccode] path\n\n");
426 fprintf (stdout, "Examples:\n");
427 fprintf (stdout, " uniconv -c dbd -f ASCII -t UTF8 -m MAC_ROMAN /path/to/share\n");
428 fprintf (stdout, " uniconv -c cdb -f ISO-8859-1 -t UTF8 -m MAC_ROMAN /path/to/share\n");
429 fprintf (stdout, " uniconv -c cdb -f ISO-8859-ADAPTED -t ASCII -m MAC_ROMAN /path/to/share\n");
430 fprintf (stdout, " uniconv -f UTF8 -t ASCII -m MAC_ROMAN /path/to/share\n\n");
431 fprintf (stdout, "Options:\n");
432 fprintf (stdout, "\t-f\tencoding to convert from, use ASCII for CAP encoded volumes\n");
433 fprintf (stdout, "\t-t\tvolume encoding to convert to, e.g. UTF8.\n");
434 fprintf (stdout, "\t-m\tMacintosh client codepage, required for CAP encoded volumes.\n");
435 fprintf (stdout, "\t\tDefaults to `MAC_ROMAN'\n");
436 fprintf (stdout, "\t-n\t`dry run', don't change anything.\n");
437 fprintf (stdout, "\t-d\tDon't CAP encode leading dots (:2e).\n");
438 fprintf (stdout, "\t-c\tCNID backend used on this volume, usually cdb or dbd.\n");
439 fprintf (stdout, "\t\tIf not specified, the default cnid backend `%s' is used\n", DEFAULT_CNID_SCHEME);
440 fprintf (stdout, "\t-v\tVerbose output, use twice for maximum logging.\n");
441 fprintf (stdout, "\t-V\tPrint version and exit\n");
442 fprintf (stdout, "\t-h\tThis help screen\n\n");
443 fprintf (stdout, "WARNING:\n");
444 fprintf (stdout, " Setting the wrong options might render your data unusable!!!\n");
445 fprintf (stdout, " Make sure you know what you are doing. Always backup your data first.\n\n");
446 fprintf (stdout, " It is *strongly* recommended to do a `dry run' first and to check the\n");
447 fprintf (stdout, " output for conversion errors.\n");
448 fprintf (stdout, " USE AT YOUR OWN RISK!!!\n\n");
453 int main(int argc, char *argv[])
455 char path[MAXPATHLEN];
459 conv_flags = CONV_UNESCAPEHEX | CONV_ESCAPEHEX | CONV_ESCAPEDOTS;
461 #ifdef HAVE_SETLINEBUF
465 while ((c = getopt (argc, argv, "f:m:t:c:dnvVh")) != -1)
469 from_charset = strdup(optarg);
472 to_charset = strdup(optarg);
475 mac_charset = strdup(optarg);
478 conv_flags &= ~CONV_ESCAPEDOTS;
482 fprintf (stderr, "doing dry run, volume will *not* be changed\n");
486 cnid_type = strdup(optarg);
487 fprintf (stderr, "CNID backend set to: %s\n", cnid_type);
504 if ( argc - optind != 1 ) {
508 set_processname("uniconv");
510 if ( from_charset == NULL || to_charset == NULL) {
511 fprintf (stderr, "required charsets not specified\n");
515 if ( mac_charset == NULL )
516 mac_charset = "MAC_ROMAN";
518 if ( cnid_type == NULL)
519 cnid_type = DEFAULT_CNID_SCHEME;
523 strlcpy(path, argv[optind], sizeof(path));
525 /* deal with relative path */
527 fprintf (stderr, "ERROR: cannot chdir to '%s'\n", path);
531 if (NULL == (getcwd(path, sizeof(path))) ) {
532 fprintf (stderr, "ERROR: getcwd failed\n");
537 atalk_register_charset(&charset_iso8859_adapted);
539 if ( (charset_t) -1 == ( ch_from = add_charset(from_charset)) ) {
540 fprintf( stderr, "Setting codepage %s as source codepage failed", from_charset);
544 if ( (charset_t) -1 == ( ch_to = add_charset(to_charset)) ) {
545 fprintf( stderr, "Setting codepage %s as destination codepage failed", to_charset);
549 if ( (charset_t) -1 == ( ch_mac = add_charset(mac_charset)) ) {
550 fprintf( stderr, "Setting codepage %s as mac codepage failed", mac_charset);