]> arthur.barton.de Git - netatalk.git/blob - bin/uniconv/uniconv.c
Whitespace and exclamation mark fixes
[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 <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
51 static struct _cnid_db *cdb;
52 static char curpath[MAXPATHLEN];
53 static cnid_t cdir_id;
54 static char db_stamp[ADEDLEN_PRIVSYN];
55
56 static charset_t ch_from;
57 char* from_charset;
58 static charset_t ch_to;
59 char* to_charset;
60 static charset_t ch_mac;
61 char* mac_charset;
62 static int usedots = 0;
63 static uint16_t conv_flags = 0;
64 static int dry_run = 0;
65 static int verbose=0;
66 char *cnid_type;
67
68 char             Cnid_srv[256] = "localhost";
69 int              Cnid_port = 4700;
70
71 extern  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
80 static 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
109 static 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
146 static 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
217 static 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
247 static 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                 
282 static 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
306 static 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
333 static 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
388 static 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
410 static 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
417 static void print_version (void)
418 {
419     fprintf( stderr, "uniconv - Netatalk %s\n", VERSION );
420 }
421
422 static 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
453 int 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 }