Ошибка `write too long` при попытке создать файл tar.gz из файла и каталогов

go tar gzip

3088 просмотра

2 ответа

635 Репутация автора

Поэтому я пытаюсь создать файл tar.gz из нескольких каталогов и файлов. Что-то с тем же использованием, что и:

tar -cvzf sometarfile.tar.gz somedir/ someotherdir/ somefile.json somefile.xml

Предполагая, что у каталогов есть другие каталоги внутри них. У меня есть это в качестве входа:

    paths := []string{
      "somedir/",
      "someotherdir/",
      "somefile.json",
      "somefile.xml",
    }

и используя эти:

    func TarFilesDirs(paths []string, tarFilePath string ) error {
       // set up the output file
       file, err := os.Create(tarFilePath)
       if err != nil {
           return err
       }

       defer file.Close()
       // set up the gzip writer
       gz := gzip.NewWriter(file)
       defer gz.Close()

       tw := tar.NewWriter(gz)
       defer tw.Close()

       // add each file/dir as needed into the current tar archive
       for _,i := range paths {
          if err := tarit(i, tw); err != nil {
               return err
          }
       }

       return nil
   }

func tarit(source string, tw *tar.Writer) error {
    info, err := os.Stat(source)
    if err != nil {
        return nil
    }

    var baseDir string
    if info.IsDir() {
        baseDir = filepath.Base(source)
    }

    return filepath.Walk(source,
        func(path string, info os.FileInfo, err error) error {
            if err != nil {
                return err
            }

            header, err := tar.FileInfoHeader(info, info.Name())
            if err != nil {
                return err
            }

            if baseDir != "" {
                header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
            }

            if err := tw.WriteHeader(header); err != nil {
                return err
            }

            if info.IsDir() {
                return nil
            }

            file, err := os.Open(path)
            if err != nil {
                return err
            }

            defer file.Close()

            _, err = io.Copy(tw, file)
            if err != nil {
                log.Println("failing here")
                return err
            }

            return err
        })
}

Проблема: если каталог большой, я получаю:

archive/tar: write too long

ошибка, когда убираю это все работает.

Закончились идеи и потратили много часов на это, пытаясь найти решение ...

Есть идеи?

Спасибо

Автор: Blue Bot Источник Размещён: 19.07.2016 09:41

Ответы (2)


0 плюса

2993 Репутация автора

Запись пишет в текущую запись в архиве tar. Write возвращает ошибку ErrWriteTooLong, если после WriteHeader записано больше байтов hdr.Size.

Существует Sizeопция, которую вы можете добавить в заголовок. Не пробовал, но, может быть, это помогает ...

Смотрите также https://golang.org/pkg/archive/tar/

Автор: TehSphinX Размещён: 19.07.2016 09:58

6 плюса

76 Репутация автора

Решение

У меня была похожая проблема, пока я не посмотрел более внимательно на документ tar.FileInfoHeader:

FileInfoHeader создает частично заполненный заголовок из fi. Если fi описывает символическую ссылку, FileInfoHeader записывает ссылку как цель ссылки. Если fi описывает каталог, к имени добавляется косая черта. Поскольку метод Name os.FileInfo возвращает только базовое имя файла, который он описывает, может потребоваться изменить поле «Имя» возвращаемого заголовка, чтобы указать полное имя файла.

По сути, FileInfoHeader не обязательно заполняет все поля заголовка до того, как вы напишите его с помощью WriteHeader, и если вы посмотрите на реализацию, поле Размер будет установлено только для обычных файлов. Ваш фрагмент кода, кажется, обрабатывает только каталоги, это означает, что если вы столкнетесь с любым другим нестандартным файлом, вы напишите заголовок с нулевым размером, а затем попытаетесь скопировать специальный файл с ненулевым размером на диск в tar. Go возвращает ErrWriteTooLong, чтобы остановить вас от создания сломанной tar.

Я придумал это, и с тех пор у меня не было проблемы.

    if err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
        if err != nil {
            return check(err)
        }

        var link string
        if info.Mode()&os.ModeSymlink == os.ModeSymlink {
            if link, err = os.Readlink(path); err != nil {
                return check(err)
            }
        }

        header, err := tar.FileInfoHeader(info, link)
        if err != nil {
            return check(err)
        }

        header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, directory))
        if err = tw.WriteHeader(header); err != nil {
            return check(err)
        }

        if !info.Mode().IsRegular() { //nothing more to do for non-regular
            return nil
        }

        fh, err := os.Open(path)
        if err != nil {
            return check(err)
        }
        defer fh.Close()

        if _, err = io.CopyBuffer(tw, fh, buf); err != nil {
            return check(err)
        }
        return nil
})
Автор: IaBurton Размещён: 12.10.2016 04:23
32x32