+/* Format totals to file FP. FORMATS is an array of strings to output
+ before each data item (bytes read, written, deleted, in that order).
+ EOR is a delimiter to output after each item (used only if deleting
+ from the archive), EOL is a delimiter to add at the end of the output
+ line. */
+int
+format_total_stats (FILE *fp, const char **formats, int eor, int eol)
+{
+ int n;
+
+ switch (subcommand_option)
+ {
+ case CREATE_SUBCOMMAND:
+ case CAT_SUBCOMMAND:
+ case UPDATE_SUBCOMMAND:
+ case APPEND_SUBCOMMAND:
+ n = print_stats (fp, formats[TF_WRITE],
+ prev_written + bytes_written);
+ break;
+
+ case DELETE_SUBCOMMAND:
+ {
+ char buf[UINTMAX_STRSIZE_BOUND];
+ n = print_stats (fp, formats[TF_READ],
+ records_read * record_size);
+
+ fputc (eor, fp);
+ n++;
+
+ n += print_stats (fp, formats[TF_WRITE],
+ prev_written + bytes_written);
+
+ fputc (eor, fp);
+ n++;
+
+ if (formats[TF_DELETED] && formats[TF_DELETED][0])
+ n += fprintf (fp, "%s: ", gettext (formats[TF_DELETED]));
+ n += fprintf (fp, "%s",
+ STRINGIFY_BIGINT ((records_read - records_skipped)
+ * record_size
+ - (prev_written + bytes_written), buf));
+ }
+ break;
+
+ case EXTRACT_SUBCOMMAND:
+ case LIST_SUBCOMMAND:
+ case DIFF_SUBCOMMAND:
+ n = print_stats (fp, _(formats[TF_READ]),
+ records_read * record_size);
+ break;
+
+ default:
+ abort ();
+ }
+ if (eol)
+ {
+ fputc (eol, fp);
+ n++;
+ }
+ return n;
+}
+
+const char *default_total_format[] = {
+ N_("Total bytes read"),
+ /* Amanda 2.4.1p1 looks for "Total bytes written: [0-9][0-9]*". */
+ N_("Total bytes written"),
+ N_("Total bytes deleted")
+};
+
+void
+print_total_stats (void)
+{
+ format_total_stats (stderr, default_total_format, '\n', '\n');
+}
+
+/* Compute and return the block ordinal at current_block. */
+off_t
+current_block_ordinal (void)
+{
+ return record_start_block + (current_block - record_start);
+}
+
+/* If the EOF flag is set, reset it, as well as current_block, etc. */
+void
+reset_eof (void)
+{
+ if (hit_eof)
+ {
+ hit_eof = false;
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+ access_mode = ACCESS_WRITE;
+ }
+}
+
+/* Return the location of the next available input or output block.
+ Return zero for EOF. Once we have returned zero, we just keep returning
+ it, to avoid accidentally going on to the next file on the tape. */
+union block *
+find_next_block (void)
+{
+ if (current_block == record_end)
+ {
+ if (hit_eof)
+ return 0;
+ flush_archive ();
+ if (current_block == record_end)
+ {
+ hit_eof = true;
+ return 0;
+ }
+ }
+ return current_block;
+}
+
+/* Indicate that we have used all blocks up thru BLOCK. */
+void
+set_next_block_after (union block *block)
+{
+ while (block >= current_block)
+ current_block++;
+
+ /* Do *not* flush the archive here. If we do, the same argument to
+ set_next_block_after could mean the next block (if the input record
+ is exactly one block long), which is not what is intended. */
+
+ if (current_block > record_end)
+ abort ();
+}
+
+/* Return the number of bytes comprising the space between POINTER
+ through the end of the current buffer of blocks. This space is
+ available for filling with data, or taking data from. POINTER is
+ usually (but not always) the result of previous find_next_block call. */
+size_t
+available_space_after (union block *pointer)
+{
+ return record_end->buffer - pointer->buffer;
+}
+
+/* Close file having descriptor FD, and abort if close unsuccessful. */
+void
+xclose (int fd)
+{
+ if (close (fd) != 0)
+ close_error (_("(pipe)"));
+}
+
+static void
+init_buffer (void)
+{
+ if (! record_buffer_aligned[record_index])
+ record_buffer_aligned[record_index] =
+ page_aligned_alloc (&record_buffer[record_index], record_size);
+
+ record_start = record_buffer_aligned[record_index];
+ current_block = record_start;
+ record_end = record_start + blocking_factor;
+}
+
+static void
+check_tty (enum access_mode mode)
+{
+ /* Refuse to read archive from and write it to a tty. */
+ if (strcmp (archive_name_array[0], "-") == 0
+ && isatty (mode == ACCESS_READ ? STDIN_FILENO : STDOUT_FILENO))
+ {
+ FATAL_ERROR ((0, 0,
+ mode == ACCESS_READ
+ ? _("Refusing to read archive contents from terminal "
+ "(missing -f option?)")
+ : _("Refusing to write archive contents to terminal "
+ "(missing -f option?)")));
+ }
+}
+
+/* Open an archive file. The argument specifies whether we are
+ reading or writing, or both. */
+static void
+_open_archive (enum access_mode wanted_access)
+{
+ int backed_up_flag = 0;
+
+ if (record_size == 0)
+ FATAL_ERROR ((0, 0, _("Invalid value for record_size")));
+
+ if (archive_names == 0)
+ FATAL_ERROR ((0, 0, _("No archive name given")));
+
+ tar_stat_destroy (¤t_stat_info);
+
+ record_index = 0;
+ init_buffer ();
+
+ /* When updating the archive, we start with reading. */
+ access_mode = wanted_access == ACCESS_UPDATE ? ACCESS_READ : wanted_access;
+ check_tty (access_mode);
+
+ read_full_records = read_full_records_option;
+
+ records_read = 0;
+
+ if (use_compress_program_option)
+ {
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ child_pid = sys_child_open_for_uncompress ();
+ read_full_records = true;
+ record_end = record_start; /* set up for 1st record = # 0 */
+ break;
+
+ case ACCESS_WRITE:
+ child_pid = sys_child_open_for_compress ();
+ break;
+
+ case ACCESS_UPDATE:
+ abort (); /* Should not happen */
+ break;
+ }
+
+ if (!index_file_name
+ && wanted_access == ACCESS_WRITE
+ && strcmp (archive_name_array[0], "-") == 0)
+ stdlis = stderr;
+ }
+ else if (strcmp (archive_name_array[0], "-") == 0)
+ {
+ read_full_records = true; /* could be a pipe, be safe */
+ if (verify_option)
+ FATAL_ERROR ((0, 0, _("Cannot verify stdin/stdout archive")));
+
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ {
+ bool shortfile;
+ enum compress_type type;
+
+ archive = STDIN_FILENO;
+ type = check_compressed_archive (&shortfile);
+ if (type != ct_tar && type != ct_none)
+ FATAL_ERROR ((0, 0,
+ _("Archive is compressed. Use %s option"),
+ compress_option (type)));
+ if (shortfile)
+ ERROR ((0, 0, _("This does not look like a tar archive")));
+ }
+ break;
+
+ case ACCESS_WRITE:
+ archive = STDOUT_FILENO;
+ if (!index_file_name)
+ stdlis = stderr;
+ break;
+
+ case ACCESS_UPDATE:
+ archive = STDIN_FILENO;
+ write_archive_to_stdout = true;
+ record_end = record_start; /* set up for 1st record = # 0 */
+ if (!index_file_name)
+ stdlis = stderr;
+ break;
+ }
+ }
+ else
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ archive = open_compressed_archive ();
+ if (archive >= 0)
+ guess_seekable_archive ();
+ break;
+
+ case ACCESS_WRITE:
+ if (backup_option)
+ {
+ maybe_backup_file (archive_name_array[0], 1);
+ backed_up_flag = 1;
+ }
+ if (verify_option)
+ archive = rmtopen (archive_name_array[0], O_RDWR | O_CREAT | O_BINARY,
+ MODE_RW, rsh_command_option);
+ else
+ archive = rmtcreat (archive_name_array[0], MODE_RW,
+ rsh_command_option);
+ break;
+
+ case ACCESS_UPDATE:
+ archive = rmtopen (archive_name_array[0],
+ O_RDWR | O_CREAT | O_BINARY,
+ MODE_RW, rsh_command_option);
+
+ switch (check_compressed_archive (NULL))
+ {
+ case ct_none:
+ case ct_tar:
+ break;
+
+ default:
+ FATAL_ERROR ((0, 0,
+ _("Cannot update compressed archives")));
+ }
+ break;
+ }
+
+ if (archive < 0
+ || (! _isrmt (archive) && !sys_get_archive_stat ()))
+ {
+ int saved_errno = errno;
+
+ if (backed_up_flag)
+ undo_last_backup ();
+ errno = saved_errno;
+ open_fatal (archive_name_array[0]);
+ }
+
+ sys_detect_dev_null_output ();
+ sys_save_archive_dev_ino ();
+ SET_BINARY_MODE (archive);
+
+ switch (wanted_access)
+ {
+ case ACCESS_READ:
+ find_next_block (); /* read it in, check for EOF */
+ break;
+
+ case ACCESS_UPDATE:
+ case ACCESS_WRITE:
+ records_written = 0;
+ break;
+ }
+}
+
+/* Perform a write to flush the buffer. */
+static ssize_t
+_flush_write (void)
+{
+ ssize_t status;
+
+ checkpoint_run (true);
+ if (tape_length_option && tape_length_option <= bytes_written)
+ {
+ errno = ENOSPC;
+ status = 0;
+ }
+ else if (dev_null_output)
+ status = record_size;
+ else
+ status = sys_write_archive_buffer ();
+
+ if (status && multi_volume_option && !inhibit_map)
+ {
+ struct bufmap *map = bufmap_locate (status);
+ if (map)
+ {
+ size_t delta = status - map->start * BLOCKSIZE;
+ if (delta > map->sizeleft)
+ delta = map->sizeleft;
+ map->sizeleft -= delta;
+ if (map->sizeleft == 0)
+ map = map->next;
+ bufmap_reset (map, map ? (- map->start) : 0);