1 /*
2 * Copyright (C) 2008 Giuseppe Torelli - <colossus73@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA.
18 */
19
20 #include <string.h>
21 #include <time.h>
22 #include <unistd.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include "gzip_et_al.h"
26 #include "interface.h"
27 #include "main.h"
28 #include "parser.h"
29 #include "string_utils.h"
30 #include "support.h"
31 #include "tar.h"
32 #include "window.h"
33
34 #define compress (archive->type == XARCHIVETYPE_COMPRESS)
35 #define lrzip (archive->type == XARCHIVETYPE_LRZIP)
36 #define lz4 (archive->type == XARCHIVETYPE_LZ4)
37 #define xz (archive->type == XARCHIVETYPE_XZ)
38 #define zstd (archive->type == XARCHIVETYPE_ZSTD)
39
40 static gpointer item[7];
41 static gchar *filename;
42 static gboolean data_line, last_line;
43 static gboolean lrzip_can_password;
44 static gboolean zstd_can_list, zstd_can_test;
45
xa_gzip_et_al_check_lrzip(const gchar * path)46 void xa_gzip_et_al_check_lrzip (const gchar *path)
47 {
48 gchar *command, *output;
49
50 command = g_strconcat(path, " -h", NULL);
51 g_spawn_command_line_sync(command, NULL, &output, NULL, NULL);
52 g_free(command);
53
54 lrzip_can_password = (strstr(output, "-e, --encrypt[=pass") != NULL);
55 g_free(output);
56 }
57
xa_gzip_et_al_check_zstd(const gchar * compressor,const gchar * decompressor,gboolean * is_compressor)58 gchar *xa_gzip_et_al_check_zstd (const gchar *compressor, const gchar *decompressor, gboolean *is_compressor)
59 {
60 gchar *path, *command, *output;
61 gboolean found_compressor = FALSE;
62
63 path = g_find_program_in_path(compressor);
64
65 if (path)
66 found_compressor = TRUE;
67 else
68 path = g_find_program_in_path(decompressor);
69
70 if (!path)
71 return NULL;
72
73 command = g_strconcat(path, " -h", NULL);
74 g_spawn_command_line_sync(command, &output, NULL, NULL, NULL);
75 g_free(command);
76
77 /* check whether decompression is available */
78 if (strstr(output, "\n -d "))
79 {
80 if (found_compressor)
81 *is_compressor = (strstr(output, "\n -# ") != NULL);
82
83 zstd_can_list = (strstr(output, "\n -l ") || strstr(output, "\n--list "));
84 zstd_can_test = (strstr(output, "\n -t ") || strstr(output, "\n--test "));
85 }
86 else // useless
87 {
88 g_free(path);
89 path = NULL;
90 }
91
92 g_free(output);
93
94 return path;
95 }
96
xa_gzip_et_al_password_str(const gchar * password,XArchiveType type)97 static gchar *xa_gzip_et_al_password_str (const gchar *password, XArchiveType type)
98 {
99 if (password)
100 if (type == XARCHIVETYPE_LRZIP)
101 return g_strconcat(" --encrypt=", password, NULL);
102
103 return g_strdup("");
104 }
105
xa_gzip_et_al_get_command(const gchar * program,gchar * workfile,gchar * archive,const gchar * password,XArchiveType type)106 gchar *xa_gzip_et_al_get_command (const gchar *program, gchar *workfile, gchar *archive, const gchar *password, XArchiveType type)
107 {
108 gchar *password_str, *command;
109
110 password_str = xa_gzip_et_al_password_str(password, type);
111 workfile = xa_escape_bad_chars(workfile, "\"");
112 archive = xa_quote_shell_command(archive, TRUE);
113 command = g_strconcat("sh -c \"", program, type == XARCHIVETYPE_LRZIP ? " " : " -c ", workfile, password_str, type == XARCHIVETYPE_LRZIP ? " -fo " : " > ", archive, "\"", NULL);
114 g_free(archive);
115 g_free(workfile);
116 g_free(password_str);
117
118 return command;
119 }
120
xa_gzip_et_al_can(XArchive * archive,gboolean can)121 static void xa_gzip_et_al_can (XArchive *archive, gboolean can)
122 {
123 archive->can_test = (can && !compress && (!zstd || zstd_can_test));
124 archive->can_extract = can;
125 archive->can_password = (can && lrzip && lrzip_can_password);
126 archive->can_overwrite = can;
127 archive->can_move = can;
128
129 /* only if archive is new and empty */
130 archive->can_add = (can && archiver[archive->type].is_compressor);
131 }
132
xa_gzip_et_al_ask(XArchive * archive)133 void xa_gzip_et_al_ask (XArchive *archive)
134 {
135 xa_gzip_et_al_can(archive, TRUE);
136 }
137
xa_gzip_et_al_parse_output(gchar * line,XArchive * archive)138 static void xa_gzip_et_al_parse_output (gchar *line, XArchive *archive)
139 {
140 XEntry *entry;
141 guint idx0 = 0, idx1 = 1;
142 const gchar *streams = NULL, *blocks = NULL;
143
144 USE_PARSER;
145
146 if (archive->type == XARCHIVETYPE_GZIP)
147 {
148 /* heading? */
149 if (line[9] == 'c' || line[0] == 'c') // genuine gzip or pigz
150 return;
151 }
152 else if (archive->type == XARCHIVETYPE_LZIP)
153 {
154 /* heading? */
155 if (line[3] == 'u')
156 return;
157 else
158 {
159 idx0 = 1;
160 idx1 = 0;
161 }
162 }
163 else if (archive->type == XARCHIVETYPE_LZOP)
164 {
165 /* heading? */
166 if (line[12] == 'c')
167 return;
168 else
169 /* method */
170 NEXT_ITEM(item[5]);
171 }
172 else if (archive->type == XARCHIVETYPE_XZ)
173 {
174 /* heading? */
175 if (*line == 't')
176 return;
177 else if (*line == 'n')
178 {
179 /* "name" */
180 SKIP_ITEM;
181 LAST_ITEM(filename);
182
183 if (g_str_has_suffix(filename, ".xz"))
184 *(line - 4) = 0;
185
186 filename = g_path_get_basename(filename);
187
188 return;
189 }
190 else
191 {
192 /* "file" */
193 SKIP_ITEM;
194
195 /* number of streams */
196 NEXT_ITEM(streams);
197
198 /* number of blocks */
199 NEXT_ITEM(blocks);
200 }
201 }
202 else
203 return;
204
205 /* compressed (uncompressed for lzip) */
206 NEXT_ITEM(item[idx1]);
207
208 /* uncompressed (compressed for lzip) */
209 NEXT_ITEM(item[idx0]);
210
211 /* ratio */
212 NEXT_ITEM(item[2]);
213
214 if (archive->type == XARCHIVETYPE_XZ)
215 {
216 const gchar *padding;
217
218 /* check type */
219 NEXT_ITEM(item[5]);
220
221 /* stream padding */
222 NEXT_ITEM(padding);
223
224 item[6] = g_strconcat(streams, "/", blocks, "/", padding, NULL);
225 }
226 else
227 {
228 item[6] = NULL;
229
230 /* uncompressed_name */
231 LAST_ITEM(filename);
232
233 if ((archive->type == XARCHIVETYPE_LZIP) && g_str_has_suffix(filename, ".lz"))
234 *(line - 4) = 0;
235
236 filename = g_path_get_basename(filename);
237 }
238
239 entry = xa_set_archive_entries_for_each_row(archive, filename, item);
240
241 if (entry)
242 {
243 archive->files = 1;
244 archive->files_size = g_ascii_strtoull(item[0], NULL, 0);
245 }
246
247 g_free(item[3]);
248 g_free(item[4]);
249 g_free(item[6]);
250 g_free(filename);
251 }
252
xa_gzip_et_al_parse_lrzip(gchar * line,XArchive * archive)253 static void xa_gzip_et_al_parse_lrzip (gchar *line, XArchive *archive)
254 {
255 XEntry *entry;
256
257 USE_PARSER;
258
259 if (last_line)
260 return;
261
262 if (!data_line)
263 {
264 LAST_ITEM(filename);
265
266 filename[strlen(filename) - 1] = 0; // remove colon
267
268 if (g_str_has_suffix(filename, ".lrz"))
269 *(line - 6) = 0;
270
271 filename = g_path_get_basename(filename);
272 data_line = TRUE;
273
274 return;
275 }
276
277 IF_ITEM_LINE("lrzip version:")
278 {
279 NEXT_ITEM(item[6]);
280 item[6] = g_strdup(item[6]);
281 }
282 else IF_ITEM_LINE("Compression:")
283 DUPE_ITEM(item[5]);
284 else IF_ITEM_LINE("Decompressed file size:")
285 DUPE_ITEM(item[0]);
286 else IF_ITEM_LINE("Compressed file size:")
287 DUPE_ITEM(item[1]);
288 else IF_ITEM_LINE("Compression ratio:")
289 {
290 NEXT_ITEM(item[2]);
291 item[2] = g_strconcat(item[2], ":1", NULL);
292 last_line = TRUE;
293 }
294
295 if (!last_line)
296 return;
297
298 entry = xa_set_archive_entries_for_each_row(archive, filename, item);
299
300 if (entry)
301 {
302 entry->is_encrypted = archive->has_password;
303
304 archive->files = 1;
305 archive->files_size = g_ascii_strtoull(item[0], NULL, 0);
306 }
307
308 g_free(item[0]);
309 g_free(item[1]);
310 g_free(item[2]);
311 g_free(item[3]);
312 g_free(item[4]);
313 g_free(item[5]);
314 g_free(item[6]);
315 g_free(filename);
316 }
317
xa_gzip_et_al_parse_zstd(gchar * line,XArchive * archive)318 static void xa_gzip_et_al_parse_zstd (gchar *line, XArchive *archive)
319 {
320 static gchar *zstandard;
321 XEntry *entry;
322 char *pos;
323
324 USE_PARSER;
325
326 if (last_line)
327 return;
328
329 if (!data_line)
330 {
331 data_line = (strncmp(line, "Number of files listed:", 23) == 0);
332 return;
333 }
334
335 if ((pos = strstr(line, " (1/1):\n")))
336 {
337 *pos = 0;
338 LAST_ITEM(filename);
339
340 if (g_str_has_suffix(filename, ".zst"))
341 *(line - 4) = 0;
342
343 filename = g_path_get_basename(filename);
344 }
345 else IF_ITEM_LINE("# Zstandard Frames:")
346 DUPE_ITEM(zstandard);
347 else IF_ITEM_LINE("# Skippable Frames:")
348 {
349 NEXT_ITEM(item[6]);
350 item[6] = g_strconcat(zstandard, "/", item[6], NULL);
351 g_free(zstandard);
352 }
353 else IF_ITEM_LINE("Compressed Size:")
354 {
355 pos = strchr(line, '(');
356
357 item[1] = (pos ? g_strdup(pos + 1) : pos);
358 }
359 else IF_ITEM_LINE("Decompressed Size:")
360 {
361 pos = strchr(line, '(');
362
363 item[0] = (pos ? g_strdup(pos + 1) : pos);
364 }
365 else IF_ITEM_LINE("Ratio:")
366 {
367 NEXT_ITEM(item[2]);
368 item[2] = g_strconcat(item[2], ":1", NULL);
369 }
370 else IF_ITEM_LINE("Check:")
371 {
372 DUPE_ITEM(item[5]);
373 last_line = TRUE;
374 }
375
376 if (!last_line)
377 return;
378
379 entry = xa_set_archive_entries_for_each_row(archive, filename, item);
380
381 if (entry)
382 {
383 archive->files = 1;
384 archive->files_size = g_ascii_strtoull(item[0], NULL, 0);
385 }
386
387 g_free(item[0]);
388 g_free(item[1]);
389 g_free(item[2]);
390 g_free(item[3]);
391 g_free(item[4]);
392 g_free(item[5]);
393 g_free(item[6]);
394 g_free(filename);
395 }
396
xa_gzip_et_al_globally_stored_entry(gchar * line,XArchive * archive)397 static void xa_gzip_et_al_globally_stored_entry (gchar *line, XArchive *archive)
398 {
399 XEntry *entry;
400 gchar *filename;
401 char *dot;
402
403 filename = g_path_get_basename(archive->path[0]);
404 dot = strrchr(filename, '.');
405
406 if (dot)
407 *dot = 0;
408
409 entry = xa_set_archive_entries_for_each_row(archive, filename, item);
410
411 if (entry)
412 {
413 entry->is_encrypted = archive->has_password;
414
415 archive->files = 1;
416 }
417
418 g_free(item[0]);
419 g_free(item[1]);
420 g_free(item[2]);
421 g_free(item[3]);
422 g_free(item[4]);
423 g_free(filename);
424 }
425
xa_gzip_et_al_list(XArchive * archive)426 void xa_gzip_et_al_list (XArchive *archive)
427 {
428 GType types[] = {GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_UINT64, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_POINTER};
429 const gchar *titles[] = {_("Original Size"), _("Compressed"), _("Saving"), _("Date"), _("Time"), NULL, NULL};
430 const gchar *decompfile = "xa-tmp.decompressed";
431 gchar *password_str, *archive_path, *command, *workfile, buffer[12];
432 FILE *file;
433 struct stat st;
434 guint i;
435
436 if (archive->type == XARCHIVETYPE_LRZIP)
437 {
438 file = fopen(archive->path[0], "r");
439
440 if (file)
441 {
442 char encrypted = 0;
443
444 if (fseek(file, 22, SEEK_SET) == 0)
445 if (fread(&encrypted, sizeof(encrypted), 1, file) == 1)
446 if (encrypted == 1)
447 archive->has_password = TRUE;
448
449 fclose(file);
450 }
451
452 if (archive->has_password && !lrzip_can_password)
453 {
454 command = g_strconcat("sh -c \"echo ", _("The archiver lacks necessary support for password protected decryption!"), " >&2; exit 1\"", NULL);
455 xa_run_command(archive, command);
456 g_free(command);
457 return;
458 }
459 }
460
461 if (archive->has_password)
462 if (!xa_check_password(archive))
463 return;
464
465 if (!xa_create_working_directory(archive))
466 return;
467
468 password_str = xa_gzip_et_al_password_str(archive->password, archive->type);
469 archive_path = xa_quote_shell_command(archive->path[0], TRUE);
470
471 archive->child_dir = g_strdup(archive->working_dir);
472 command = g_strconcat("sh -c \"", archiver[archive->type].program[0], lrzip ? " " : " -c ", "-d", password_str, " ", archive_path, lrzip ? " -fo " : " > ", decompfile, "\"", NULL);
473 xa_run_command(archive, command);
474 g_free(command);
475
476 g_free(archive->child_dir);
477 archive->child_dir = NULL;
478
479 g_free(archive_path);
480 g_free(password_str);
481
482 workfile = g_strconcat(archive->working_dir, "/", decompfile, NULL);
483
484 /* check if uncompressed file is tar archive */
485
486 file = fopen(workfile, "r");
487
488 if (file)
489 {
490 gboolean is_tar;
491
492 is_tar = isTar(file);
493 fclose(file);
494
495 if (is_tar && (archiver[XARCHIVETYPE_TAR].ask == xa_tar_ask))
496 {
497 if (!xa_get_compressed_tar_type(&archive->type))
498 return;
499
500 archive->path[2] = g_shell_quote(workfile);
501 g_free(workfile);
502
503 xa_gzip_et_al_can(archive, FALSE);
504
505 archive->archiver = &archiver[XARCHIVETYPE_TAR];
506
507 (*archive->archiver->ask)(archive);
508 (*archive->archiver->list)(archive);
509
510 return;
511 }
512 }
513
514 /* continue listing gzip et al. archive type */
515
516 archive->can_add = FALSE;
517
518 stat(archive->path[0], &st);
519
520 /* date */
521 strftime(buffer, sizeof(buffer), "%Y-%m-%d", localtime(&st.st_mtime));
522 item[3] = g_strdup(buffer);
523
524 /* time */
525 strftime(buffer, sizeof(buffer), "%H:%M:%S", localtime(&st.st_mtime));
526 item[4] = g_strdup(buffer);
527
528 archive->columns = 8;
529 archive->parse_output = NULL;
530
531 switch (archive->type)
532 {
533 case XARCHIVETYPE_GZIP:
534 case XARCHIVETYPE_LRZIP:
535 case XARCHIVETYPE_LZIP:
536 case XARCHIVETYPE_LZOP:
537 case XARCHIVETYPE_XZ:
538 case XARCHIVETYPE_ZSTD:
539
540 if (archive->type == XARCHIVETYPE_LRZIP)
541 {
542 if (archive->has_password)
543 /* no further information will be available then */
544 break;
545
546 data_line = FALSE;
547 last_line = FALSE;
548
549 types[7] = G_TYPE_STRING;
550 types[8] = G_TYPE_STRING;
551
552 titles[2] = _("Compression ratio");
553 titles[5] = _("Method");
554 titles[6] = _("Version");
555
556 archive->columns = 10;
557 }
558 else if (archive->type == XARCHIVETYPE_LZOP)
559 {
560 types[7] = G_TYPE_STRING;
561
562 titles[2] = _("Occupancy");
563 titles[5] = _("Method");
564
565 archive->columns = 9;
566 }
567 else if (archive->type == XARCHIVETYPE_XZ)
568 {
569 types[7] = G_TYPE_STRING;
570 types[8] = G_TYPE_STRING;
571
572 titles[2] = _("Ratio");
573 titles[5] = _("Check Type");
574 titles[6] = _("Streams/Blocks/Padding");
575
576 archive->columns = 10;
577 }
578 else if (archive->type == XARCHIVETYPE_ZSTD)
579 {
580 if (!zstd_can_list)
581 break;
582
583 /* items potentially not listed */
584 item[0] = NULL;
585 item[2] = NULL;
586
587 data_line = FALSE;
588 last_line = FALSE;
589
590 types[7] = G_TYPE_STRING;
591 types[8] = G_TYPE_STRING;
592
593 titles[2] = _("Compression ratio");
594 titles[5] = _("Check Type");
595 titles[6] = _("Data Frames/Skippable Frames");
596
597 archive->columns = 10;
598 }
599
600 command = g_strconcat(archiver[archive->type].program[0], lrzip ? " -i" : " -l", xz ? " --robot " : (zstd ? "v " : " "), archive->path[1], NULL);
601
602 if (archive->type == XARCHIVETYPE_LRZIP)
603 archive->parse_output = xa_gzip_et_al_parse_lrzip;
604 else if (archive->type == XARCHIVETYPE_ZSTD)
605 archive->parse_output = xa_gzip_et_al_parse_zstd;
606 else
607 archive->parse_output = xa_gzip_et_al_parse_output;
608
609 break;
610
611 case XARCHIVETYPE_BZIP2:
612 case XARCHIVETYPE_COMPRESS:
613 case XARCHIVETYPE_LZ4:
614 case XARCHIVETYPE_LZMA:
615 break;
616
617 default:
618 return;
619 }
620
621 if (!archive->parse_output)
622 {
623 off_t compressed;
624
625 /* compressed */
626 compressed = st.st_size;
627 item[1] = g_strdup_printf("%" G_GUINT64_FORMAT, (guint64) compressed);
628
629 /* uncompressed */
630 stat(workfile, &st);
631 archive->files_size = (guint64) st.st_size;
632 item[0] = g_strdup_printf("%" G_GUINT64_FORMAT, archive->files_size);
633
634 /* saving */
635 if (st.st_size)
636 item[2] = g_strdup_printf("%.1f%%", 100.0 - 100.0 * compressed / st.st_size);
637 else
638 item[2] = g_strdup("-");
639
640 /* trigger pseudo-parser once */
641 command = g_strdup("sh -c echo");
642 archive->parse_output = xa_gzip_et_al_globally_stored_entry;
643 }
644
645 g_free(workfile);
646
647 xa_spawn_async_process(archive, command);
648 g_free(command);
649
650 archive->size_column = 2;
651 archive->column_types = g_malloc0(sizeof(types));
652
653 for (i = 0; i < archive->columns; i++)
654 archive->column_types[i] = types[i];
655
656 xa_create_liststore(archive, titles);
657 }
658
xa_gzip_et_al_test(XArchive * archive)659 void xa_gzip_et_al_test (XArchive *archive)
660 {
661 gchar *password_str, *command;
662
663 password_str = xa_gzip_et_al_password_str(archive->password, archive->type);
664 command = g_strconcat(archiver[archive->type].program[0], " -t", password_str, lrzip || lz4 ? " " : "v ", archive->path[1], NULL);
665 g_free(password_str);
666
667 xa_run_command(archive, command);
668 g_free(command);
669 }
670
xa_gzip_et_al_extract(XArchive * archive,GSList * file_list)671 gboolean xa_gzip_et_al_extract (XArchive *archive, GSList *file_list)
672 {
673 GString *files;
674 gchar *command, *filename, *files_str, *out_dir, *out_file, *archive_path, *extraction_dir, *password_str;
675 gboolean result;
676
677 files = xa_quote_filenames(file_list, NULL, TRUE);
678
679 if (*files->str)
680 {
681 filename = g_shell_unquote(files->str + 1, NULL);
682 files_str = xa_quote_shell_command(files->str + 1, FALSE);
683 }
684 else
685 {
686 char *dot;
687
688 filename = g_path_get_basename(archive->path[0]);
689 dot = strrchr(filename, '.');
690
691 if (dot)
692 *dot = 0;
693
694 files_str = xa_quote_shell_command(filename, TRUE);
695 }
696
697 out_dir = g_shell_unquote(archive->extraction_dir, NULL);
698 out_file = g_strconcat(out_dir, "/", filename, NULL);
699
700 archive_path = xa_quote_shell_command(archive->path[0], TRUE);
701 extraction_dir = xa_quote_shell_command(archive->extraction_dir, FALSE);
702
703 if (archive->do_overwrite || !g_file_test(out_file, G_FILE_TEST_EXISTS))
704 {
705 password_str = xa_gzip_et_al_password_str(archive->password, archive->type);
706 command = g_strconcat("sh -c \"", archiver[archive->type].program[0], lrzip ? " " : " -c ", "-d", password_str, " ", archive_path, lrzip ? " -fo " : " > ", extraction_dir, "/", files_str, "\"", NULL);
707 g_free(password_str);
708 }
709 else
710 command = g_strdup("sh -c \"\"");
711
712 g_free(extraction_dir);
713 g_free(archive_path);
714 g_free(out_file);
715 g_free(out_dir);
716 g_free(filename);
717 g_free(files_str);
718 g_string_free(files, TRUE);
719
720 result = xa_run_command(archive, command);
721 g_free(command);
722
723 return result;
724 }
725
xa_gzip_et_al_add(XArchive * archive,GSList * file_list,gchar * compression)726 void xa_gzip_et_al_add (XArchive *archive, GSList *file_list, gchar *compression)
727 {
728 GString *files;
729 gchar *move, *files_str, *archive_path, *password_str, *command;
730
731 if (archive->location_path != NULL)
732 archive->child_dir = g_strdup(archive->working_dir);
733
734 if (!compression)
735 {
736 switch (archive->type)
737 {
738 case XARCHIVETYPE_BZIP2:
739 compression = "9";
740 break;
741
742 case XARCHIVETYPE_COMPRESS:
743 compression = "16";
744 break;
745
746 case XARCHIVETYPE_GZIP:
747 case XARCHIVETYPE_LZIP:
748 case XARCHIVETYPE_XZ:
749 compression = "6";
750 break;
751
752 case XARCHIVETYPE_LZ4:
753 compression = "1";
754 break;
755
756 case XARCHIVETYPE_LRZIP:
757 case XARCHIVETYPE_LZMA:
758 compression = "7";
759 break;
760
761 case XARCHIVETYPE_LZOP:
762 case XARCHIVETYPE_ZSTD:
763 compression = "3";
764 break;
765
766 default:
767 break;
768 }
769 }
770
771 files = xa_quote_filenames(file_list, NULL, TRUE);
772 files_str = xa_escape_bad_chars(files->str, "\"");
773 archive_path = xa_quote_shell_command(archive->path[0], TRUE);
774
775 if (archive->do_move)
776 move = g_strconcat(" && rm", files_str, NULL);
777 else
778 move = g_strdup("");
779
780 password_str = xa_gzip_et_al_password_str(archive->password, archive->type);
781 command = g_strconcat("sh -c \"", archiver[archive->type].program[0], lrzip ? " -" : " -c -", compress ? "b " : (lrzip ? "L " : ""), compression, password_str, files_str, lrzip ? " -fo " : " > ", archive_path, move, "\"", NULL);
782 g_free(password_str);
783
784 g_free(move);
785 g_free(archive_path);
786 g_free(files_str);
787 g_string_free(files, TRUE);
788
789 xa_run_command(archive, command);
790 g_free(command);
791 }
792