linux 如何使用分块方法将文件流式传输到tar?

pwuypxnk  于 2023-08-03  发布在  Linux
关注(0)|答案(1)|浏览(158)

我想通过调用ssh上的tar脚本来备份我的系统,该脚本通过管道返回到stdout,以便ssh启动主机存储tar。
但是,我想对在该主机上运行的一些服务执行逻辑转储,但那里没有足够的磁盘空间将这些巨大的文件转储到磁盘上,然后从tar捕获它们。
我知道tar不能处理流(或任何大小未知的文件)。所以我想,我在运行到固定大小的块时split转储,将它们临时存储在磁盘上,将它们带到tar进行处理,然后在处理下一个块之前删除它们。
我的脚本看起来像这样:

mkfifo filenames
tar --files-from filenames -cf - &
TAR_PID=$!
exec 100>filenames

# tar all relevant host-level directories/files
echo "/etc" >&100
echo "/root" >&100

function splitfilter() {
  tee $1

  (
    # wait for tar to finish reading the file and delete it after being processed
    inotifywait -e close_nowrite $1
    rm $1
  ) &
  RM_SHELL_PID=$!

  # send the filename for processing to tar
  echo $1 >&100

  wait $RM_SHELL_PID
}
export -f splitfilter

# perform the logical dumps of my services
dump_program_1 | split -b 4K /var/backup/PREFIX_DUMP_1_ --filter "splitfilter $FILE"
dump_program_2 | split -b 4K /var/backup/PREFIX_DUMP_2_ --filter "splitfilter $FILE"

exec 100>&-
wait $TAR_PID
rm filenames

字符串
然而,我不明白为什么这是随机的工作和不。到目前为止,我观察到了两种不同的失效行为:

  • 焦油没有停止。在脚本的最后,我关闭了文件描述符,所以我希望fifo向tar发送EOF信号。这应该会很快结束tar进程,因为它只需要完成最后4k块的处理(如果还没有完成的话)。我无法解释为什么它会随机挂起。最终生成的存档实际上是完整的(除了tar的EOF标记)...
  • tar处理0字节文件。经过一段时间的处理后,inotifywait似乎在tar关闭块文件以供阅读之前唤醒。从而导致文件被删除,从而在存档中显示为0字节大小的条目。我已经通过在echo $1 >&100调用后放置一个sleep 1来缓解这种情况。在那之后,前几个块实际上会被填满,但是运行一段时间后,后面的块又变成了0大小。我感觉到时间问题在这里的某个地方,但目前看不出来。

经过一天的调试,我对这种方法失去了希望,但如果它能可靠地工作,那就太好了:居然能产生流状焦油!别误会我的意思它在调试时工作了一两次。我就是不明白为什么不总是

wixjitnu

wixjitnu1#

tar格式相当简单。我们可以自己用这个TXR Lisp程序来传输它。
警告:这不能处理长路径;它为每个对象只输出一个头块。
备份列表由路径和命令条目混合组成。
命令被执行,它们的输出被切成4K的片段,这些片段成为编号的文件。这些都是删除,因为我们去,所以没有积累。
即使在我们编写自己的tar实现时,我们仍然必须这样做,因为该格式要求预先知道每个对象的大小并将其放置在头中。没有办法将任意长的命令输出作为tar流进行流式传输。

(defvar backup-list
  '("/etc"
    "/root"
    (:cmd "cat /proc/cpuinfo" "cpuinfo")
    (:cmd "lspci" "lspci")))

(defsymacro splsize 4096) ;; split size for commands

(defsymacro blsize 512) ;; tar block size: written in stone

(typedef tarheader
  (struct tarheader
    (name (array 100 zchar))
    (mode (array 8 zchar))
    (uid (array 8 zchar))
    (gid (array 8 zchar))
    (size (array 12 zchar))
    (mtime (array 12 zchar))
    (chksum (array 8 char))
    (typeflag char)
    (linkname (array 100 zchar))
    (magic (array 6 char))
    (version (array 2 char))
    (uname (array 32 zchar))
    (gname (array 32 zchar))
    (devmajor (array 8 zchar))
    (devminor (array 8 zchar))
    (prefix (array 155 zchar))))

(defmacro octfill (slot expr)
  ^(fmt "~,0*o" (pred (sizeof tarheader.,slot)) ,expr))

;; Dump an object into the archive.
;; Form a correct header, calculate the checksum,
;; put out a header block and for regular files,
;; put out data blocks.
(defun tar-dump-object (file-in stream : stat)
  (let* ((file (trim-path-seps file-in))
         (s (or stat (stat file)))
         (tf (ecaseql* (logand s.mode s-ifmt)
               (s-ifreg #\0)
               (s-iflnk #\2)
               (s-ifchr #\3)
               (s-ifblk #\4)
               (s-ifdir #\5)
               (s-ififo #\6)))
         (h (new tarheader
                 name (let* ((n (cond
                                  ((equal "/" file) ".")
                                  ((starts-with "/" file) [file 1..:])
                                  (t file))))
                        (if (eql tf #\5) `@n/` n))
                 mode (octfill mode (logand s.mode #o777))
                 uid (octfill uid s.uid)
                 gid (octfill gid s.gid)
                 size (octfill size (if (eql tf #\0) s.size 0))
                 mtime (octfill mtime s.mtime)
                 chksum (load-time (str 8))
                 typeflag tf
                 linkname (if (eql tf #\2) (readlink file) "")
                 magic "ustar "
                 version " "
                 uname (or (getpwuid s.uid).?name "")
                 gname (or (getgrgid s.gid).?name "")
                 devmajor (if (meql tf #\3 #\4)
                            (octfill devmajor (major s.rdev)) "")
                 devminor (if (meql tf #\3 #\4)
                            (octfill devminor (minor s.rdev)) "")
                 prefix ""))
         (hb (ffi-put h (ffi tarheader)))
         (ck (logand (sum hb) #x1FFFF))
         (bl (make-buf blsize))
         (nb (trunc (+ s.size blsize -1) blsize)))
    (set h.chksum (fmt "~,06o\xdc00 " ck))
    (ffi-put-into bl h (ffi tarheader))
    (put-buf bl 0 stream)
    (if (eql tf #\0)
      (with-stream (in (open-file file "rb"))
        (each ((i 0..nb))
          (fill-buf-adjust bl 0 in)
          (buf-set-length bl blsize)
          (put-buf bl 0 stream))))))

;; Output two zero-filled blocks to terminate archive.
(defun tar-finish-archive (: (stream *stdout*))
  (let ((bl (make-buf (* 2 blsize))))
    (put-buf bl 0 stream)))

;; Dump an object into the archive, recursing
;; if it is a directory.
(defun tar-dump-recursive (path : (stream *stdout*))
  (ftw path (lambda (path type stat . rest)
              (tar-dump-object path stream stat))))

;; Dump a command to the archive by capturing the
;; output into numbered temporary split files.
(defun tar-dump-command (command prefix : (stream *stdout*))
  (let ((bl (make-buf splsize))
        (i 0))
    (with-stream (s (open-command command "rb"))
      (while (plusp (fill-buf-adjust bl 0 s))
        (let ((name (pic `@{prefix}0###` (inc i))))
          (file-put-buf name bl)
          (tar-dump-object name stream)
          (remove-path name))))))

;; main: process the backup list to stream out the archive
;; on standard output, then terminate it.
(each ((item backup-list))
  (match-ecase item
    ((:cmd @cmd @prefix) (tar-dump-command cmd prefix))
    (`@file` (tar-dump-recursive file))))

(tar-finish-archive)

字符串
我没有一个回归测试套件;我手动测试了它,方法是将各种类型的单个对象存档,并将十六进制转储与GNU tar进行比较,然后解包由该实现存档的目录树,并对原始树进行递归差异。
但是,我想知道您正在使用的备份服务是否无法处理连锁存档。如果它处理链接的归档,那么您可以使用tar的多个调用来生成流,而不会有所有这些进程协调问题。
对于一个tar消费者来说,要处理连锁的归档,它只需要忽略全零块(不把它们当作归档的结尾),但是继续阅读。
如果备份服务是这样的,那么你基本上可以这样做:

(tar cf - /etc
 tar cf - /root
 dump_program_1 | \
   split -b 4K /var/backup/PREFIX_DUMP_1_ \
         --filter "tar cf - $FILE; rm $FILE"
 ...) | ... into backup service ...


我在GNUTar中看不到任何不写终止零的选项。也许可以写一个过滤器来消除这些:

tar cf - file | remove-zero-blocks


尚未写入的remove-zero-blocks过滤器,通过面向块的FIFO读取512字节块,该FIFO足够长,可以覆盖tar使用的分块因子。它将新读取的缓冲区放入FIFO的一端,并写入从另一端碰撞的最旧缓冲区。当遇到EOF时,FIFO被刷新,但是所有为零的尾部512字节块被省略。
这应该会击败拒绝忽略零块的备份服务。

相关问题