]> arthur.barton.de Git - netatalk.git/blob - bin/uniconv/uniconv.c
Fix all remaining warnings from gcc -Wall that can be fixed
[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 +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
214 static 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
244 static 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                 
279 static 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
303 static 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
330 static 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
385 static int init(char* path)
386 {
387         DIR* startdir;
388
389         if (NULL == (cdb = cnid_open (path, 0, cnid_type, 0)) ) {
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
407 static 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
414 static void print_version (void)
415 {
416     fprintf( stderr, "uniconv - Netatalk %s\n", VERSION );
417 }
418
419 static 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
450 int 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 }