6080b3d014f78a38aaaec9a7f61e91b9782bdc86
[rsync-patches.git] / transliterate.diff
1 This patch adds an option --tr=BAD/GOOD to transliterate filenames.  It
2 can be used to remove characters illegal on the destination filesystem.
3 Jeff Weber expressed interest in this:
4
5 http://lists.samba.org/archive/rsync/2007-October/018996.html
6
7 To use this patch, run these commands for a successful build:
8
9     patch -p1 <patches/transliterate.diff
10     ./configure                                 (optional if already run)
11     make
12
13 based-on: 63f91976112b8b2118cc17eb5fc8142175566f4f
14 diff --git a/flist.c b/flist.c
15 --- a/flist.c
16 +++ b/flist.c
17 @@ -73,6 +73,7 @@ extern uid_t our_uid;
18  extern struct stats stats;
19  extern char *filesfrom_host;
20  extern char *usermap, *groupmap;
21 +extern char *tr_opt;
22  
23  extern char curr_dir[MAXPATHLEN];
24  
25 @@ -99,6 +100,8 @@ int file_total = 0; /* total of all active items over all file-lists */
26  int file_old_total = 0; /* total of active items that will soon be gone */
27  int flist_eof = 0; /* all the file-lists are now known */
28  
29 +char tr_substitutions[256];
30 +
31  #define NORMAL_NAME 0
32  #define SLASH_ENDING_NAME 1
33  #define DOTDIR_NAME 2
34 @@ -668,6 +671,23 @@ static void send_file_entry(int f, const char *fname, struct file_struct *file,
35                 stats.total_size += F_LENGTH(file);
36  }
37  
38 +static void transliterate(char *path, int len)
39 +{
40 +       while (1) {
41 +               /* Find position of any char in tr_opt in path, or the end of the path. */
42 +               int span = strcspn(path, tr_opt);
43 +               if ((len -= span) == 0)
44 +                       return;
45 +               path += span;
46 +               if ((*path = tr_substitutions[*(uchar*)path]) == '\0')
47 +                       memmove(path, path+1, len--); /* copies the trailing '\0' too. */
48 +               else {
49 +                       path++;
50 +                       len--;
51 +               }
52 +       }
53 +}
54 +
55  static struct file_struct *recv_file_entry(int f, struct file_list *flist, int xflags)
56  {
57         static int64 modtime;
58 @@ -733,9 +753,13 @@ static struct file_struct *recv_file_entry(int f, struct file_list *flist, int x
59                         outbuf.len = 0;
60                 }
61                 thisname[outbuf.len] = '\0';
62 +               basename_len = outbuf.len;
63         }
64  #endif
65  
66 +       if (tr_opt)
67 +               transliterate(thisname, basename_len);
68 +
69         if (*thisname)
70                 clean_fname(thisname, 0);
71  
72 @@ -2441,6 +2465,15 @@ struct file_list *recv_file_list(int f)
73                         parse_name_map(usermap, True);
74                 if (groupmap)
75                         parse_name_map(groupmap, False);
76 +               if (tr_opt) { /* Parse FROM/TO string and populate tr_substitutions[] */
77 +                       char *f, *t;
78 +                       if ((t = strchr(tr_opt, '/')) != NULL)
79 +                               *t++ = '\0';
80 +                       else
81 +                               t = "";
82 +                       for (f = tr_opt; *f; f++)
83 +                               tr_substitutions[*(uchar*)f] = *t ? *t++ : '\0';
84 +               }
85         }
86  
87         start_read = stats.total_read;
88 diff --git a/options.c b/options.c
89 --- a/options.c
90 +++ b/options.c
91 @@ -190,6 +190,7 @@ int logfile_format_has_i = 0;
92  int logfile_format_has_o_or_i = 0;
93  int always_checksum = 0;
94  int list_only = 0;
95 +char *tr_opt = NULL;
96  
97  #define MAX_BATCH_NAME_LEN 256 /* Must be less than MAXPATHLEN-13 */
98  char *batch_name = NULL;
99 @@ -801,6 +802,7 @@ void usage(enum logcode F)
100  #ifdef ICONV_OPTION
101    rprintf(F,"     --iconv=CONVERT_SPEC    request charset conversion of filenames\n");
102  #endif
103 +  rprintf(F,"     --tr=BAD/GOOD           transliterate filenames\n");
104    rprintf(F,"     --checksum-seed=NUM     set block/file checksum seed (advanced)\n");
105    rprintf(F," -4, --ipv4                  prefer IPv4\n");
106    rprintf(F," -6, --ipv6                  prefer IPv6\n");
107 @@ -1019,6 +1021,7 @@ static struct poptOption long_options[] = {
108    {"iconv",            0,  POPT_ARG_STRING, &iconv_opt, 0, 0, 0 },
109    {"no-iconv",         0,  POPT_ARG_NONE,   0, OPT_NO_ICONV, 0, 0 },
110  #endif
111 +  {"tr",               0,  POPT_ARG_STRING, &tr_opt, 0, 0, 0 },
112    {"ipv4",            '4', POPT_ARG_VAL,    &default_af_hint, AF_INET, 0, 0 },
113    {"ipv6",            '6', POPT_ARG_VAL,    &default_af_hint, AF_INET6, 0, 0 },
114    {"8-bit-output",    '8', POPT_ARG_VAL,    &allow_8bit_chars, 1, 0, 0 },
115 @@ -2318,6 +2321,24 @@ int parse_arguments(int *argc_p, const char ***argv_p)
116                 }
117         }
118  
119 +       if (tr_opt) {
120 +               if (*tr_opt == '/' && tr_opt[1]) {
121 +                       snprintf(err_buf, sizeof err_buf,
122 +                               "Do not start the --tr arg with a slash\n");
123 +                       return 0;
124 +               }
125 +               if (*tr_opt && *tr_opt != '/') {
126 +                       need_unsorted_flist = 1;
127 +                       arg = strchr(tr_opt, '/');
128 +                       if (arg && strchr(arg+1, '/')) {
129 +                               snprintf(err_buf, sizeof err_buf,
130 +                                       "--tr cannot transliterate slashes\n");
131 +                               return 0;
132 +                       }
133 +               } else
134 +                       tr_opt = NULL;
135 +       }
136 +
137         am_starting_up = 0;
138  
139         return 1;
140 @@ -2734,6 +2755,12 @@ void server_options(char **args, int *argc_p)
141         if (relative_paths && !implied_dirs && (!am_sender || protocol_version >= 30))
142                 args[ac++] = "--no-implied-dirs";
143  
144 +       if (tr_opt) {
145 +               if (asprintf(&arg, "--tr=%s", tr_opt) < 0)
146 +                       goto oom;
147 +               args[ac++] = arg;
148 +       }
149 +
150         if (remove_source_files == 1)
151                 args[ac++] = "--remove-source-files";
152         else if (remove_source_files)
153 diff --git a/rsync.yo b/rsync.yo
154 --- a/rsync.yo
155 +++ b/rsync.yo
156 @@ -453,6 +453,7 @@ to the detailed description below for a complete description.  verb(
157       --read-batch=FILE       read a batched update from FILE
158       --protocol=NUM          force an older protocol version to be used
159       --iconv=CONVERT_SPEC    request charset conversion of filenames
160 +     --tr=BAD/GOOD           transliterate filenames
161       --checksum-seed=NUM     set block/file checksum seed (advanced)
162   -4, --ipv4                  prefer IPv4
163   -6, --ipv6                  prefer IPv6
164 @@ -2543,6 +2544,22 @@ daemon uses the charset specified in its "charset" configuration parameter
165  regardless of the remote charset you actually pass.  Thus, you may feel free to
166  specify just the local charset for a daemon transfer (e.g. bf(--iconv=utf8)).
167  
168 +dit(bf(--tr=BAD/GOOD)) Transliterates filenames on the receiver, after the
169 +iconv conversion (if any).  This can be used to remove characters illegal
170 +on the destination filesystem.  If you use this option, consider saving a
171 +"find . -ls" listing of the source in the destination to help you determine
172 +the original filenames in case of need.
173 +
174 +The argument consists of a string of characters to remove, optionally
175 +followed by a slash and a string of corresponding characters with which to
176 +replace them.  The second string may be shorter, in which case any leftover
177 +characters in the first string are simply deleted.  For example,
178 +bf(--tr=':\/!') replaces colons with exclamation marks and deletes backslashes.
179 +Slashes cannot be transliterated because it would cause havoc.
180 +
181 +If the receiver is invoked over a remote shell, use bf(--protect-args) to
182 +stop the shell from interpreting any nasty characters in the argument.
183 +
184  dit(bf(-4, --ipv4) or bf(-6, --ipv6)) Tells rsync to prefer IPv4/IPv6
185  when creating sockets.  This only affects sockets that rsync has direct
186  control over, such as the outgoing socket when directly contacting an