clitar.c: update TAR_DEFAULT_BLOCK_SIZE comment
[samba.git] / source3 / client / clitar.c
1 /*
2    Unix SMB/CIFS implementation.
3    Tar backup command extension
4    Copyright (C) AurĂ©lien Aptel 2013
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "includes.h"
21 #include "system/filesys.h"
22 #include "client/client_proto.h"
23 #include "client/clitar_proto.h"
24 #include "libsmb/libsmb.h"
25 #include <archive.h>
26 #include <archive_entry.h>
27
28 #define LEN(x) (sizeof(x)/sizeof((x)[0]))
29
30 /**
31  * Maximum value for the blocksize field
32  */
33 #define TAR_MAX_BLOCK_SIZE 0xffff
34
35 /**
36  * Default tar block size in bytes. Hasn't changed since the first
37  * commit in 1996...
38  *
39  * A more adequate size will be used for better performance unless
40  * we're dealing with a tape device with a fixed read/write block
41  * size.
42  *
43  * The actual choice is made by libarchive.
44  */
45 #define TAR_DEFAULT_BLOCK_SIZE 20
46
47 enum tar_operation {
48     TAR_NO_OPERATION,
49     TAR_CREATE,    /* c flag */
50     TAR_EXTRACT,   /* x flag */
51 };
52
53 enum tar_selection {
54     TAR_NO_SELECTION,
55     TAR_INCLUDE,       /* I flag, default */
56     TAR_INCLUDE_LIST,  /* F flag */
57     TAR_EXCLUDE,       /* X flag */
58 };
59
60 enum {
61     ATTR_UNSET,
62     ATTR_SET,
63 };
64
65 struct tar {
66     /* in state that needs/can be processed? */
67     bool to_process;
68
69     /* flags */
70     struct tar_mode {
71         enum tar_operation operation; /* create, extract */
72         enum tar_selection selection; /* inc, inc from file, exclude */
73         int blocksize;    /* size in bytes of a block in the tar file */
74         bool hidden;      /* backup hidden file? */
75         bool system;      /* backup system file? */
76         bool incremental; /* backup _only_ archived file? */
77         bool reset;       /* unset archive bit? */
78         bool dry;         /* don't write tar file? */
79         bool regex;       /* XXX: never actually using regex... */
80         bool verbose;
81     } mode;
82
83     /* path to tar archive name */
84     char *tar_path;
85
86     /* file descriptor of tar file */
87     int tar_fd;
88
89     /* list of path to include or exclude */
90     char **path_list;
91
92     /* archive handle */
93     struct archive *archive;
94 };
95
96 struct tar tar_ctx = {
97     .mode.selection   = TAR_INCLUDE,
98     .mode.blocksize   = TAR_DEFAULT_BLOCK_SIZE,
99     .mode.hidden      = true,
100     .mode.system      = true,
101     .mode.incremental = false,
102     .mode.dry         = false,
103 };
104
105 static char *fix_unix_path (char *path, bool removeprefix)
106 {
107     char *from = path, *to = path;
108
109     if (!path || !*path)
110         return path;
111
112     /* remove prefix:
113      * ./path => path
114      *  /path => path
115      */
116     if (removeprefix) {
117         /* /path */
118         if (path[0] == '/' || path[0] == '\\') {
119             from += 1;
120         }
121
122         /* ./path */
123         if (path[1] && path[0] == '.' && (path[1] == '/' || path[1] == '\\')) {
124             from += 2;
125         }
126     }
127
128     /* replace \ with / */
129     while (*from) {
130         if (*from == '\\') {
131             *to = '/';
132         } else {
133             *to = *from;
134         }
135         from++; to++;
136     }
137     *to = 0;
138
139     return path;
140 }
141
142 #define XSET(v)      [v] = #v
143 #define XTABLE(v, t) DEBUG(2, ("DUMP:%-20.20s = %s\n", #v, t[v]))
144 #define XBOOL(v)     DEBUG(2, ("DUMP:%-20.20s = %d\n", #v, v ? 1 : 0))
145 #define XSTR(v)      DEBUG(2, ("DUMP:%-20.20s = %s\n", #v, v ? v : "NULL"))
146 #define XINT(v)      DEBUG(2, ("DUMP:%-20.20s = %d\n", #v, v))
147 static void tar_dump(struct tar *t)
148 {
149     int i;
150     const char* op[] = {
151         XSET(TAR_NO_OPERATION),
152         XSET(TAR_CREATE),
153         XSET(TAR_EXTRACT),
154     };
155
156     const char* sel[] = {
157         XSET(TAR_NO_SELECTION),
158         XSET(TAR_INCLUDE),
159         XSET(TAR_INCLUDE_LIST),
160         XSET(TAR_EXCLUDE),
161     };
162
163     XBOOL(t->to_process);
164     XTABLE(t->mode.operation, op);
165     XTABLE(t->mode.selection, sel);
166     XINT(t->mode.blocksize);
167     XBOOL(t->mode.hidden);
168     XBOOL(t->mode.system);
169     XBOOL(t->mode.incremental);
170     XBOOL(t->mode.reset);
171     XBOOL(t->mode.dry);
172     XBOOL(t->mode.verbose);
173     XSTR(t->tar_path);
174     XINT(t->tar_fd);
175
176     for(i = 0; t->path_list && t->path_list[i]; i++) {
177         DEBUG(2, ("DUMP: t->path_list[%2d] = %s\n", i, t->path_list[i]));
178     }
179
180     DEBUG(2, ("DUMP:t->path_list @ %p (%d elem)\n", t->path_list, i));
181 }
182 #undef XSET
183 #undef XTABLE
184 #undef XBOOL
185 #undef XSTR
186 #undef XINT
187
188 static int tar_set_blocksize(struct tar *t, int size)
189 {
190     if (size <= 0 || size > TAR_MAX_BLOCK_SIZE) {
191         return 0;
192     }
193
194     t->mode.blocksize = size;
195
196     return 1;
197 }
198
199 static bool tar_set_newer_than(struct tar *t, const char *filename)
200 {
201     extern time_t newer_than;
202     SMB_STRUCT_STAT stbuf;
203
204     if (sys_stat(filename, &stbuf, false) != 0) {
205         DEBUG(0, ("Error setting newer-than time\n"));
206         return 0;
207     }
208
209     newer_than = convert_timespec_to_time_t(stbuf.st_ex_mtime);
210     DEBUG(1, ("Getting files newer than %s\n", time_to_asc(newer_than)));
211     return 1;
212 }
213
214 static bool tar_read_inclusion_file (struct tar *t, const char* filename)
215 {
216     char *line;
217     char **list;
218     TALLOC_CTX *ctx = talloc_tos();
219     int fd = open(filename, O_RDONLY);
220
221     if (fd < 0) {
222         DEBUG(0, ("Can't open inclusion file '%s': %s\n", filename, strerror(errno)));
223         return 0;
224     }
225
226     list = str_list_make_empty(ctx);
227
228     while ((line = afdgets(fd, ctx, 0))) {
229         list = str_list_add((const char **)list, fix_unix_path(line, true));
230     }
231
232     close(fd);
233     t->path_list = list;
234     return 1;
235 }
236
237 bool tar_to_process (struct tar *t)
238 {
239     return t->to_process;
240 }
241
242 /**
243  * cmd_block - interactive command to change tar blocksize
244  *
245  * Read a size from the client command line and update the current
246  * blocksize.
247  */
248 int cmd_block(void)
249 {
250     /* XXX: from client.c */
251     const extern char *cmd_ptr;
252     char *buf;
253     TALLOC_CTX *ctx = talloc_tos();
254
255     if (!next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
256         DEBUG(0, ("blocksize <n>\n"));
257         return 1;
258     }
259
260     if(!tar_set_blocksize(&tar_ctx, atoi(buf))) {
261         DEBUG(0, ("invalid blocksize\n"));
262     }
263
264     DEBUG(2, ("blocksize is now %d\n", tar_ctx.mode.blocksize));
265
266     return 0;
267 }
268
269 /**
270  * cmd_tarmode - interactive command to change tar behaviour
271  *
272  * Read one or more modes from the client command line and update the
273  * current tar mode.
274  */
275 int cmd_tarmode(void)
276 {
277     const extern char *cmd_ptr;
278     char *buf;
279     int i;
280     TALLOC_CTX *ctx = talloc_tos();
281
282     struct {
283         const char *cmd;
284         bool *p;
285         bool value;
286     } table[] = {
287         {"full",      &tar_ctx.mode.incremental, false},
288         {"inc",       &tar_ctx.mode.incremental, true },
289         {"reset",     &tar_ctx.mode.reset,       true },
290         {"noreset",   &tar_ctx.mode.reset,       false},
291         {"system",    &tar_ctx.mode.system,      true },
292         {"nosystem",  &tar_ctx.mode.system,      false},
293         {"hidden",    &tar_ctx.mode.hidden,      true },
294         {"nohidden",  &tar_ctx.mode.hidden,      false},
295         {"verbose",   &tar_ctx.mode.verbose,     true },
296         {"noquiet",   &tar_ctx.mode.verbose,     true },
297         {"quiet",     &tar_ctx.mode.verbose,     false},
298         {"noverbose", &tar_ctx.mode.verbose,     false},
299     };
300
301     while (next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
302         for (i = 0; i < LEN(table); i++) {
303             if (strequal(table[i].cmd, buf)) {
304                 *table[i].p = table[i].value;
305                 break;
306             }
307         }
308
309         if (i == LEN(table))
310             DEBUG(0, ("tarmode: unrecognised option %s\n", buf));
311
312         TALLOC_FREE(buf);
313     }
314
315     DEBUG(0, ("tarmode is now %s, %s, %s, %s, %s\n",
316               tar_ctx.mode.incremental ? "incremental" : "full",
317               tar_ctx.mode.system      ? "system"      : "nosystem",
318               tar_ctx.mode.hidden      ? "hidden"      : "nohidden",
319               tar_ctx.mode.reset       ? "reset"       : "noreset",
320               tar_ctx.mode.verbose     ? "verbose"     : "quiet"));
321     return 0;
322 }
323
324 /**
325  * set_remote_attr - set DOS attributes of a remote file
326  * @filename: path to the file name
327  * @new_attr: attribute bit mask to use
328  * @mode: one of ATTR_SET or ATTR_UNSET
329  *
330  * Update the file attributes with the one provided.
331  */
332 static void set_remote_attr(char *filename, uint16 new_attr, int mode)
333 {
334     extern struct cli_state *cli;
335     uint16 old_attr;
336     NTSTATUS status;
337
338     if (!NT_STATUS_IS_OK(cli_getatr(cli, filename, &old_attr, NULL, NULL))) {
339         /* XXX: debug message */
340         return;
341     }
342
343     if (mode == ATTR_SET) {
344         new_attr |= old_attr;
345     } else {
346         new_attr = old_attr & ~new_attr;
347     }
348
349     status = cli_setatr(cli, filename, new_attr, 0);
350     if (!NT_STATUS_IS_OK(status)) {
351         DEBUG(1, ("setatr failed: %s\n", nt_errstr(status)));
352     }
353 }
354
355 /**
356  * cmd_setmode - interactive command to set DOS attributes
357  *
358  * Read a filename and mode from the client command line and update
359  * the file DOS attributes.
360  */
361 int cmd_setmode(void)
362 {
363     const extern char *cmd_ptr;
364     char *buf;
365     char *fname = NULL;
366     uint16 attr[2] = {0};
367     int mode = ATTR_SET;
368     TALLOC_CTX *ctx = talloc_tos();
369
370
371     if (!next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
372         DEBUG(0, ("setmode <filename> <[+|-]rsha>\n"));
373         return 1;
374     }
375
376     fname = talloc_asprintf(ctx,
377                             "%s%s",
378                             client_get_cur_dir(),
379                             buf);
380     if (!fname) {
381         return 1;
382     }
383
384     while (next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
385         const char *s = buf;
386
387         while(*s) {
388             switch (*s++) {
389             case '+':
390                 mode = ATTR_SET;
391                 break;
392             case '-':
393                 mode = ATTR_UNSET;
394                 break;
395             case 'r':
396                 attr[mode] |= FILE_ATTRIBUTE_READONLY;
397                 break;
398             case 'h':
399                 attr[mode] |= FILE_ATTRIBUTE_HIDDEN;
400                 break;
401             case 's':
402                 attr[mode] |= FILE_ATTRIBUTE_SYSTEM;
403                 break;
404             case 'a':
405                 attr[mode] |= FILE_ATTRIBUTE_ARCHIVE;
406                 break;
407             default:
408                 DEBUG(0, ("setmode <filename> <perm=[+|-]rsha>\n"));
409                 return 1;
410             }
411         }
412     }
413
414     if (attr[ATTR_SET] == 0 && attr[ATTR_UNSET] == 0) {
415         DEBUG(0, ("setmode <filename> <[+|-]rsha>\n"));
416         return 1;
417     }
418
419     DEBUG(2, ("\nperm set %d %d\n", attr[ATTR_SET], attr[ATTR_UNSET]));
420     set_remote_attr(fname, attr[ATTR_SET], ATTR_SET);
421     set_remote_attr(fname, attr[ATTR_UNSET], ATTR_UNSET);
422     return 0;
423 }
424
425
426 /**
427  * cmd_tar - interactive command to start a tar backup/restoration
428  *
429  * Check presence of argument, parse them and handle the request.
430  */
431 int cmd_tar(void)
432 {
433     return 0;
434 }
435
436 static int tar_extract(struct tar *t)
437 {
438     int err = 0;
439     int r;
440     struct archive_entry *entry;
441
442     t->archive = archive_read_new();
443     archive_read_support_format_all(t->archive);
444     archive_read_support_filter_all(t->archive);
445
446     if (strequal(t->tar_path, "-")) {
447         r = archive_read_open_fd(t->archive, STDIN_FILENO, t->mode.blocksize);
448     } else {
449         r = archive_read_open_filename(t->archive, t->tar_path,
450                                        t->mode.blocksize);
451     }
452
453     if (r != ARCHIVE_OK) {
454         DEBUG(0, ("Can't open %s : %s\n", t->tar_path,
455                   archive_error_string(t->archive)));
456         return 1;
457     }
458
459     for (;;) {
460         r = archive_read_next_header(t->archive, &entry);
461         if (r == ARCHIVE_EOF) {
462             break;
463         }
464         if (r == ARCHIVE_WARN) {
465             DEBUG(0, ("Warning: %s", archive_error_string(t->archive)));
466         }
467         if (r == ARCHIVE_FATAL) {
468             DEBUG(0, ("Fatal: %s", archive_error_string(t->archive)));
469             err = 1;
470             goto out;
471         }
472
473         DEBUG(0, ("Processing %s...\n", archive_entry_pathname(entry)));
474     }
475
476  out:
477     r = archive_read_free(t->archive);
478     if (r != ARCHIVE_OK) {
479         DEBUG(0, ("Can't close %s : %s\n", t->tar_path,
480                   archive_error_string(t->archive)));
481         err = 1;
482     }
483     return err;
484 }
485
486 int tar_process(struct tar *t)
487 {
488     int rc = 0;
489
490     switch(t->mode.operation) {
491     case TAR_EXTRACT:
492         rc = tar_extract(t);
493         break;
494     case TAR_CREATE:
495         /* tar_create(t); */
496         break;
497     default:
498         DEBUG(0, ("Invalid tar state\n"));
499         rc = 1;
500     }
501     return rc;
502 }
503
504 /**
505  * tar_parse_args - parse and set tar command line arguments
506  * @flag: string pointing to tar options
507  * @val: number of tar arguments
508  * @valsize: table of arguments after the flags (number of element in val)
509  *
510  * tar arguments work in a weird way. For each flag f that takes a
511  * value v, the user is supposed to type:
512  *
513  * on the CLI:
514  *   -Tf1f2f3 v1 v2 v3 TARFILE PATHS...
515  *
516  * in the interactive session:
517  *   tar f1f2f3 v1 v2 v3 TARFILE PATHS...
518  *
519  * opt has only flags (eg. "f1f2f3") and val has the arguments
520  * (values) following them (eg. ["v1", "v2", "v3", "TARFILE", "PATH1",
521  * "PATH2"]).
522  *
523  * There are only 2 flags that take an arg: b and N. The other flags
524  * just change the semantic of PATH or TARFILE.
525  *
526  * PATH can be a list of included/excluded paths, the path to a file
527  * containing a list of included/excluded paths to use (F flag). If no
528  * PATH is provided, the whole share is used (/).
529  */
530 int tar_parse_args(struct tar* t, const char *flag, const char **val, int valsize)
531 {
532     TALLOC_CTX *ctx = talloc_tos();
533
534     /* index of next value to use */
535     int ival = 0;
536
537     /*
538      * Reset back some options - could be from interactive version
539      * all other modes are left as they are
540      */
541     t->mode.operation = TAR_NO_OPERATION;
542     t->mode.selection = TAR_NO_SELECTION;
543     t->mode.dry = false;
544     t->to_process = false;
545
546     while (*flag) {
547         switch(*flag++) {
548         /* operation */
549         case 'c':
550             if (t->mode.operation != TAR_NO_OPERATION) {
551                 printf("Tar must be followed by only one of c or x.\n");
552                 return 0;
553             }
554             t->mode.operation = TAR_CREATE;
555             break;
556         case 'x':
557             if (t->mode.operation != TAR_NO_OPERATION) {
558                 printf("Tar must be followed by only one of c or x.\n");
559                 return 0;
560             }
561             t->mode.operation = TAR_EXTRACT;
562             break;
563
564         /* selection  */
565         case 'I':
566             if (t->mode.selection != TAR_NO_SELECTION) {
567                 DEBUG(0,("Only one of I,X,F must be specified\n"));
568                 return 0;
569             }
570             t->mode.selection = TAR_INCLUDE;
571             break;
572         case 'X':
573             if (t->mode.selection != TAR_NO_SELECTION) {
574                 DEBUG(0,("Only one of I,X,F must be specified\n"));
575                 return 0;
576             }
577             t->mode.selection = TAR_EXCLUDE;
578             break;
579         case 'F':
580             if (t->mode.selection != TAR_NO_SELECTION) {
581                 DEBUG(0,("Only one of I,X,F must be specified\n"));
582                 return 0;
583             }
584             t->mode.selection = TAR_INCLUDE_LIST;
585             break;
586
587         /* blocksize */
588         case 'b':
589             if (ival >= valsize) {
590                 DEBUG(0, ("Option b must be followed by a blocksize\n"));
591                 return 0;
592             }
593
594             if (!tar_set_blocksize(t, atoi(val[ival]))) {
595                 DEBUG(0, ("Option b must be followed by a valid blocksize\n"));
596                 return 0;
597             }
598
599             ival++;
600             break;
601
602          /* incremental mode */
603         case 'g':
604             t->mode.incremental = true;
605             break;
606
607         /* newer than */
608         case 'N':
609             if (ival >= valsize) {
610                 DEBUG(0, ("Option N must be followed by valid file name\n"));
611                 return 0;
612             }
613
614             if (!tar_set_newer_than(t, val[ival])) {
615                 DEBUG(0,("Error setting newer-than time\n"));
616                 return 0;
617             }
618
619             ival++;
620             break;
621
622         /* reset mode */
623         case 'a':
624             t->mode.reset = true;
625             break;
626
627         /* verbose */
628         case 'q':
629             t->mode.verbose = true;
630             break;
631
632         /* regex match  */
633         case 'r':
634             t->mode.regex = true;
635             break;
636
637         /* dry run mode */
638         case 'n':
639             if (t->mode.operation != TAR_CREATE) {
640                 DEBUG(0, ("n is only meaningful when creating a tar-file\n"));
641                 return 0;
642             }
643
644             t->mode.dry = true;
645             DEBUG(0, ("dry_run set\n"));
646             break;
647
648         default:
649             DEBUG(0,("Unknown tar option\n"));
650             return 0;
651         }
652     }
653
654     /* no selection given? default selection is include */
655     if (t->mode.selection == TAR_NO_SELECTION) {
656         t->mode.selection = TAR_INCLUDE;
657     }
658
659     if (valsize - ival < 1) {
660         DEBUG(0, ("No tar file given.\n"));
661         return 0;
662     }
663
664     /* handle TARFILE */
665     t->tar_path = talloc_strdup(ctx, val[ival]);
666     ival++;
667
668     /* handle PATHs... */
669     tar_ctx.path_list = str_list_make_empty(ctx);
670
671     /* flag F -> read file list */
672     if (t->mode.selection == TAR_INCLUDE_LIST) {
673         if (valsize - ival != 1) {
674             DEBUG(0,("Option F must be followed by exactly one filename.\n"));
675             return 0;
676         }
677
678         if (!tar_read_inclusion_file(t, val[ival])) {
679             return 0;
680         }
681         ival++;
682     }
683
684     /* otherwise store all the PATHs on the command line */
685     else {
686         int i;
687         for (i = ival; i < valsize; i++) {
688             t->path_list = str_list_add((const char**)t->path_list, val[i]);
689             fix_unix_path(t->path_list[i-ival], true);
690         }
691     }
692
693     t->to_process = true;
694     tar_dump(t);
695     return 1;
696 }