libarchiveを使う

2020/04/04 17:06:32

改訂履歴
改訂 0.002018/08/01 14:23:55aaauuue

初版


目次

1. はじめに
2. インストール
3. コンパイル方法
4. コード
4.1. バージョンの出力
4.2. アーカイブの読み込み
4.3. ファイルの抽出
4.4. アーカイブの作成
5. さいごに

libarchive(https://www.libarchive.org/)とは、C言語からアーカイブを操作できるライブラリです。このライブラリの最大の特徴は、さまざまな種類のアーカイブを同じようなコードで操作できる事です。

今回はlibarchiveのテストコードを元にlibarchiveを使用したコードを書いてみます。

動作確認には以下の環境を用いています。

  • Linux Mint 18.1

  • GCC 5.4.0

  • libarchive 3.2.1

libarchiveはLinux Mintの場合apt-getでインストールする事ができます。

その他に、libarchiveはソースからもインストールする事ができます。libarchiveは、コンパイルを行う時に他のライブラリにあまり依存していないので、簡単にコンパイルする事ができます。ただ、実際にlibarchiveを使用して圧縮などを行う時には、個別の圧縮フォーマットのライブラリが必要になる事があるので、注意してください。

libarchiveを使用したコードをコンパイルする時には、以下のようにします。

gcc -o foo foo.c -larchive

ここから実際にコードを書いていきます。

libarchiveのAPIはよく考えられていて、アーカイブの読み込みと書き込みの処理の流れはほとんど変わりません。手順1「アーカイブの読み込みの流れ」, 手順2「アーカイブの書き込みの流れ」は、読み込みと書き込みのおおまかな処理の流れですが、非常によく似ています。

また、libarchiveは、アーカイブをディスクのファイルからではなくメモリから読み込む事もできますが、その時にもソースを読み込む関数を変える以外は変更はありません。

アーカイブを読み込みます。例2「read_archive.c」は、そのコードです。

ここで注目して欲しいのは、read_archive_header_only関数とread_archive_header_only_from_memory関数です。どちらの関数もアーカイブからエントリーのヘッダを読み込むだけですが、前者はアーカイブをディスクから、後者はアーカイブをメモリから読み込んでいます。「コード」にも書いたように、例え別のソースから読み込む場合でも、ソースを読み込む箇所(例2「read_archive.c」の(1), (2))以外の処理は全く同じです。

例2 read_archive.c


#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

#include <archive.h>
#include <archive_entry.h>

void read_archive_header_only( char *src )
{
     struct archive       *a     = NULL;
     struct archive_entry *entry = NULL;
     int                   r     = ARCHIVE_OK;

     a = archive_read_new();
     archive_read_support_filter_all( a );
     archive_read_support_format_all( a );
     r = archive_read_open_filename( a, src, BUFSIZ );                              (1)
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
     while( archive_read_next_header( a, &entry ) == ARCHIVE_OK ) {
          printf( "filename = [%s]\n", archive_entry_pathname_utf8( entry ) );
          archive_read_data_skip( a );                                              (3)
     }
     r = archive_read_close( a );
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
     r = archive_read_free( a );
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
}

void read_archive_header_only_from_memory( char *buf, size_t size )
{
     struct archive       *a     = NULL;
     struct archive_entry *entry = NULL;
     int                   r     = ARCHIVE_OK;

     a = archive_read_new();
     archive_read_support_filter_all( a );
     archive_read_support_format_all( a );
     r = archive_read_open_memory( a, buf, size );                                  (2)
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
     while( archive_read_next_header( a, &entry ) == ARCHIVE_OK ) {
          printf( "filename = [%s]\n", archive_entry_pathname_utf8( entry ) );
          archive_read_data_skip( a );
     }
     r = archive_read_close( a );
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
     r = archive_read_free( a );
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
}

void read_archive_with_data( char *src )
{
     struct archive       *a     = NULL;
     struct archive_entry *entry = NULL;
     int                   r     = ARCHIVE_OK;
     size_t                size  = 0;
     char buf[BUFSIZ]            = { '\0' };

     a = archive_read_new();
     archive_read_support_filter_all( a );
     archive_read_support_format_all( a );
     r = archive_read_open_filename( a, src, BUFSIZ );
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
     while( archive_read_next_header( a, &entry ) == ARCHIVE_OK ) {
          printf( "filename = [%s]\n", archive_entry_pathname_utf8( entry ) );
          for( ;; ) {
               size = archive_read_data( a, buf, BUFSIZ );                          (4)
               if( size <= 0 ) {                                                    (4)
                    break;                                                          (4)
               }                                                                    (4)
               printf( "%s", buf );                                                 (4)
               memset( buf, 0, BUFSIZ );                                            (4)
          }
     }
     r = archive_read_close( a );
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
     r = archive_read_free( a );
     if( r != ARCHIVE_OK ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          exit( 1 );
     }
}

int main( int argc, char **argv )
{
     read_archive_header_only( "ar.tar" );
     
     {
          FILE *fp = NULL;
          size_t size = 0;
          char *buf = NULL;

          fp = fopen( "ar.tar", "rb" );
          if( !fp ) {
               fprintf( stderr, "Can't open file\n" );
               exit( 1 );
          }
          fseek( fp, 0, SEEK_END );
          size = ftell( fp );
          fseek( fp, 0, SEEK_SET );

          buf = ( char * )malloc( sizeof( char ) * size );
          if( !buf ) {
               fprintf( stderr, "Can't allocate memory\n" );
               exit( 1 );
          }
          fread( buf, size, 1, fp );
          fclose( fp );
          read_archive_header_only_from_memory( buf, size );
     }

     read_archive_with_data( "ar.tar" );

     return 0;
}


/* 
 * Local variables:
 * compile-command: "gcc -std=c99 -g3 -o read_archive read_archive.c -larchive"
 * End:
 */

(1)

ディスクからアーカイブを読み込む

(2)

メモリからアーカイブを読み込む

(3)

エントリーのデータ部分を読み飛ばす。この関数の呼び出しは必須ではなく、一度archive_read_next_headerが呼び出された後にもう一度この関数が呼び出されると、archive_read_data_skipが呼び出されたと見なされて処理が進む。

(4)

エントリーのデータの読み込み。libarchiveは、エントリーのデータの大きさと同じ大きさのバッファを確保してそこに読み込むという戦略を取らず、小さいバッファで複数回読み込むという戦略を取る。libarchiveでは、これをZeroCopyと呼んでいる。


アーカイブからファイルを抽出します。例3「extract_file.c」は、そのコードです。

例3 extract_file.c


#include <stdio.h>
#include <stdlib.h>

#include <archive.h>
#include <archive_entry.h>

void extract_all_file( char *src )
{
     int                          flags      = 0;
     int                          r          = 0;
     long                         len        = 0;
     char                    buf[BUFSIZ]     = { '\0' };
     struct archive              *ext        = NULL;
     struct archive              *a          = NULL;
     struct archive_entry        *entry_iter = NULL;

     flags = ARCHIVE_EXTRACT_TIME;                                                  (1)
     flags |= ARCHIVE_EXTRACT_PERM;                                                 (1)
     flags |= ARCHIVE_EXTRACT_ACL;                                                  (1)
     flags |= ARCHIVE_EXTRACT_FFLAGS;                                               (1)

     a = archive_read_new();
     archive_read_support_filter_all( a );
     archive_read_support_format_all( a );
     ext = archive_write_disk_new();
     archive_write_disk_set_options( ext, flags );
     archive_write_disk_set_standard_lookup( ext );

     if( ( r = archive_read_open_filename( a, src, BUFSIZ ) ) ) {
          fprintf( stderr, "%s\n", archive_error_string( a ) );
          archive_read_close( a );
          archive_read_free( a );
          archive_write_close( ext );
          archive_write_free( ext );
          exit( 1 );
     }

     for( ;; ) {
          r = archive_read_next_header( a, &entry_iter );
          if( r == ARCHIVE_EOF ) {
               break;
          }
          if( r < ARCHIVE_OK ) {
               fprintf( stderr, "%s\n", archive_error_string( a ) );
               archive_read_close( a );
               archive_read_free( a );
               archive_write_close( ext );
               archive_write_free( ext );
               exit( 1 );
          }
          if( r < ARCHIVE_WARN ) {
               fprintf( stderr, "%s\n", archive_error_string( a ) );
               archive_read_close( a );
               archive_read_free( a );
               archive_write_close( ext );
               archive_write_free( ext );
               exit( 1 );
          }

          r = archive_write_header( ext, entry_iter );
          if( r < ARCHIVE_OK ) {
               fprintf( stderr, "%s\n", archive_error_string( ext ) );
               archive_read_close( a );
               archive_read_free( a );
               archive_write_close( ext );
               archive_write_free( ext );
               exit( 1 );
          }
          if( archive_entry_size( entry_iter ) > 0 ) {
               len = archive_read_data( a, buf, sizeof( buf ) );
               while( len > 0 ) {
                    if( archive_write_data( ext, buf, len ) != len ) {              (2)
                         fprintf( stderr, "%s\n", archive_error_string( ext ) );
                         archive_read_close( a );
                         archive_read_free( a );
                         archive_write_close( ext );
                         archive_write_free( ext );
                         exit( 1 );
                    }
                    len = archive_read_data( a, buf, sizeof( buf ) );
               }
               if( len < 0 ) {
                    fprintf( stderr, "%s\n", archive_error_string( a ) );
                    archive_read_close( a );
                    archive_read_free( a );
                    archive_write_close( ext );
                    archive_write_free( ext );
                    exit( 1 );
               }
          }

          r = archive_write_finish_entry( ext );
          if( r < ARCHIVE_OK ) {
               fprintf( stderr, "%s\n", archive_error_string( ext ) );
               archive_read_close( a );
               archive_read_free( a );
               archive_write_close( ext );
               archive_write_free( ext );
               exit( 1 );
          }
          if( r < ARCHIVE_WARN ) {
               fprintf( stderr, "%s\n", archive_error_string( ext ) );
               archive_read_close( a );
               archive_read_free( a );
               archive_write_close( ext );
               archive_write_free( ext );
               exit( 1 );
          }
     }

     archive_read_close( a );
     archive_read_free( a );
     archive_write_close( ext );
     archive_write_free( ext );
}

int main( int argc, char **argv )
{
     extract_all_file( argv[1] );
     
     return 0;
}


/* 
 * Local variables:
 * compile-command: "gcc -std=c99 -g3 -o extract_file extract_file.c -larchive"
 * End:
 */

(1)

ファイルのどの付属情報を復元するか決める。

(2)

データの抽出。


アーカイブを新たに作成します。例4「write_archive.c」は、そのコードです。

例4 write_archive.c


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <archive.h>
#include <archive_entry.h>

void write_archive( char *outname, char **filename )
{
     struct archive       *a           = NULL;
     struct archive_entry *entry       = NULL;
     char                  buf[BUFSIZ] = { '\0' };
     int                   len         = 0;
     struct stat           st;
     int                   fd;

     a = archive_write_new();
     archive_write_add_filter_gzip( a );                                            (1)
     archive_write_set_format_pax_restricted( a );                                  (1)
     archive_write_open_filename( a, outname );
     entry = archive_entry_new();
     while( *filename ) {
          stat( *filename, &st );
          archive_entry_copy_stat( entry, &st );                                    (2)
          archive_entry_copy_pathname( entry, *filename );
          archive_entry_set_size( entry, st.st_size );
          archive_entry_set_filetype( entry, AE_IFREG );
          archive_entry_set_perm( entry, 0644 );
          archive_write_header( a, entry );
          fd = open( *filename, O_RDONLY );
          len = read( fd, buf, sizeof( buf ) );
          while( len > 0 ) {
               archive_write_data( a, buf, len );
               len = read( fd, buf, sizeof( buf ) );
          }
          close( fd );
          archive_entry_clear( entry );
          filename++;
     }
     archive_write_close( a );
     archive_write_free( a );
}

int main( int argc, char **argv )
{
     char *files[] = { "extract_file.c", "livarchive_version.c",
                       "read_archive.c", "write_archive.c", NULL };
     write_archive( "create.tar.gz", files );
     
     return 0;
}

/*
 * Local variables:
 * compile-command: "gcc -std=c99 -g3 -o write_archive write_archive.c -larchive"
 * End:
 */

(1)

アーカイブの形式の指定。この場合はtar.gz(pax)となる。他にも、archive_write_set_format,archive_write_add_filter関数と定数を用いてアーカイブの形式を指定する方法もある。

(2)

エントリーにファイルの情報をコピーする。これ以外にも、archive_entry_set_ctime関数などを呼んで個別に設定する事もできる。


libarchiveのテストコードを元にコードを書いてみました。実際にコードを書いてみて分かった事は、驚く程簡単に複雑な複数種類のアーカイブ形式の操作ができたという事です。また、APIも良く抽象化されていて、ほとんどの処理が同じような呼び出し手順で書けたのも驚きです。

このような有用なライブラリを公開して下さったlibarchiveの開発者の方に感謝します。