New file
[debian/tar] / src / transform.c
1 /* This file is part of GNU tar. 
2    Copyright (C) 2006 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify it
5    under the terms of the GNU General Public License as published by the
6    Free Software Foundation; either version 2, or (at your option) any later
7    version.
8
9    This program is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
12    Public License for more details.
13
14    You should have received a copy of the GNU General Public License along
15    with this program; if not, write to the Free Software Foundation, Inc.,
16    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 #include <system.h>
19 #include <regex.h>
20 #include "common.h"
21
22 enum transform_type
23   {
24     transform_none,
25     transform_first,
26     transform_global
27   }
28 transform_type = transform_none;
29 static regex_t regex;
30 static struct obstack stk;
31
32 enum replace_segm_type
33   {
34     segm_literal,   /* Literal segment */
35     segm_backref,   /* Back-reference segment */
36   };
37
38 struct replace_segm
39 {
40   struct replace_segm *next;
41   enum replace_segm_type type;
42   union
43   {
44     struct
45     {
46       char *ptr;
47       size_t size;
48     } literal;
49     size_t ref;
50   } v;
51 };
52
53 static struct replace_segm *repl_head, *repl_tail;
54 static segm_count;
55
56 static struct replace_segm *
57 add_segment (void)
58 {
59   struct replace_segm *segm = xmalloc (sizeof *segm);
60   segm->next = NULL;
61   if (repl_tail)
62     repl_tail->next = segm;
63   else
64     repl_head = segm;
65   repl_tail = segm;
66   segm_count++;
67   return segm;
68 }
69
70 static void
71 add_literal_segment (char *str, char *end)
72 {
73   size_t len = end - str;
74   if (len)
75     {
76       struct replace_segm *segm = add_segment ();
77       segm->type = segm_literal;
78       segm->v.literal.ptr = xmalloc (len + 1);
79       memcpy (segm->v.literal.ptr, str, len);
80       segm->v.literal.ptr[len] = 0;
81       segm->v.literal.size = len;
82     }
83 }
84
85 static void
86 add_char_segment (int chr)
87 {
88   struct replace_segm *segm = add_segment ();
89   segm->type = segm_literal;
90   segm->v.literal.ptr = xmalloc (2);
91   segm->v.literal.ptr[0] = chr;
92   segm->v.literal.ptr[1] = 0;
93   segm->v.literal.size = 2;
94 }
95
96 static void
97 add_backref_segment (size_t ref)
98 {
99   struct replace_segm *segm = add_segment ();
100   segm->type = segm_backref;
101   segm->v.ref = ref;
102 }
103
104 void
105 set_transform_expr (const char *expr)
106 {
107   int delim;
108   int i, j, rc;
109   char *str, *beg, *cur;
110   const char *p;
111   int cflags = 0;
112
113   if (transform_type == transform_none)
114     obstack_init (&stk);
115   else
116     {
117       /* Redefinition of the transform expression */
118       regfree (&regex);
119     }
120
121   if (expr[0] != 's')
122     USAGE_ERROR ((0, 0, _("Invalid transform expression")));
123
124   delim = expr[1];
125
126   /* Scan regular expression */
127   for (i = 2; expr[i] && expr[i] != delim; i++)
128     if (expr[i] == '\\' && expr[i+1])
129       i++;
130
131   if (expr[i] != delim)
132     USAGE_ERROR ((0, 0, _("Invalid transform expression")));
133
134   /* Scan replacement expression */
135   for (j = i + 1; expr[j] && expr[j] != delim; j++)
136     if (expr[j] == '\\' && expr[j+1])
137       j++;
138
139   if (expr[j] != delim)
140     USAGE_ERROR ((0, 0, _("Invalid transform expression")));
141
142   /* Check flags */
143   transform_type = transform_first;
144   for (p = expr + j + 1; *p; p++)
145     switch (*p)
146       {
147       case 'g':
148         transform_type = transform_global;
149         break;
150
151       case 'i':
152         cflags |= REG_ICASE;
153         break;
154
155       case 'x':
156         cflags |= REG_EXTENDED;
157         break;
158         
159       default:
160         USAGE_ERROR ((0, 0, _("Unknown flag in transform expression")));
161       }
162
163   /* Extract and compile regex */
164   str = xmalloc (i - 1);
165   memcpy (str, expr + 2, i - 2);
166   str[i - 2] = 0;
167
168   rc = regcomp (&regex, str, cflags);
169   
170   if (rc)
171     {
172       char errbuf[512];
173       regerror (rc, &regex, errbuf, sizeof (errbuf));
174       USAGE_ERROR ((0, 0, _("Invalid transform expression: %s"), errbuf));
175     }
176
177   if (str[0] == '^' || str[strlen (str) - 1] == '$')
178     transform_type = transform_first;
179   
180   free (str);
181
182   /* Extract and compile replacement expr */
183   i++;
184   str = xmalloc (j - i + 1);
185   memcpy (str, expr + i, j - i);
186   str[j - i] = 0;
187
188   for (cur = beg = str; *cur;)
189     {
190       if (*cur == '\\')
191         {
192           size_t n;
193           
194           add_literal_segment (beg, cur);
195           switch (*++cur)
196             {
197             case '0': case '1': case '2': case '3': case '4':
198             case '5': case '6': case '7': case '8': case '9':
199               n = strtoul (cur, &cur, 10);
200               if (n > regex.re_nsub)
201                 USAGE_ERROR ((0, 0, _("Invalid transform replacement: back reference out of range")));
202               add_backref_segment (n);
203               break;
204
205             case '\\':
206               add_char_segment ('\\');
207               cur++;
208               break;
209
210             case 'a':
211               add_char_segment ('\a');
212               cur++;
213               break;
214               
215             case 'b':
216               add_char_segment ('\b');
217               cur++;
218               break;
219               
220             case 'f':
221               add_char_segment ('\f');
222               cur++;
223               break;
224               
225             case 'n':
226               add_char_segment ('\n');
227               cur++;
228               break;
229               
230             case 'r':
231               add_char_segment ('\r');
232               cur++;
233               break;
234               
235             case 't':
236               add_char_segment ('\t');
237               cur++;
238               break;
239               
240             case 'v':
241               add_char_segment ('\v');
242               cur++;
243               break;
244
245             case '&':
246               add_char_segment ('&');
247               cur++;
248               break;
249
250             default:
251               /* Try to be nice */
252               {
253                 char buf[2];
254                 buf[0] = '\\';
255                 buf[1] = *cur;
256                 add_literal_segment (buf, buf + 2);
257               }
258               cur++;
259               break;
260             }
261           beg = cur;
262         }
263       else if (*cur == '&')
264         {
265           add_literal_segment (beg, cur);
266           add_backref_segment (0);
267           beg = ++cur;
268         }
269       else
270         cur++;
271     }
272   add_literal_segment (beg, cur);
273   
274 }
275
276 bool
277 _transform_name_to_obstack (char *input)
278 {
279   regmatch_t *rmp;
280   char *p;
281   int rc;
282   
283   if (transform_type == transform_none)
284     return false;
285
286   rmp = xmalloc ((regex.re_nsub + 1) * sizeof (*rmp));
287
288   while (*input)
289     {
290       size_t disp;
291       
292       rc = regexec (&regex, input, regex.re_nsub + 1, rmp, 0);
293       
294       if (rc == 0)
295         {
296           struct replace_segm *segm;
297
298           disp = rmp[0].rm_eo;
299
300           if (rmp[0].rm_so)
301             obstack_grow (&stk, input, rmp[0].rm_so);
302           
303           for (segm = repl_head; segm; segm = segm->next)
304             {
305               switch (segm->type)
306                 {
307                 case segm_literal:    /* Literal segment */
308                   obstack_grow (&stk, segm->v.literal.ptr,
309                                 segm->v.literal.size);
310                   break;
311               
312                 case segm_backref:    /* Back-reference segment */
313                   if (rmp[segm->v.ref].rm_so != -1
314                       && rmp[segm->v.ref].rm_eo != -1)
315                     obstack_grow (&stk,
316                                   input + rmp[segm->v.ref].rm_so,
317                               rmp[segm->v.ref].rm_eo - rmp[segm->v.ref].rm_so);
318                   break;
319                 }
320             }
321         }
322       else
323         {
324           disp = strlen (input);
325           obstack_grow (&stk, input, disp);
326         }
327
328       input += disp;
329
330       if (transform_type == transform_first)
331         {
332           obstack_grow (&stk, input, strlen (input));
333           break;
334         }
335     }
336
337   obstack_1grow (&stk, 0);
338   free (rmp);
339   return true;
340 }
341   
342 bool
343 transform_name_fp (char **pinput, char *(*fun)(char *))
344 {
345     char *str, *p;
346     bool ret = _transform_name_to_obstack (*pinput);
347     if (ret)
348       {
349         str = obstack_finish (&stk);
350         assign_string (pinput, fun ? fun (str) : str);
351         obstack_free (&stk, str);
352       }
353     return ret;
354 }
355
356 bool
357 transform_name (char **pinput)
358 {
359   return transform_name_fp (pinput, NULL);
360 }
361
362 #if 0
363 void
364 read_and_transform_loop ()
365 {
366   char buf[512];
367   while (fgets (buf, sizeof buf, stdin))
368     {
369       char *p = buf + strlen (buf);
370       if (p[-1] == '\n')
371         p[-1] = 0;
372       if (transform_name (buf, &p))
373         printf ("=> %s\n", p);
374       else
375         printf ("=\n");
376     }
377 }
378 #endif