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