1/* 2 uniconv - convert volume encodings 3 Copyright (C) Bjoern Fernhomberg 2004 4 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. 9 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. 14 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. 18*/ 19 20 21#ifdef HAVE_CONFIG_H 22#include "config.h" 23#endif /* HAVE_CONFIG_H */ 24#include <errno.h> 25#include <stdlib.h> 26#include <stdio.h> 27#include <fcntl.h> 28#include <unistd.h> 29#include <string.h> 30#include <sys/mman.h> 31#include <sys/stat.h> 32#include <sys/time.h> 33#include <ctype.h> 34#include <sys/types.h> 35#include <sys/param.h> 36#include <pwd.h> 37#include <dirent.h> 38#include <atalk/afp.h> 39#include <atalk/unicode.h> 40#include <atalk/util.h> 41#include <atalk/logger.h> 42 43#include "atalk/cnid.h" 44#ifndef MAXPATHLEN 45#define MAXPATHLEN 4096 46#endif 47 48static struct _cnid_db *cdb; 49static char curpath[MAXPATHLEN]; 50static cnid_t cdir_id; 51static char db_stamp[ADEDLEN_PRIVSYN]; 52 53static charset_t ch_from; 54char* from_charset; 55static charset_t ch_to; 56char* to_charset; 57static charset_t ch_mac; 58char* mac_charset; 59static int usedots = 0; 60static u_int16_t conv_flags = 0; 61static int dry_run = 0; 62static int verbose=0; 63char *cnid_type; 64 65char Cnid_srv[256] = "localhost"; 66int Cnid_port = 4700; 67 68extern struct charset_functions charset_iso8859_adapted; 69 70#ifndef MAX 71#define MAX(a,b) (((a)>(b))?(a):(b)) 72#endif 73 74 75#define VETO "./../.AppleDB/.AppleDouble/.AppleDesktop/.Parent/" 76 77static int veto(const char *path) 78{ 79 int i,j; 80 char *veto_str = VETO; 81 82 83 if ((path == NULL)) 84 return 0; 85 86 for(i=0, j=0; veto_str[i] != '\0'; i++) { 87 if (veto_str[i] == '/') { 88 if ((j>0) && (path[j] == '\0')) 89 return 1; 90 j = 0; 91 } else { 92 if (veto_str[i] != path[j]) { 93 while ((veto_str[i] != '/') 94 && (veto_str[i] != '\0')) 95 i++; 96 j = 0; 97 continue; 98 } 99 j++; 100 } 101 } 102 return 0; 103} 104 105 106static int do_rename( char* src, char *dst, struct stat *st) 107{ 108 char adsrc[ MAXPATHLEN + 1]; 109 struct stat tmp_st; 110 111 if (!stat(dst, &tmp_st)) { 112 fprintf (stderr, "error: cannot rename %s to %s, destination exists\n", src, dst); 113 return -1; 114 } 115 116 if ( rename( src, dst ) < 0 ) { 117 fprintf (stderr, "error: cannot rename %s to %s, %s\n", src, dst, strerror(errno)); 118 return -1; 119 } 120 121 if (S_ISDIR(st->st_mode)) 122 return 0; 123 124 strcpy( adsrc, ad_path( src, 0 )); 125 126 if (rename( adsrc, ad_path( dst, 0 )) < 0 ) { 127 struct stat ad_st; 128 129 if (errno == ENOENT) { 130 if (stat(adsrc, &ad_st)) /* source has no ressource fork, */ 131 return 0; 132 } 133 else { 134 fprintf (stderr, "failed to rename resource fork, error: %s\n", strerror(errno)); 135 return -1; 136 } 137 138 } 139 return 0; 140} 141 142 143static char *convert_name(char *name, struct stat *st, cnid_t cur_did) 144{ 145 static char buffer[MAXPATHLEN +2]; /* for convert_charset dest_len parameter +2 */ 146 size_t outlen = 0; 147 unsigned char *p,*q; 148 int require_conversion = 0; 149 u_int16_t flags = conv_flags; 150 cnid_t id; 151 152 p = (unsigned char *)name; 153 q = (unsigned char *)buffer; 154 155 /* optimize for ascii case */ 156 while (*p != 0) { 157 if ( *p >= 0x80 || *p == ':') { 158 require_conversion = 1; 159 break; 160 } 161 p++; 162 } 163 164 if (!require_conversion) { 165 if (verbose > 1) 166 fprintf(stdout, "no conversion required\n"); 167 return name; 168 } 169 170 /* convert charsets */ 171 q=(unsigned char *)buffer; 172 p=(unsigned char *)name; 173 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? */ 178 flags = conv_flags; 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)); 186 return name; 187 } 188 189 if (!strcmp(buffer, name)) { 190 return name; 191 } 192 } 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)); 195 return name; 196 } 197 198 if (strcmp (name, buffer)) { 199 if (dry_run) { 200 fprintf(stdout, "dry_run: would rename %s to %s.\n", name, buffer); 201 } 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)); 206 } 207 } 208 else if (verbose > 1) 209 fprintf(stdout, "no conversion required\n"); 210 211 return (buffer); 212} 213 214static int check_dirent(char** name, cnid_t cur_did) 215{ 216 struct stat st; 217 int ret = 0; 218 219 if (veto(*name)) 220 return 0; 221 222 if (stat(*name, &st) != 0) { 223 switch (errno) { 224 case ELOOP: 225 case ENOENT: 226 return 0; 227 default: 228 return (-1); 229 } 230 } 231 232 if (S_ISDIR(st.st_mode)){ 233 ret = 1; 234 } 235 236 if (verbose > 1) 237 fprintf(stdout, "Checking: '%s' - ", *name); 238 239 *name = convert_name(*name, &st, cur_did); 240 241 return ret; 242} 243 244static int check_adouble(DIR *curdir, char * path _U_) 245{ 246 DIR *adouble; 247 struct dirent* entry; 248 struct dirent* ad_entry; 249 int found = 0; 250 251 strlcat(curpath, "/", sizeof(curpath)); 252 strlcat(curpath, ".AppleDouble", sizeof(curpath)); 253 254 if (NULL == (adouble = opendir(curpath))) { 255 return(-1); 256 } 257 258 while (NULL != (ad_entry=readdir(adouble)) ) { 259 if (veto(ad_entry->d_name)) 260 break; 261 found = 0; 262 rewinddir(curdir); 263 while (NULL != (entry=readdir(curdir)) ) { 264 if (!strcmp(ad_entry->d_name, entry->d_name)) { 265 found = 1; 266 break; 267 } 268 } 269 if (!found) { 270 fprintf (stderr, "found orphaned resource file %s", ad_entry->d_name); 271 } 272 } 273 274 rewinddir(curdir); 275 closedir(adouble); 276 return (0); 277} 278 279static cnid_t add_dir_db(char *name, cnid_t cur_did) 280{ 281 cnid_t id, did; 282 struct stat st; 283 284 if (CNID_INVALID != ( id = cnid_get(cdb, cur_did, name, strlen(name))) ) 285 return id; 286 287 if (dry_run) { 288 return 0; 289 } 290 291 did = cur_did; 292 if (stat(name, &st)) { 293 fprintf( stderr, "dir '%s' cannot be stat'ed, error %u\n", name, errno); 294 return 0; 295 } 296 297 id = cnid_add(cdb, &st, did, name, strlen(name), 0); 298 299 fprintf (stderr, "added '%s' to DID %u as %u\n", name, ntohl(did), ntohl(id)); 300 return id; 301} 302 303static int getdir(DIR *curdir, char ***names) 304{ 305 struct dirent* entry; 306 char **tmp = NULL, **new=NULL; 307 char *name; 308 int i=0; 309 310 while ((entry=readdir(curdir)) != NULL) { 311 new = (char **) realloc (tmp, (i+1) * sizeof(char*)); 312 if (new == NULL) { 313 fprintf(stderr, "out of memory"); 314 exit (-1); 315 } 316 tmp = new; 317 name = strdup(entry->d_name); 318 if (name == NULL) { 319 fprintf(stderr, "out of memory"); 320 exit (-1); 321 } 322 tmp[i]= (char*) name; 323 i++; 324 }; 325 326 *names = tmp; 327 return i; 328} 329 330static int checkdir(DIR *curdir, char *path, cnid_t cur_did) 331{ 332 DIR* cdir; 333 int ret = 0; 334 cnid_t id; 335 char *name, *tmp; 336 int n; 337 size_t len=strlen(curpath); 338 339 char **names; 340 341 chdir(path); 342 343 check_adouble(curdir, path); 344 curpath[len] = 0; 345 346 if (verbose) 347 fprintf( stdout, "\nchecking DIR '%s' with ID %u\n", path, ntohl(cur_did)); 348 349 n = getdir(curdir, &names); 350 351 while (n--) { 352 name = names[n]; 353 tmp = strdup(name); 354 ret = check_dirent(&name, cur_did); 355 if (ret==1) { 356 id = add_dir_db(name, cur_did); 357 if ( id == 0 && !dry_run ) 358 continue; /* skip, no ID */ 359 if ( dry_run ) 360 name = tmp; 361 strlcat(curpath, "/", sizeof(curpath)); 362 strlcat(curpath, name, sizeof(curpath)); 363 cdir = opendir(curpath); 364 if (cdir == NULL) { 365 fprintf( stderr, "ERROR: cannot open DIR '%s' with ID %u\n", curpath, ntohl(cur_did)); 366 continue; 367 } 368 checkdir(cdir, curpath, id); 369 closedir(cdir); 370 curpath[len] = 0; 371 chdir(path); 372 if (verbose) 373 fprintf( stdout, "returned to DIR '%s' with ID %u\n", path, ntohl(cur_did)); 374 } 375 free(names[n]); 376 free(tmp); 377 } 378 free(names); 379 if (verbose) 380 fprintf( stdout, "leaving DIR '%s' with ID %u\n\n", path, ntohl(cur_did)); 381 382 return 0; 383} 384 385static int init(char* path) 386{ 387 DIR* startdir; 388 389 if (NULL == (cdb = cnid_open (path, 0, cnid_type, 0, "localhost", "4700")) ) { 390 fprintf (stderr, "ERROR: cannot open CNID database in '%s'\n", path); 391 fprintf (stderr, "ERROR: check the logs for reasons, aborting\n"); 392 return -1; 393 } 394 cnid_getstamp(cdb, db_stamp, sizeof(db_stamp)); 395 cdir_id = htonl(2); 396 397 startdir = opendir(path); 398 strlcpy(curpath, path, sizeof(curpath)); 399 checkdir (startdir, path, cdir_id); 400 closedir(startdir); 401 402 cnid_close(cdb); 403 404 return (0); 405} 406 407static void usage( char * name ) 408{ 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 ); 411 exit( 1 ); 412} 413 414static void print_version (void) 415{ 416 fprintf( stderr, "uniconv - Netatalk %s\n", VERSION ); 417} 418 419static void help (void) 420{ 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"); 446 447} 448 449 450int main(int argc, char *argv[]) 451{ 452 char path[MAXPATHLEN]; 453 int c; 454 455 path[0]= 0; 456 conv_flags = CONV_UNESCAPEHEX | CONV_ESCAPEHEX | CONV_ESCAPEDOTS; 457 458#ifdef HAVE_SETLINEBUF 459 setlinebuf(stdout); 460#endif 461 462 while ((c = getopt (argc, argv, "f:m:t:c:dnvVh")) != -1) 463 switch (c) 464 { 465 case 'f': 466 from_charset = strdup(optarg); 467 break; 468 case 't': 469 to_charset = strdup(optarg); 470 break; 471 case 'm': 472 mac_charset = strdup(optarg); 473 break; 474 case 'd': 475 conv_flags &= ~CONV_ESCAPEDOTS; 476 usedots = 1; 477 break; 478 case 'n': 479 fprintf (stderr, "doing dry run, volume will *not* be changed\n"); 480 dry_run = 1; 481 break; 482 case 'c': 483 cnid_type = strdup(optarg); 484 fprintf (stderr, "CNID backend set to: %s\n", cnid_type); 485 break; 486 case 'v': 487 verbose++; 488 break; 489 case 'V': 490 print_version(); 491 exit (1); 492 break; 493 case 'h': 494 help(); 495 exit(1); 496 break; 497 default: 498 break; 499 } 500 501 if ( argc - optind != 1 ) { 502 usage( argv[0] ); 503 exit( 1 ); 504 } 505 set_processname("uniconv"); 506 507 if ( from_charset == NULL || to_charset == NULL) { 508 fprintf (stderr, "required charsets not specified\n"); 509 exit(-1); 510 } 511 512 if ( mac_charset == NULL ) 513 mac_charset = "MAC_ROMAN"; 514 515 if ( cnid_type == NULL) 516 cnid_type = DEFAULT_CNID_SCHEME; 517 518 519 /* get path */ 520 strlcpy(path, argv[optind], sizeof(path)); 521 522 /* deal with relative path */ 523 if (chdir(path)) { 524 fprintf (stderr, "ERROR: cannot chdir to '%s'\n", path); 525 return (-1); 526 } 527 528 if (NULL == (getcwd(path, sizeof(path))) ) { 529 fprintf (stderr, "ERROR: getcwd failed\n"); 530 return (-1); 531 } 532 533 /* set charsets */ 534 atalk_register_charset(&charset_iso8859_adapted); 535 536 if ( (charset_t) -1 == ( ch_from = add_charset(from_charset)) ) { 537 fprintf( stderr, "Setting codepage %s as source codepage failed", from_charset); 538 exit (-1); 539 } 540 541 if ( (charset_t) -1 == ( ch_to = add_charset(to_charset)) ) { 542 fprintf( stderr, "Setting codepage %s as destination codepage failed", to_charset); 543 exit (-1); 544 } 545 546 if ( (charset_t) -1 == ( ch_mac = add_charset(mac_charset)) ) { 547 fprintf( stderr, "Setting codepage %s as mac codepage failed", mac_charset); 548 exit (-1); 549 } 550 551 cnid_init(); 552 init(path); 553 554 return (0); 555} 556