e9fcd2febae73e7ece9065954d691254c72fd471
[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: 603cf476ef5a1155203037d2127341cdbb8646d7
14 diff --git a/flist.c b/flist.c
15 --- a/flist.c
16 +++ b/flist.c
17 @@ -77,6 +77,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 @@ -104,6 +105,8 @@ int file_old_total = 0; /* total of active items that will soon be gone */
26  int flist_eof = 0; /* all the file-lists are now known */
27  int xfer_flags_as_varint = 0;
28  
29 +char tr_substitutions[256];
30 +
31  #define NORMAL_NAME 0
32  #define SLASH_ENDING_NAME 1
33  #define DOTDIR_NAME 2
34 @@ -674,6 +677,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, atime;
58 @@ -744,9 +764,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, CFN_REFUSE_DOT_DOT_DIRS) < 0 || (!relative_paths && *thisname == '/'))) {
71                 rprintf(FERROR, "ABORTING due to unsafe pathname from sender: %s\n", thisname);
72 @@ -2531,6 +2555,15 @@ struct file_list *recv_file_list(int f, int dir_ndx)
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 @@ -201,6 +201,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 @@ -797,6 +798,7 @@ static struct poptOption long_options[] = {
100    {"temp-dir",        'T', POPT_ARG_STRING, &tmpdir, 0, 0, 0 },
101    {"iconv",            0,  POPT_ARG_STRING, &iconv_opt, 0, 0, 0 },
102    {"no-iconv",         0,  POPT_ARG_NONE,   0, OPT_NO_ICONV, 0, 0 },
103 +  {"tr",               0,  POPT_ARG_STRING, &tr_opt, 0, 0, 0 },
104    {"ipv4",            '4', POPT_ARG_VAL,    &default_af_hint, AF_INET, 0, 0 },
105    {"ipv6",            '6', POPT_ARG_VAL,    &default_af_hint, AF_INET6, 0, 0 },
106    {"8-bit-output",    '8', POPT_ARG_VAL,    &allow_8bit_chars, 1, 0, 0 },
107 @@ -2436,6 +2438,24 @@ int parse_arguments(int *argc_p, const char ***argv_p)
108                 }
109         }
110  
111 +       if (tr_opt) {
112 +               if (*tr_opt == '/' && tr_opt[1]) {
113 +                       snprintf(err_buf, sizeof err_buf,
114 +                               "Do not start the --tr arg with a slash\n");
115 +                       return 0;
116 +               }
117 +               if (*tr_opt && *tr_opt != '/') {
118 +                       need_unsorted_flist = 1;
119 +                       arg = strchr(tr_opt, '/');
120 +                       if (arg && strchr(arg+1, '/')) {
121 +                               snprintf(err_buf, sizeof err_buf,
122 +                                       "--tr cannot transliterate slashes\n");
123 +                               return 0;
124 +                       }
125 +               } else
126 +                       tr_opt = NULL;
127 +       }
128 +
129         am_starting_up = 0;
130  
131         return 1;
132 @@ -2848,6 +2868,12 @@ void server_options(char **args, int *argc_p)
133         if (relative_paths && !implied_dirs && (!am_sender || protocol_version >= 30))
134                 args[ac++] = "--no-implied-dirs";
135  
136 +       if (tr_opt) {
137 +               if (asprintf(&arg, "--tr=%s", tr_opt) < 0)
138 +                       goto oom;
139 +               args[ac++] = arg;
140 +       }
141 +
142         if (write_devices && am_sender)
143                 args[ac++] = "--write-devices";
144  
145 diff --git a/rsync.1.md b/rsync.1.md
146 --- a/rsync.1.md
147 +++ b/rsync.1.md
148 @@ -466,6 +466,7 @@ detailed description below for a complete description.
149  --read-batch=FILE        read a batched update from FILE
150  --protocol=NUM           force an older protocol version to be used
151  --iconv=CONVERT_SPEC     request charset conversion of filenames
152 +--tr=BAD/GOOD            transliterate filenames
153  --checksum-seed=NUM      set block/file checksum seed (advanced)
154  --ipv4, -4               prefer IPv4
155  --ipv6, -6               prefer IPv6
156 @@ -3331,6 +3332,25 @@ your home directory (remove the '=' for that).
157      free to specify just the local charset for a daemon transfer (e.g.
158      `--iconv=utf8`).
159  
160 +0.  `--tr=BAD/GOOD`
161 +
162 +    Transliterates filenames on the receiver, after the iconv conversion (if
163 +    any).  This can be used to remove characters illegal on the destination
164 +    filesystem.  If you use this option, consider saving a "find . -ls" listing
165 +    of the source in the destination to help you determine the original
166 +    filenames in case of need.
167 +
168 +    The argument consists of a string of characters to remove, optionally
169 +    followed by a slash and a string of corresponding characters with which to
170 +    replace them.  The second string may be shorter, in which case any leftover
171 +    characters in the first string are simply deleted.  For example,
172 +    `--tr=':\/!'` replaces colons with exclamation marks and deletes
173 +    backslashes.  Slashes cannot be transliterated because it would cause
174 +    havoc.
175 +
176 +    If the receiver is invoked over a remote shell, use `--protect-args` to
177 +    stop the shell from interpreting any nasty characters in the argument.
178 +
179  0.  `--ipv4`, `-4` or `--ipv6`, `-6`
180  
181      Tells rsync to prefer IPv4/IPv6 when creating sockets or running ssh.  This