]> arthur.barton.de Git - netatalk.git/blob - bin/uniconv/uniconv.c
b3c8ee91b2677e3e6e27235f55308b33d954f6b6
[netatalk.git] / bin / uniconv / uniconv.c
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
48 static struct _cnid_db *cdb;
49 static char curpath[MAXPATHLEN];
50 static cnid_t cdir_id;
51 static char db_stamp[ADEDLEN_PRIVSYN];
52
53 static charset_t ch_from;
54 char* from_charset;
55 static charset_t ch_to;
56 char* to_charset;
57 static charset_t ch_mac;
58 char* mac_charset;
59 static int usedots = 0;
60 static u_int16_t conv_flags = 0;
61 static int dry_run = 0;
62 static int verbose=0;
63 char *cnid_type;
64
65 char             Cnid_srv[256] = "localhost";
66 int              Cnid_port = 4700;
67
68 extern  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
77 static 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
106 static 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
143 static char *convert_name(char *name, struct stat *st, cnid_t cur_did)
144 {
145         static char   buffer[MAXPATHLEN];
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 = name;
153         q = 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=buffer;
172         p=name;
173
174         outlen = convert_charset(ch_from, ch_to, ch_mac, p, strlen(p), q, sizeof(buffer), &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 = (char*) buffer;
180                 p = name;
181                 outlen = convert_charset(ch_to, ch_to, ch_mac, p, strlen(p), q, sizeof(buffer), &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                 buffer[outlen] = 0;
189
190                 if (!strcmp(buffer, name)) {
191                         return name;
192                 }
193            }
194            fprintf(stderr, "ERROR: conversion from '%s' to '%s' for '%s' in DID %u failed. Please check this!\n", 
195                         from_charset, to_charset, name, ntohl(cur_did));
196            return name;
197         }
198         buffer[outlen] = 0;
199         if (strcmp (name, buffer)) {
200             if (dry_run) {
201                 fprintf(stdout, "dry_run: would rename %s to %s.\n", name, buffer);
202             }   
203             else if (!do_rename(name, buffer, st)) {
204                 if (CNID_INVALID != (id = cnid_add(cdb, st, cur_did, buffer, strlen(buffer), 0))) 
205                         fprintf(stdout, "converted '%s' to '%s' (ID %u, DID %u).\n", 
206                                 name, buffer, ntohl(id), ntohl(cur_did));
207             }
208         }
209         else if (verbose > 1)
210             fprintf(stdout, "no conversion required\n");
211         
212         return (buffer);
213 }
214
215 static int check_dirent(char** name, cnid_t cur_did)
216 {
217         struct stat st;
218         int ret = 0;
219
220         if (veto(*name))
221                 return 0;
222
223         if (stat(*name, &st) != 0) {
224                 switch (errno) {
225                     case ELOOP:
226                     case ENOENT:
227                         return 0;
228                     default:
229                         return (-1);
230                 }
231         }
232
233         if (S_ISDIR(st.st_mode)){
234                 ret = 1;
235         } 
236
237         if (verbose > 1)
238             fprintf(stdout, "Checking: '%s' - ", *name);
239
240         *name = convert_name(*name, &st, cur_did);
241
242         return ret;
243 }
244
245 static int check_adouble(DIR *curdir, char * path)
246 {
247         DIR *adouble;
248         struct dirent* entry;
249         struct dirent* ad_entry;
250         int found = 0;
251
252         strlcat(curpath, "/", sizeof(curpath));
253         strlcat(curpath, ".AppleDouble", sizeof(curpath));
254
255         if (NULL == (adouble = opendir(curpath))) {
256                 return(-1);
257         }
258
259         while (NULL != (ad_entry=readdir(adouble)) ) {
260                 if (veto(ad_entry->d_name))
261                         break;
262                 found = 0;
263                 rewinddir(curdir);
264                 while (NULL != (entry=readdir(curdir)) ) {
265                         if (!strcmp(ad_entry->d_name, entry->d_name)) {
266                                 found = 1;
267                                 break;
268                         }
269                 }
270                 if (!found) {
271                         fprintf (stderr, "found orphaned resource file %s", ad_entry->d_name);
272                 }
273         }
274                         
275         rewinddir(curdir);
276         closedir(adouble);
277         return (0);
278 }
279                 
280 static cnid_t add_dir_db(char *name, cnid_t cur_did)
281 {
282         cnid_t id, did;
283         struct stat st;
284
285         if (CNID_INVALID != ( id = cnid_get(cdb, cur_did, name, strlen(name))) )
286             return id;
287
288         if (dry_run) {
289                 return 0;
290         }
291
292         did = cur_did;
293         if (stat(name, &st)) {
294              fprintf( stderr, "dir '%s' cannot be stat'ed, error %u\n", name, errno);
295              return 0;
296         }
297
298         id = cnid_add(cdb, &st, did, name, strlen(name), 0);
299         
300         fprintf (stderr, "added '%s' to DID %u as %u\n", name, ntohl(did), ntohl(id));
301         return id;
302 }
303
304 static int getdir(DIR *curdir, char ***names)
305 {
306         struct dirent* entry;
307         char **tmp = NULL, **new=NULL;
308         char *name;
309         int i=0;
310
311         while ((entry=readdir(curdir)) != NULL) {
312                 new = (char **) realloc (tmp, (i+1) * sizeof(char*));
313                 if (new == NULL) {
314                         fprintf(stderr, "out of memory");
315                         exit (-1);
316                 }
317                 tmp = new;
318                 name = strdup(entry->d_name);
319                 if (name == NULL) {
320                         fprintf(stderr, "out of memory");
321                         exit (-1);
322                 }
323                 tmp[i]= (char*) name;
324                 i++;
325         };
326
327         *names = tmp;
328         return i;
329 }
330
331 static int checkdir(DIR *curdir, char *path, cnid_t cur_did)
332 {
333         DIR* cdir;
334         int ret = 0;
335         cnid_t id;
336         char *name, *tmp;
337         int n;
338         size_t len=strlen(curpath);
339
340         char **names;
341
342         chdir(path);
343
344         check_adouble(curdir, path);
345         curpath[len] = 0;
346
347         if (verbose)
348             fprintf( stdout, "\nchecking DIR '%s' with ID %u\n", path, ntohl(cur_did));
349
350         n = getdir(curdir, &names);
351
352         while (n--) {
353                 name = names[n];
354                 tmp = strdup(name);
355                 ret = check_dirent(&name, cur_did);
356                 if (ret==1) {
357                     id = add_dir_db(name, cur_did);
358                     if ( id == 0 && !dry_run ) 
359                         continue;  /* skip, no ID */ 
360                     if ( dry_run )
361                         name = tmp;
362                     strlcat(curpath, "/", sizeof(curpath));
363                     strlcat(curpath, name, sizeof(curpath));
364                     cdir = opendir(curpath);
365                     if (cdir == NULL) {
366                         fprintf( stderr, "ERROR: cannot open DIR '%s' with ID %u\n", curpath, ntohl(cur_did));
367                         continue;
368                     }
369                     checkdir(cdir, curpath, id);
370                     closedir(cdir);
371                     curpath[len] = 0;
372                     chdir(path);
373                     if (verbose)
374                         fprintf( stdout, "returned to DIR '%s' with ID %u\n", path, ntohl(cur_did));
375                 }
376                 free(names[n]);
377                 free(tmp);
378         }
379         free(names);
380         if (verbose)
381             fprintf( stdout, "leaving DIR '%s' with ID %u\n\n", path, ntohl(cur_did));
382
383         return 0;
384 }
385
386 static int init(char* path)
387 {
388         DIR* startdir;
389
390         if (NULL == (cdb = cnid_open (path, 0, cnid_type, 0)) ) {
391                 fprintf (stderr, "ERROR: cannot open CNID database in '%s'\n", path);
392                 fprintf (stderr, "ERROR: check the logs for reasons, aborting\n");
393                 return -1;
394         }
395         cnid_getstamp(cdb, db_stamp, sizeof(db_stamp));
396         cdir_id = htonl(2);
397
398         startdir = opendir(path);
399         strlcpy(curpath, path, sizeof(curpath));
400         checkdir (startdir, path, cdir_id);
401         closedir(startdir);
402
403         cnid_close(cdb);
404
405         return (0);
406 }
407
408 static void usage( char * name )
409 {
410     fprintf( stderr, "usage:\t%s [-ndv] -c cnid -f fromcode -t tocode [-m maccode] path\n", name );
411     fprintf( stderr, "Try `%s -h' for more information.\n", name );
412     exit( 1 );
413 }
414
415 static void print_version ()
416 {
417     fprintf( stderr, "uniconv - Netatalk %s\n", VERSION );
418 }
419
420 static void help ()
421 {
422     fprintf (stdout, "\nuniconv, a tool to convert between various Netatalk volume encodings\n");
423     fprintf (stdout, "\nUsage:  uniconv [-ndv] -c cnid -f fromcode -t tocode [-m maccode] path\n\n");
424     fprintf (stdout, "Examples:\n");
425     fprintf (stdout, "     uniconv -c dbd -f ASCII -t UTF8 -m MAC_ROMAN /path/to/share\n");
426     fprintf (stdout, "     uniconv -c cdb -f ISO-8859-1 -t UTF8 -m MAC_ROMAN /path/to/share\n");
427     fprintf (stdout, "     uniconv -c cdb -f ISO-8859-ADAPTED -t ASCII -m MAC_ROMAN /path/to/share\n");
428     fprintf (stdout, "     uniconv -f UTF8 -t ASCII -m MAC_ROMAN /path/to/share\n\n");
429     fprintf (stdout, "Options:\n");
430     fprintf (stdout, "\t-f\tencoding to convert from, use ASCII for CAP encoded volumes\n");
431     fprintf (stdout, "\t-t\tvolume encoding to convert to, e.g. UTF8.\n");
432     fprintf (stdout, "\t-m\tMacintosh client codepage, required for CAP encoded volumes.\n");
433     fprintf (stdout, "\t\tDefaults to `MAC_ROMAN'\n");
434     fprintf (stdout, "\t-n\t`dry run', don't change anything.\n");
435     fprintf (stdout, "\t-d\tDon't CAP encode leading dots (:2e).\n");
436     fprintf (stdout, "\t-c\tCNID backend used on this volume, usually cdb or dbd.\n");
437     fprintf (stdout, "\t\tIf not specified, the default cnid backend `%s' is used\n", DEFAULT_CNID_SCHEME);
438     fprintf (stdout, "\t-v\tVerbose output, use twice for maximum logging.\n");
439     fprintf (stdout, "\t-V\tPrint version and exit\n");
440     fprintf (stdout, "\t-h\tThis help screen\n\n");
441     fprintf (stdout, "WARNING:\n");
442     fprintf (stdout, "     Setting the wrong options might render your data unusable!!!\n");
443     fprintf (stdout, "     Make sure you know what you are doing. Always backup your data first.\n\n");
444     fprintf (stdout, "     It is *strongly* recommended to do a `dry run' first and to check the\n");
445     fprintf (stdout, "     output for conversion errors.\n");
446     fprintf (stdout, "     USE AT YOUR OWN RISK!!!\n\n");
447
448 }
449
450
451 int main(int argc, char *argv[])
452 {
453         char path[MAXPATHLEN];
454         int  c;
455
456         path[0]= 0;
457         conv_flags = CONV_UNESCAPEHEX | CONV_ESCAPEHEX | CONV_ESCAPEDOTS;
458
459 #ifdef HAVE_SETLINEBUF
460         setlinebuf(stdout); 
461 #endif        
462
463         while ((c = getopt (argc, argv, "f:m:t:c:nvVh")) != -1)
464         switch (c)
465         {
466         case 'f':
467                 from_charset = strdup(optarg);
468                 break;
469         case 't':
470                 to_charset = strdup(optarg);
471                 break;
472         case 'm':
473                 mac_charset = strdup(optarg);
474                 break;
475         case 'd':
476                 conv_flags &= !CONV_ESCAPEDOTS;
477                 usedots = 1;
478                 break;
479         case 'n':
480                 fprintf (stderr, "doing dry run, volume will *not* be changed\n");
481                 dry_run = 1;
482                 break;
483         case 'c':
484                 cnid_type = strdup(optarg);
485                 fprintf (stderr, "CNID backend set to: %s\n", cnid_type);
486                 break;
487         case 'v':
488                 verbose++;
489                 break;
490         case 'V':
491                 print_version();
492                 exit (1);
493                 break;
494         case 'h':
495                 help();
496                 exit(1);
497                 break;
498         default:
499                 break;
500         }
501
502         if ( argc - optind != 1 ) {
503             usage( argv[0] );
504             exit( 1 );
505         }
506         set_processname("uniconv");
507
508         if ( from_charset == NULL || to_charset == NULL) {
509                 fprintf (stderr, "required charsets not specified\n");
510                 exit(-1);
511         }
512
513         if ( mac_charset == NULL )
514                 mac_charset = "MAC_ROMAN";
515
516         if ( cnid_type == NULL)
517                 cnid_type = DEFAULT_CNID_SCHEME;
518         
519
520         /* get path */
521         strlcpy(path, argv[optind], sizeof(path));
522
523         /* deal with relative path */   
524         if (chdir(path)) {
525                 fprintf (stderr, "ERROR: cannot chdir to '%s'\n", path);
526                 return (-1);
527         }
528
529         if (NULL == (getcwd(path, sizeof(path))) ) {
530                 fprintf (stderr, "ERROR: getcwd failed\n");
531                 return (-1);
532         }
533
534         /* set charsets */
535         atalk_register_charset(&charset_iso8859_adapted);
536
537         if ( (charset_t) -1 == ( ch_from = add_charset(from_charset)) ) {
538                 fprintf( stderr, "Setting codepage %s as source codepage failed", from_charset);
539                 exit (-1);
540         }
541
542         if ( (charset_t) -1 == ( ch_to = add_charset(to_charset)) ) {
543                 fprintf( stderr, "Setting codepage %s as destination codepage failed", to_charset);
544                 exit (-1);
545         }
546
547         if ( (charset_t) -1 == ( ch_mac = add_charset(mac_charset)) ) {
548                 fprintf( stderr, "Setting codepage %s as mac codepage failed", mac_charset);
549                 exit (-1);
550         }
551
552         cnid_init();
553         init(path);
554
555         return (0);
556 }