Gaucheでtarの読み書きをする実験

2020/04/04 17:07:56

改訂履歴
改訂 0.002018/09/01 06:37:10aaauuue

初版


目次

1. はじめに
1.1. 環境
2. tarについて
2.1. 種類
2.2. 構造
2.3. フォーマットの判別
3. C言語でtarを読み書きする
4. Gaucheでtarを読み書きする
5. 最後に
参考文献

以前Gaucheのlibarchiveの拡張を作成した時に、tarファイルについて興味を惹かれたので、Gaucheのみで読み書きができないかどうか実験してみる事にしました。

筆者はGaucheでバイナリの読み書きを行うのは初めてなので、tarのファイル形式の理解を深めるという意味も兼ねて、一旦C言語でtarを読み書きするコードを書いてから、それをGaucheのコードに移植するという方法を取りました。

実際に作成したコードは、それぞれ、こちら(C言語版)とこちら(Gauche版)になります。

今回の実験で実際にtarを読み書きするコードを書く事はできましたが、tarの仕様は大きくまた複雑であるため、全てを解析できたわけではなく、あくまでも最低限の部分しか実装できていません。今後もtarの仕様の理解に努めますが、その点、ご了承下さい。

tarには以下のファイル形式が存在します。これ以外にもstarという形式もあるようですが[2]、作成する事ができなかったので詳細は不明です。

  • v7 format

  • ustar format

  • pax format

  • posix format

  • GNU format

v7 formatは、tarが始めて作成された時に制定されたものです。ustar formatとpax formatはposix formatと同一のものとされています[2]。GNU formatに関しては、GNU Tarの1.13以降は新しい形式に移行しました。これによって、GNU formatは、1.13以前の古いGNU old formatが出来る事となりました。新しいGNU formatはposix形式に準拠するとされており[1]、未だにformat形式は決定されていませんが、今回の実験で調べた所、ほとんどposix formatと同じでした。

tarは512バイトのブロックが連なった形式になっています。この512バイトのブロックそれぞれに意味を持たせる事によって、tarファイルになっています。

512バイトのブロックは、以下の種類があります。

上記のブロックは以下のように配置されます。

+------------------------+
| directory header block |
+------------------------+
| header block 1         |
+------------------------+
| file content block 1-1 |
+------------------------+
| file content block 1-2 |
+------------------------+
| header block 2         |
+------------------------+
| file content block 2-1 |
+------------------------+
| file content block 2-2 |
+------------------------+
| end of tar block 1     |
+------------------------+
| end of tar block 2     |
+------------------------+

1は、実際にtarに格納されるファイルまたはディレクトリの所有者やサイズなどの情報を格納します。2は、実際のファイルが格納されています。1と2が合わさって一つのtarエントリーを構成します。

1は、それ自体の妥当性を検証するたにチェックサムフィールドを持っています。このチェックサムは、1の全ての値を足した数字です。この時にチェックサムフィールドそれ自体の値は、全て空白文字が入っていると仮定して計算されます。また、1に格納される数字の基数は8進数となっています。

2のaは0個以上のブロックで構成されます。このブロックは、格納されるファイルの大きさが512の倍数にならない場合、ファイルの大きさが丁度格納できる、最小の512の倍数の数のブロックを必要とします。例えば、513バイトのファイルを格納しようとするなら、2つの512バイトのブロックが必要になります。このブロック数は、以下の式で求められます。

( (size) + 511 ) / 512

2のbは、0個のブロックで構成されます。これは、エントリーを構成する1にディレクトリ名やパーミッション、所有者などが記録されているので、特に何も格納する必要がないためです。

2のcは長いファイル名を格納するためのブロックです。v7 formatでは、ファイル名の最大長は100バイトと決っていました。しかし、GNU formatやposix formatではこの制限を撤廃して事実上無限長の長さのファイル名を扱えるようになりました。このために採用した方法が、実際のファイル名を2のブロックに格納してしまう、というものです。aのファイル名やbのディレクトリ名が100バイト以上だった場合、これを格納する前にcのブロックを格納します。つまり、通常ならば

+----------------------+
| header block         |
+----------------------+
| file content block 1 |
+----------------------+
| file content block 2 |
+----------------------+

となる所を、以下のようになるのです。

+---------------------------+
| long name header block    |
+---------------------------+
| long name content block 1 |
+---------------------------+
| long name content block 2 |
+---------------------------+
| header block              |
+---------------------------+
| file content block 1      |
+---------------------------+
| file content block 2      |
+---------------------------+

cのheaderは、1とフィールドは変わりませんが、nameフィールドには「././@LongLink」という値が格納されて、uid,gidは0が格納されます。そしてsizeフィールドには実際のファイル名の長さが格納されます。詳しくは「C言語でtarを読み書きする」または「Gaucheでtarを読み書きする」のコードを参照してください。

a、b、cを区別するには、1のtypeflagの値で判別します。ここの値が「0(数字の0)」か「"0"(文字列の0)」であるなら通常のファイルで、「5(数字の5)」か「"D"」であるならば、ディレクトリになります。longnameの場合は、ここの値が「"L"」になっています。

3は、tarファイルの終わりを表わすブロックです。このブロックは少し特殊で、512バイト全てが0で埋められたブロックを二つ連続で配置します。[3], [4]

フォーマットの判別には、ヘッダのmagicとversionフィールドを使用します。表1「magicとversionフィールドの値」がフォーマットとその値です。少し分かりにくいですが、ustar/pax/posixは、magicの値が「ustar0(数字の0)」とversionが「"00"(文字列の0が二つ)」、GNUは、magicの値が「ustar (スペース)」とversionが「 0(スペースと数字の0)」になります。v7の場合はそもそもフォーマットを判別する必要がなかったので、どちらも数字の0になります。


実際にC言語でtarの読み書きを行います。仕様は、tarの読み込みはv7,ustar,posix,GNU,GNU old形式が読み込めます。tarの書き込みはGNU形式となります。また、読み書きのどちらもlong nameをサポートします。

例1 mytar.h

#ifndef __MYTAR_H__
#define __MYTAR_H__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#define __USE_MISC 1
#include <dirent.h>


#define BLOCKSIZE 512
#define TAR_BLOCK_COUNT(SIZE) (((SIZE)+511)/512)
#define OCT2DEC(VALUE) (strtol((VALUE),NULL,8))
#define SET_OCT_VALUE(FIELD,DECVALUE)           \
     { char buf[BUFSIZ] = {'\0'};               \
          sprintf(buf,"%o",(DECVALUE));         \
          strncpy((FIELD),buf,strlen(buf));     \
     }


static char tar_eob[BLOCKSIZE] = { '\0' };

typedef enum _tar_type {
     TAR_UNKNOWN = 0<<0,
     TAR_V7      = 1<<0,
     TAR_PAX     = 1<<1,
     TAR_POSIX   = 1<<2,
     TAR_USTAR   = 1<<3,
     TAR_GNU     = 1<<4,
} tar_type;
     

typedef union _tar_header {
     char header[BLOCKSIZE];
     /* Refer from gnu tar 1.30 source (src/tar.h) */
     struct {                   /* byte offset */
          char name[100];       /*   0 */
          char mode[8];         /* 100 */
          char uid[8];          /* 108 */
          char gid[8];          /* 116 */
          char size[12];        /* 124 */
          char mtime[12];       /* 136 */
          char chksum[8];       /* 148 */
          char typeflag;        /* 156 */
          char linkname[100];   /* 157 */
          char pad[255];        /* 257 */
     } v7_header;
     struct {                   /* byte offset */
          char name[100];       /*   0 */
          char mode[8];         /* 100 */
          char uid[8];          /* 108 */
          char gid[8];          /* 116 */
          char size[12];        /* 124 */
          char mtime[12];       /* 136 */
          char chksum[8];       /* 148 */
          char typeflag;        /* 156 */
          char linkname[100];   /* 157 */
          char magic[6];        /* 257 */
          char version[2];      /* 263 */
          char uname[32];       /* 265 */
          char gname[32];       /* 297 */
          char devmajor[8];     /* 329 */
          char devminor[8];     /* 337 */
          char prefix[155];     /* 345 */
          char pad[12];         /* 500 */
     } posix_header;
     struct {                   /* byte offset */                                   (1)
          char name[100];       /*   0 */
          char mode[8];         /* 100 */
          char uid[8];          /* 108 */
          char gid[8];          /* 116 */
          char size[12];        /* 124 */
          char mtime[12];       /* 136 */
          char chksum[8];       /* 148 */
          char typeflag;        /* 156 */
          char linkname[100];   /* 157 */
          char magic[6];        /* 257 */
          char version[2];      /* 263 */
          char uname[32];       /* 265 */
          char gname[32];       /* 297 */
          char devmajor[8];     /* 329 */
          char devminor[8];     /* 337 */
          char atime[12];       /* 345 Incr. archive: atime of the file */
          char ctime[12];       /* 357 Incr. archive: ctime of the file */
          char offset[12];      /* 369 Multivolume archive: the offset of
                                           the start of this volume */
          char longnames[4];    /* 381 Not used */
          char unused_pad2;     /* 385 */
          char sparse[96];      /* 386 */
          char isextended;      /* 482 Sparse file: Extension sparse header
                                           follows */
          char realsize[12];    /* 483 Sparse file: Real size*/
          char pad[17];         /* 495 */
     } oldgnu_header;
} tar_header;

void dump_header( union _tar_header *header );
int end_of_tar( union _tar_header *header, FILE *fp );
void clear_header( union _tar_header *header );
void skip_data( union _tar_header *header, FILE *fp );
unsigned int calc_chksum( union _tar_header *header );
enum _tar_type get_type( union _tar_header *header );
void tar_type2str( enum _tar_type type );
void set_tar_type( union _tar_header *header, enum _tar_type type );


#endif /* __MYTAR_H__ */

(1)

GNU Tarのソース[2]では、ここは一つの大きな使用しないブロックになっているのですが、ヘッダのアクセスの都合上、フィールドを埋めました。


例2 mytar.c

#include "mytar.h"


void dump_header( union _tar_header *header )
{
     int i = 0;

     printf( "name=[" );
     for( i = 0; i < 100; i++ ) {
          if( header->posix_header.name[i] == '\0' ) {
               break;
          }
          printf( "%c", header->posix_header.name[i] );
     }
     printf( "]\n" );
     printf( "mode=[%s]\n", header->posix_header.mode );
     printf( "uid=[%ld]\n", OCT2DEC( header->posix_header.uid ) );
     printf( "gid=[%ld]\n", OCT2DEC( header->posix_header.gid ) );
     printf( "size=[%ld]\n", OCT2DEC( header->posix_header.size ) );
     printf( "mtime=[%ld]\n", OCT2DEC( header->posix_header.mtime ) );
     printf( "chksum=[%ld]\n", OCT2DEC( header->posix_header.chksum ) );
     printf( "typeflag=[%c]\n", header->posix_header.typeflag );
     printf( "linkname=[%s]\n", header->posix_header.linkname );
     printf( "magic=[%c|%c|%c|%c|%c|%x]\n",
             header->posix_header.magic[0],
             header->posix_header.magic[1],
             header->posix_header.magic[2],
             header->posix_header.magic[3],
             header->posix_header.magic[4],
             header->posix_header.magic[5] );
     printf( "version=[%x|%x]\n",
             header->posix_header.version[0],
             header->posix_header.version[1] );
     printf( "uname=[%s]\n", header->posix_header.uname );
     printf( "gname=[%s]\n", header->posix_header.gname );
     printf( "devmajor=[%s]\n", header->posix_header.devmajor );
     printf( "devminor=[%s]\n", header->posix_header.devminor );

     printf( "\n---gnuold---\n" );
     printf( "atime=[%ld]\n", OCT2DEC( header->oldgnu_header.atime ) );
     printf( "ctime=[%ld]\n", OCT2DEC( header->oldgnu_header.ctime ) );
     printf( "---gnuold---\n\n" );

     printf( "\n---block count---\n" );
     printf( "block count=[%ld]\n", TAR_BLOCK_COUNT( OCT2DEC( header->oldgnu_header.size ) ) );
     printf( "---block count---\n\n" );
}

int end_of_tar( union _tar_header *header, FILE *fp )
{
     if( header->header[0] != '\0' ) {
          return 0;
     }

     if( header->header[0] == '\0' &&
         memcmp( ( void * )header->header, ( void * )tar_eob,
                 BLOCKSIZE ) == 0 ) {
          skip_data( header, fp );
          if( header->header[0] == '\0' &&
              memcmp( ( void * )header->header, ( void * )tar_eob,
                      BLOCKSIZE ) == 0 ) {
               return 1;
          }
     }
     return 0;
}

void clear_header( union _tar_header *header )
{
     memset( header->header, 0, BLOCKSIZE );
}

void skip_data( union _tar_header *header, FILE *fp )
{
     size_t block_count = TAR_BLOCK_COUNT( OCT2DEC( header->v7_header.size ) );

     fseek( fp, block_count * BLOCKSIZE, SEEK_CUR );
}

unsigned int calc_chksum( union _tar_header *header )
{
     int i = 0;
     unsigned int sum = 0;
                                                                                    (1)
     for( i = 0; i < 512; i++ ) {                                                   (1)(2)
          if( i >= 148 && i < 156 ) {                                               (1)(2)
               sum += ( unsigned int )' ';                                          (1)
          } else {                                                                  (1)
               sum += ( unsigned int )header->header[i];                            (1)
          }                                                                         (1)
     }

     return sum;
}

enum _tar_type get_type( union _tar_header *header )
{
     if( memcmp( ( void * )header->posix_header.magic,
                 ( void * )"ustar ", 6 ) == 0 &&
         memcmp( ( void * )header->posix_header.version,
                 ( void * )" \x00", 2 ) == 0 ) {
          return TAR_GNU;
     } else if( memcmp( ( void * )header->posix_header.magic,
                        ( void * )"ustar\x00", 6 ) == 0 &&
                memcmp( ( void * )header->posix_header.version,
                        ( void * )"00", 2 ) == 0 ) {
          return TAR_PAX | TAR_POSIX | TAR_USTAR;
     } else if( header->header[257] == '\0' && header->header[263] == '\0' ) {
          return TAR_V7;
     }

     return TAR_UNKNOWN;
}

void tar_type2str( enum _tar_type type )
{
     if( type & TAR_UNKNOWN ) {
          printf( "Unknown tar format | " );
     }

     if( type & TAR_V7 ) {
          printf( "V7 tar format | " );
     }

     if( type & TAR_PAX ) {
          printf( "PAX tar format | " );
     }

     if( type & TAR_POSIX ) {
          printf( "POSIX tar format | " );
     }

     if( type & TAR_USTAR ) {
          printf( "USTAR tar format | " );
     }

     if( type & TAR_GNU ) {
          printf( "GNU tar format | " );
     }
     printf( "\n" );
}

void set_tar_type( union _tar_header *header, enum _tar_type type )
{
     switch( type ) {
     case TAR_GNU:
          memcpy( ( void * )header->posix_header.magic,
                  ( void * )"ustar ", 6 );
          memcpy( ( void * )header->posix_header.version,
                  " \x00", 2 );
          break;

     case TAR_POSIX:
          memcpy( ( void * )header->posix_header.magic,
                  ( void * )"ustar\x00", 6 );
          memcpy( ( void * )header->posix_header.version,
                  "00", 2 );
          break;
     }
}



#if 0
int main( int argc, char **argv )
{
     return 0;
}
#endif /* #if 0 */

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

(1)

フィールドの値を全て足したものがヘッダのチェックサムになります。

(2)

チェックサムフィールドの値はスペースが入っているものとして計算します。



例4 readtar.c


#include "readtar.h"

void read_header( union _tar_header *header, FILE *fp )
{
     fread( ( void * )header, BLOCKSIZE, 1, fp );
}

void extract_data( union _tar_header *header, FILE *fp, char *longname )
{
     char   *name        = header->v7_header.name;
     size_t realsize     = OCT2DEC( header->v7_header.size );
     size_t  block_count = TAR_BLOCK_COUNT( realsize );
     size_t  i           = 0;
     FILE   *fout        = NULL;
     int     remain      = 0;
     char buf[BLOCKSIZE] = { '\0' };

     if( realsize == 0 ) {
          return;
     }

     if( longname != NULL ) {
          name = longname;
     }

     remain = 512 - ( block_count * BLOCKSIZE - realsize );

     fout = fopen( name, "wb" );
     if( !fout ) {
          fprintf( stderr, "Can't open %s.\n", name );
          return;
     }

     for( i = 0; i < block_count - 1; i++ ) {
          fread( ( void * )buf, 1, BLOCKSIZE, fp );
          fwrite( ( void * )buf, 1, BLOCKSIZE, fout );
     }

     fread( ( void * )buf, 1, BLOCKSIZE, fp );
     fwrite( ( void * )buf, 1, remain, fout );

     fclose( fout );
}

void make_directory( union _tar_header *header, char *longname )
{
     char *name = header->v7_header.name;
     mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR |  
          S_IRGRP | S_IWGRP | S_IXGRP |   
          S_IROTH | S_IXOTH | S_IXOTH;

     if( longname != NULL ) {
          name = longname;
     }

     mkdir( name, mode );
}

char *get_longname( union _tar_header *header, FILE *fp )
{
     size_t realsize = OCT2DEC( header->v7_header.size );
     size_t  block_count = TAR_BLOCK_COUNT( realsize );
     char *buf = NULL;
     char *longname = NULL;

     buf = ( char * )malloc( sizeof( char ) * ( block_count * BLOCKSIZE ) );
     memset( buf, 0, ( block_count * BLOCKSIZE ) );
     longname = ( char * )malloc( sizeof( char ) * realsize );
     memset( longname, 0, realsize );

     fread( ( void * )buf, 1, ( block_count * BLOCKSIZE ), fp );
     strncpy( longname, buf, realsize );

     free( buf );
     return longname;
}

int main( int argc, char **argv )
{
     union _tar_header  header;
     FILE              *fp       = NULL;
     char               typeflag = '\0';
     char              *longname = NULL;
     enum _tar_type     type;

     fp = fopen( argv[1], "rb" );
     if( !fp ) {
          fprintf( stderr, "Can't open %s.\n", argv[1] );
          exit( 1 );
     }

     for( ;; ) {
          clear_header( &header );
          read_header( &header, fp );
          if( end_of_tar( &header, fp ) == 1 ) {
               printf( "End of tar.\n" );
               break;
          }
          dump_header( &header );
          printf( "chksum = [%d]\n", calc_chksum( &header ) );
          type = get_type( &header );
          tar_type2str( type );
          /* skip_data( &header, fp ); */

          typeflag = header.v7_header.typeflag;
          switch( ( int )typeflag ) {
          case '0':
          case '\0':
               printf( "type = regular file.\n" );
               extract_data( &header, fp, longname );
               if( longname ) {
                    free( longname );
                    longname = NULL;
               }
               
               /* skip_data( &header, fp ); */
               break;
          
          case '5':
          case 'D':
               printf( "type = directory.\n" );
               make_directory( &header, longname );
               skip_data( &header, fp );
               if( longname ) {
                    free( longname );
                    longname = NULL;
               }
               
               break;
          
          case 'L':
               printf( "type = long name.\n" );
               longname = get_longname( &header, fp );
               printf( "longname = [%s].\n", longname );
               break;
          
          default:
               printf( "Not support yet or unknown type.\n" );
               skip_data( &header, fp );
               break;
          }

          printf( "\n" );
     }

     fclose( fp );
     
     return 0;
}


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


例6 writetar.c

#include "writetar.h"

void set_file_info( union _tar_header *header, char *src )
{
     struct stat    s;
     struct passwd *passwd = NULL;
     struct group  *grp    = NULL;

     stat( src, &s );

     strncpy( header->posix_header.name, src, 100 );
     SET_OCT_VALUE( header->posix_header.mode, s.st_mode );
     SET_OCT_VALUE( header->posix_header.uid, s.st_uid );
     SET_OCT_VALUE( header->posix_header.gid, s.st_gid );
     SET_OCT_VALUE( header->posix_header.size, s.st_size );
     SET_OCT_VALUE( header->posix_header.mtime, s.st_mtime );
     header->posix_header.typeflag = '0';

     passwd = getpwuid( s.st_uid );
     strncpy( header->posix_header.uname, passwd->pw_name, 32 );

     grp = getgrgid( s.st_gid );
     strncpy( header->posix_header.gname, grp->gr_name, 32 );

     SET_OCT_VALUE( header->posix_header.chksum, calc_chksum( header ) );
}

void set_directory_info( union _tar_header *header, char *src )
{
     struct stat    s;
     struct passwd *passwd = NULL;
     struct group  *grp    = NULL;

     stat( src, &s );

     strncpy( header->posix_header.name, src, 100 );
     SET_OCT_VALUE( header->posix_header.mode, s.st_mode );
     SET_OCT_VALUE( header->posix_header.uid, s.st_uid );
     SET_OCT_VALUE( header->posix_header.gid, s.st_gid );
     SET_OCT_VALUE( header->posix_header.size, 0 );
     SET_OCT_VALUE( header->posix_header.mtime, s.st_mtime );
     header->posix_header.typeflag = '5';

     passwd = getpwuid( s.st_uid );
     strncpy( header->posix_header.uname, passwd->pw_name, 32 );

     grp = getgrgid( s.st_gid );
     strncpy( header->posix_header.gname, grp->gr_name, 32 );

     SET_OCT_VALUE( header->posix_header.chksum, calc_chksum( header ) );
}

void write_tar_eob( FILE *fp )
{
     fwrite( ( void * )tar_eob, 1, BLOCKSIZE, fp );
     fwrite( ( void * )tar_eob, 1, BLOCKSIZE, fp );
}

void write_tar_header( union _tar_header *header, FILE *fp )
{
     fwrite( ( void * )header, 1, BLOCKSIZE, fp );
}

void write_tar_file_content( union _tar_header *header, char *src, FILE *fp )
{
     size_t  block_count = 0;
     size_t  i           = 0;
     FILE   *fin         = NULL;
     char buf[BLOCKSIZE] = { '\0' };

     clear_header( header );
     set_tar_type( header, TAR_GNU );
     set_file_info( header, src );
     block_count = TAR_BLOCK_COUNT( OCT2DEC( header->posix_header.size ) );

     if( strlen( src ) > 100 ) {
          write_tar_longname_block( src, fp );
     }

     write_tar_header( header, fp );

     fin = fopen( src, "rb" );
     if( !fin ) {
          fprintf( stderr, "Can't open [%s].\n", src );
          return;
     }

     for( i = 0; i < block_count; i++ ) {
          fread( ( void * )buf, 1, BLOCKSIZE, fin );
          fwrite( ( void * )buf, 1, BLOCKSIZE, fp );
          memset( buf, 1, BLOCKSIZE );
     }
}

void write_tar_directory_content( union _tar_header *header, char *src, FILE *fp )
{
     size_t  block_count = 0;
     size_t  i           = 0;
     FILE   *fin         = NULL;

     clear_header( header );
     set_tar_type( header, TAR_GNU );
     set_directory_info( header, src );
     block_count = TAR_BLOCK_COUNT( OCT2DEC( header->posix_header.size ) );

     if( strlen( src ) > 100 ) {
          write_tar_longname_block( src, fp );
     }

     write_tar_header( header, fp );
}

void write_tar_longname_block( char *src, FILE *fp )
{
     union _tar_header  longname_block;
     char              *buf         = NULL;
     size_t             block_count = TAR_BLOCK_COUNT( strlen( src ) );
     size_t             block_size  = block_count * BLOCKSIZE;
     mode_t mode                    = S_IRUSR | S_IWUSR | S_IXUSR |  
          S_IRGRP | S_IWGRP | S_IXGRP |   
          S_IROTH | S_IXOTH | S_IXOTH;

     buf = ( char * )malloc( sizeof( char ) * block_size );
     memset( buf, 0, block_size );
     memcpy( ( void * )buf, ( void * )src, strlen( src ) );

     clear_header( &longname_block );
     set_tar_type( &longname_block, TAR_GNU );
     strncpy( longname_block.posix_header.name, "././@LongLink",
              strlen( "././@LongLink" ) );
     strncpy( longname_block.posix_header.mode, "755", strlen( "755" ) );
     SET_OCT_VALUE( longname_block.posix_header.uid, 0 );
     SET_OCT_VALUE( longname_block.posix_header.gid, 0 );
     SET_OCT_VALUE( longname_block.posix_header.size, strlen( src ) );
     SET_OCT_VALUE( longname_block.posix_header.mtime, 0 );
     longname_block.posix_header.typeflag = 'L';
     strncpy( longname_block.posix_header.uname, "root", strlen( "root" ) );
     strncpy( longname_block.posix_header.gname, "root", strlen( "root" ) );
     SET_OCT_VALUE(longname_block.posix_header.chksum,
                   calc_chksum( &longname_block ) );

     /* 
      * dump_header( &longname_block );
      * printf( "[%s]\n", buf );
      */
     fwrite( ( void * )&longname_block, 1, BLOCKSIZE, fp );
     fwrite( ( void * )buf, 1, block_size, fp );

     free( buf );
}

int exclude_parent( struct dirent *dir )
{
     if( dir->d_name[0] == '.' ) {
          return 0;
     }

     return 1;
}

int sort_alpha( const struct dirent **a, const struct dirent **b )
{
     return strcmp( ( *a )->d_name, ( *b )->d_name );
}

void walk( char *path, FILE *fp )
{
     union _tar_header   header;
     struct dirent     **files = NULL;
     char buf[BUFSIZ]          = { '\0' };
     int                 i     = 0;
     int                 r     = 0;

     r = scandir( path, &files, exclude_parent, sort_alpha );

     if( r == -1 ) {
          fprintf( stderr, "Can't retrieve [%s].\n", path );
          exit( 1 );
     }

     if( path[strlen( path ) - 1] == '/' ) {
          path[strlen( path ) - 1] = '\0';
     }

     write_tar_directory_content( &header, path, fp );
     for( i = 0; i < r; i++ ) {
          if( files[i]->d_type == DT_DIR ) {
               memset( buf, 0, BUFSIZ );
               sprintf( buf, "%s/%s", path, files[i]->d_name );
               walk( buf, fp );
          } else {
               memset( buf, 0, BUFSIZ );
               sprintf( buf, "%s/%s", path, files[i]->d_name );
               write_tar_file_content( &header, buf, fp );
          }
     }
}

int main( int argc, char **argv )
{
     FILE *fp = NULL;

     if( argc < 3 ) {
          fprintf( stderr, "%s tarname dir\n", argv[0] );
          exit( 1 );
     }
     
     fp = fopen( argv[1], "wb" );
     if( !fp ) {
          fprintf( stderr, "Can't open [%s] for C code.\n", argv[1] );
          exit( 1 );
     }

     walk( argv[2], fp );

     write_tar_eob( fp );

     fclose( fp );

     return 0;
}


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

「C言語でtarを読み書きする」で作成したコードをGaucheに移植します。仕様はC言語のものと同一です。

例7 tar.scm

#!/usr/local/gauche/bin/gosh
;; -*- coding: utf-8-unix -*-

(define-module tar
  (use file.util)
  (use gauche.uvector)
  (use gauche.collection)

  (export
   *longname*
   <tar-header-base> <tar-header-v7> <tar-header-posix> <tar-header-gnuold>
   <tar> <tar-reader> <tar-writer>
   calc-chksum tar-type set-tar-type
   clear-tar-header end-of-tar? skip-tar-data read-tar-header
   write-tar-eob write-tar-header write-tar-longname-block
   write-tar-file-content write-tar-directory-content
   open-input-tar close-input-tar call-with-iterator
   extract-tar-data call-with-input-tar
   open-output-tar close-output-tar add-file))

(select-module tar)

(define *longname* #f)
(define *tar-block-size* 512)
(define *tar-eob* (make-uvector <s8vector> *tar-block-size* 0))
(define-macro (tar-block-count size)
  `(x->integer (floor (/ (+ ,size 511) 512))))

(define-macro (get-data cls beg end :optional (is-terminate? #t))
  `(cond
    ((eq? ,is-terminate? #t)
     (s8vector->string (uvector-copy (~ ,cls 'header) ,beg ,end)
                       0 ,(- end beg) 0))
    (else
     (s8vector->string (uvector-copy (~ ,cls 'header) ,beg ,end)
                       0 ,(- end beg)))))
(define-macro (set-data cls data beg :optional (converter identity))
  `(s8vector-copy! (~ ,cls 'header) ,beg
                   (string->s8vector (,converter ,data))))

(define-macro (safe-string->number data radix)
  `(let ((v (gensym)))
     (set! v (string->number ,data ,radix))
     (if (eq? v #f) 0 v)))

(define (safe-substring src start end)
  (let ((true-end end))
    (if (> true-end #0=(string-length src))
      (set! true-end #0#))
    (substring src start true-end)))

(define-class <tar-header-base> ()
  ((header   :init-form (make-uvector <s8vector> *tar-block-size* 0))
   (name     :allocation :virtual
             :slot-ref (^o (get-data o 0 100)) 
             :slot-set! (^(o v) (set-data o v 0 (cut safe-substring <> 0 100))))
   (mode     :allocation :virtual 
             :slot-ref (^o (safe-string->number (get-data o 100 108) 10)) 
             :slot-set! (^(o v) (set-data o v 100 (cut format #f "~o" <>))))
   (uid      :allocation :virtual 
             :slot-ref (^o (safe-string->number (get-data o 108 116) 8)) 
             :slot-set! (^(o v) (set-data o v 108 (cut format #f "~o" <>))))
   (gid      :allocation :virtual 
             :slot-ref (^o (safe-string->number (get-data o 116 124) 8)) 
             :slot-set! (^(o v) (set-data o v 116 (cut format #f "~o" <>))))
   (size     :allocation :virtual 
             :slot-ref (^o (safe-string->number (get-data o 124 136) 8)) 
             :slot-set! (^(o v) (set-data o v 124 (cut format #f "~o" <>))))
   (mtime    :allocation :virtual 
             :slot-ref (^o (safe-string->number (get-data o 136 148) 8)) 
             :slot-set! (^(o v) (set-data o v 136 (cut format #f "~o" <>))))
   (chksum   :allocation :virtual 
             :slot-ref (^o (safe-string->number (get-data o 148 156) 8)) 
             :slot-set! (^(o v) (set-data o v 148 (cut format #f "~o" <>))))
   (typeflag :allocation :virtual 
             :slot-ref (^o (get-data o 156 157 #f)) 
             :slot-set! (^(o v) (set-data o v 156)))
   (linkname :allocation :virtual 
             :slot-ref (^o (get-data o 157 257)) 
             :slot-set! (^(o v) (set-data o v 157)))
   ))

(define-class <tar-header-v7> (<tar-header-base>)
  ((pad :allocation :virtual 
        :slot-ref (^o (get-data o 257 512)))))

(define-class <tar-header-posix> (<tar-header-base>)
  ((magic    :allocation :virtual 
             :slot-ref (^o (get-data o 257 263 #f)) 
             :slot-set! (^(o v) (set-data o v 257)))
   (version  :allocation :virtual 
             :slot-ref (^o (get-data o 263 265 #f)) 
             :slot-set! (^(o v) (set-data o v 263)))
   (uname    :allocation :virtual 
             :slot-ref (^o (get-data o 265 297)) 
             :slot-set! (^(o v) (set-data o v 265 (cut safe-substring <> 0 32))))
   (gname    :allocation :virtual 
             :slot-ref (^o (get-data o 297 329)) 
             :slot-set! (^(o v) (set-data o v 297 (cut safe-substring <> 0 32))))
   (devmajor :allocation :virtual 
             :slot-ref (^o (get-data o 329 337)) 
             :slot-set! (^(o v) (set-data o v 329)))
   (devminor :allocation :virtual 
             :slot-ref (^o (get-data o 337 345)) 
             :slot-set! (^(o v) (set-data o v 337)))
   (prefix   :allocation :virtual 
             :slot-ref (^o (get-data o 345 500)) 
             :slot-set! (^(o v) (set-data o v 345)))
   (pad      :allocation :virtual 
             :slot-ref (^o (get-data o 500 512)) 
             :slot-set! (^(o v) (set-data o v 512)))))


(define-class <tar-header-gnuold> (<tar-header-base>)
  ((magic       :allocation :virtual 
                :slot-ref (^o (get-data o 257 263 #f)) 
                :slot-set! (^(o v) (set-data o v 257)))
   (version     :allocation :virtual 
                :slot-ref (^o (get-data o 263 265 #f)) 
                :slot-set! (^(o v) (set-data o v 263 (cut safe-substring <> 0 32))))
   (uname       :allocation :virtual 
                :slot-ref (^o (get-data o 265 297)) 
                :slot-set! (^(o v) (set-data o v 265 (cut safe-substring <> 0 32))))
   (gname       :allocation :virtual 
                :slot-ref (^o (get-data o 297 329)) 
                :slot-set! (^(o v) (set-data o v 297)))
   (devmajor    :allocation :virtual 
                :slot-ref (^o (get-data o 329 337)) 
                :slot-set! (^(o v) (set-data o v 329)))
   (devminor    :allocation :virtual 
                :slot-ref (^o (get-data o 337 345)) 
                :slot-set! (^(o v) (set-data o v 337)))
   (atime       :allocation :virtual 
                :slot-ref (^o (safe-string->number (get-data o 345 357) 8))
                :slot-set! (^(o v) (set-data o v 345 (cut format #f "~o" <>))))
   (ctime       :allocation :virtual 
                :slot-ref (^o (safe-string->number (get-data o 357 369) 8)) 
                :slot-set! (^(o v) (set-data o v 357 (cut format #f "~o" <>))))
   (offset      :allocation :virtual 
                :slot-ref (^o (get-data o 369 381)) 
                :slot-set! (^(o v) (set-data o v 369)))
   (longnames   :allocation :virtual 
                :slot-ref (^o (get-data o 381 385)) 
                :slot-set! (^(o v) (set-data o v 381)))
   (unused-pad2 :allocation :virtual 
                :slot-ref (^o (get-data o 385 386)) 
                :slot-set! (^(o v) (set-data o v 385)))
   (sparse      :allocation :virtual 
                :slot-ref (^o (get-data o 386 482)) 
                :slot-set! (^(o v) (set-data o v 386)))
   (isextended  :allocation :virtual 
                :slot-ref (^o (get-data o 482 483)) 
                :slot-set! (^(o v) (set-data o v 482)))
   (realsize    :allocation :virtual 
                :slot-ref (^o (get-data o 483 495)) 
                :slot-set! (^(o v) (set-data o v 483)))
   (pad         :allocation :virtual 
                :slot-ref (^o (get-data o 495 512)) 
                :slot-set! (^(o v) (set-data o v 495)))))

(define-class <tar> ()
  ((name :init-form "")
   (ioport :init-form #f)
   (direction :allocation :virtual
              :slot-ref (^o
                         (cond
                          ((eq? (input-port? (~ o 'ioport)) #t)
                           'input)
                          ((eq? (output-port? (~ o 'ioport)) #t)
                           'output)
                          (else 'unknown))))
   (is-closed? :allocation :virtual
               :slot-ref (^o (port-closed? (~ o 'ioport))))))

(define-class <tar-reader> (<tar> <collection>)
  ((skip-data? :init-form #f)))

(define-class <tar-writer> (<tar>)
  ((filelist :init-form '())))

(define (calc-chksum header)
  (let ((total 0))
    (dotimes (i 512)
      (cond
       ((and (>= i 148) (< i 156))
        (set! total (+ total 32))) ;;; space
       (else
        (set! total (+ total (uvector-ref (~ header 'header) i))))))
    total))

(define-method tar-type ((header <tar-header-base>))
  (cond
   ((and (= (uvector-ref #0=(~ header 'header) 257) 0)
         (= (uvector-ref #0# 263) 0))
    'tar-v7)
   ((and (string=? (s8vector->string #0# 257 263) "ustar ")
         (string=? (s8vector->string #0# 263 265) " \0"))
    'tar-gnu) 
   ((and (string=? (s8vector->string #0# 257 263) "ustar\0")
         (string=? (s8vector->string #0# 263 265) "00"))
    'tar-posix)
   (else 'tar-unknown)))

(define-method set-tar-type ((header <tar-header-base>) type)
  (cond
   ((eq? type 'tar-v7)
    (set! (uvector-ref (~ header 'header) 257) 0)
    (set! (uvector-ref (~ header 'header) 263) 0))
   ((eq? type 'tar-posix)
    (set! (~ header 'magic) "ustar ")
    (set! (~ header 'version) " \0"))
   ((eq? type 'tar-gnu)
    (set! (~ header 'magic) "ustar\0")
    (set! (~ header 'version) "00"))))

(define-method clear-tar-header ((header <tar-header-base>))
  (uvector-copy! (~ header 'header) 0 *tar-eob*))

(define-method end-of-tar? ((tar <tar>) (header <tar-header-base>))
  (if (not (= (uvector-ref #0=(~ header 'header) 0) 0))
    #f
    (begin
      (if (and (= (uvector-ref #0# 0) 0)
               (= (s8vector-dot #0# *tar-eob*) 0))
        (let ((next-header #f))
          (skip-tar-data tar header)
          (set! next-header (read-tar-header tar))
          (if (and (= (uvector-ref #1=(~ next-header 'header) 0) 0)
                   (= (s8vector-dot #1# *tar-eob*) 0))
            #t))))))

(define-method skip-tar-data ((tar <tar>) (header <tar-header-base>))
  (let ((block-count (tar-block-count (~ header 'size))))
    (port-seek (~ tar 'ioport) (* block-count *tar-block-size*) SEEK_CUR)
    (set! (~ tar 'skip-data?) #f)))

(define-method read-tar-header ((tar <tar>) :optional (read-tar-type #f))
  (let ((header (make <tar-header-posix>))
        (type (or read-tar-type 'tar-unknown)))
    (set! (~ tar 'skip-data?) #t)
    (set! (~ header 'header)
          (read-uvector <s8vector> *tar-block-size* (~ tar 'ioport)))
    (set! type (tar-type header))
    (cond
     ((eq? type 'tar-gnu)
      (let ((new-header (make <tar-header-gnuold>)))
        (uvector-copy! (~ new-header 'header) 0 (~ header 'header))
        new-header))
     ((eq? type 'tar-v7)
      (let ((new-header (make <tar-header-v7>)))
        (uvector-copy! (~ new-header 'header) 0 (~ header 'header))
        new-header))
     (else header))))

(define-method make-tar-directory ((tar <tar>) (header <tar-header-base>) :optional (longname #f))
  (let ((name (or longname (~ header 'name))))
    (make-directory* name)
    (set! (~ tar 'skip-data?) #t)))

(define-method get-tar-long-name ((tar <tar>) (header <tar-header-base>))
  (let ((block-count (tar-block-count (~ header 'size)))
        (vec #f))
    (set! vec (read-uvector <s8vector> #0=(* block-count *tar-block-size*)
                            (~ tar 'ioport)))
    (begin0 
        (s8vector->string vec 0 #0# 0)
      (set! (~ tar 'skip-data?) #f))))

(define-method _extract-tar-data ((tar <tar>) (header <tar-header-base>) :optional (longname #f) (output #f) (closer close-output-port))
  (let* ((name (or longname (~ header 'name)))
         (block-count (tar-block-count #0=(~ header 'size)))
         (remain 0)
         (buf #f)
         (out (or output (open-output-file name))))
    (if (not (= #0# 0))
      (begin
        (set! remain (- 512 (- (* block-count *tar-block-size*) #0#)))
        (unwind-protect
         (begin 
           (dotimes (i (- block-count 1))
             (begin
               (set! buf (read-uvector <s8vector> *tar-block-size* (~ tar 'ioport)))
               (write-uvector buf out 0 *tar-block-size*)
               (uvector-copy! buf 0 *tar-eob*)))
           (begin
             (if (not (eq? buf #f)) (uvector-copy! buf 0 *tar-eob*))
             (set! buf (read-uvector <s8vector> *tar-block-size* (~ tar 'ioport)))
             (write-uvector buf out 0 remain)))
         (closer out))))
    (set! (~ tar 'skip-data?) #f)))

(define-method set-tar-file-info ((tar <tar>) (header <tar-header-base>) src)
  (set! (~ header 'name) src)
  (set! (~ header 'mode) (file-mode src))
  (set! (~ header 'uid) (file-uid src))
  (set! (~ header 'gid) (file-gid src))
  (set! (~ header 'size) (file-size src))
  (set! (~ header 'mtime) (file-mtime src))
  (set! (~ header 'typeflag) "0")
  (set! (~ header 'uname) (sys-uid->user-name (file-uid src)))
  (set! (~ header 'gname) (sys-gid->group-name (file-gid src)))
  (set! (~ header 'chksum) (calc-chksum header)))

(define-method set-tar-directory-info ((tar <tar>) (header <tar-header-base>) src)
  (set! (~ header 'name) src)
  (set! (~ header 'mode) (file-mode src))
  (set! (~ header 'uid) (file-uid src))
  (set! (~ header 'gid) (file-gid src))
  (set! (~ header 'size) 0)
  (set! (~ header 'mtime) (file-mtime src))
  (set! (~ header 'typeflag) "5")
  (set! (~ header 'uname) (sys-uid->user-name (file-uid src)))
  (set! (~ header 'gname) (sys-gid->group-name (file-gid src)))
  (set! (~ header 'chksum) (calc-chksum header)))

(define-method write-tar-eob ((tar <tar>) (header <tar-header-base>))
  (dotimes (i 2)
    (write-uvector *tar-eob* (~ tar 'ioport))))

(define-method write-tar-header ((tar <tar>) (header <tar-header-base>))
  (write-uvector (~ header 'header) (~ tar 'ioport)))

(define-method write-tar-longname-block ((tar <tar>) src)
  (let* ((longname-header (make <tar-header-posix>))
         (block-count (tar-block-count (string-length src)))
         (buf (make-uvector <s8vector> (* block-count *tar-block-size*) 0)))
    (clear-tar-header #0=longname-header)
    (set-tar-type #0# 'tar-gnu)
    (set! (~ #0# 'name) "././@LongLink")
    (set! (~ #0# 'mode) (string->number "755" 8))
    (set! (~ #0# 'uid) 0)
    (set! (~ #0# 'gid) 0)
    (set! (~ #0# 'size) (string-length src))
    (set! (~ #0# 'mtime) 0)
    (set! (~ #0# 'typeflag) "L")
    (set! (~ #0# 'uname) "root")
    (set! (~ #0# 'gname) "root")
    (set! (~ #0# 'chksum) (calc-chksum #0#))

    (write-uvector (~ #0# 'header) (~ tar 'ioport) 0 *tar-block-size*)
    (uvector-copy! buf 0 (string->u8vector src))
    (write-uvector buf (~ tar 'ioport) 0 *tar-block-size*)))

(define-method write-tar-file-content ((tar <tar>) (header <tar-header-base>) src)
  (let ((block-count 0)
        (buf (make-uvector <s8vector> *tar-block-size* 0)))
    (clear-tar-header header)
    (set-tar-type header 'tar-gnu)
    (set-tar-file-info tar header src)
    (set! block-count (tar-block-count (~ header 'size)))
    (if (> (string-length src) 100)
      (write-tar-longname-block tar src))
    (write-tar-header tar header)
    (call-with-input-file src
      (^(in)
        (dotimes (i block-count)
          (uvector-copy! buf 0 (read-uvector <s8vector> *tar-block-size* in))
          (write-uvector buf (~ tar 'ioport) 0 *tar-block-size*)
          (uvector-copy! buf 0 *tar-eob*))))))

(define-method write-tar-directory-content ((tar <tar>) (header <tar-header-base>) src)
  (let ((block-count 0)
        (buf #f))
    (clear-tar-header header)
    (set-tar-type header 'tar-gnu)
    (set-tar-directory-info tar header src)
    (set! block-count (tar-block-count (~ header 'size)))
    (if (> (string-length src) 100)
      (write-tar-longname-block tar src))
    (write-tar-header tar header)))

(define (open-input-tar src)
  (let ((tar (make <tar-reader>)))
    (set! (~ tar 'name) src)
    (set! (~ tar 'ioport) (open-input-file src))
    tar))

(define-method close-input-tar ((tar <tar-reader>))
  (close-input-port (~ tar 'ioport))
  (set! (~ tar 'name) ""))

(define-method call-with-iterator ((tar <tar-reader>) proc . opts)
  (let ((tar-header #f))
    (proc 
     (lambda ()
       (if (and (eq? (~ tar 'skip-data?) #t)
                (not (eq? tar-header #f)))
         (begin
           (skip-tar-data tar tar-header)
           (set! (~ tar 'skip-data?) #t)))
       (set! tar-header (read-tar-header tar))
       (end-of-tar? tar tar-header))
     (lambda () tar-header))))

(define-method extract-tar-data ((tar <tar>) (header <tar-header-base>) :optional (output #f) (closer close-output-port) (create-directory? #t))
  (let ((flag (~ header 'typeflag)))
    (cond
     ((or (string=? "0" flag)
          (string=? "\0" flag))
      (_extract-tar-data tar header *longname* output closer)
      (set! *longname* #f))
     ((or (string=? "5" flag)
          (string=? "D" flag))
      (if (eq? create-directory? #t)
        (make-tar-directory tar header *longname*))
      (set! *longname* #f))
     ((string=? "L" flag)
      (set! *longname* (get-tar-long-name tar header)))
     (else
      (format #t "unknown type or not implement yet.\n")))))

(define (call-with-input-tar src proc)
  (let ((tar-reader (open-input-tar src)))
    (unwind-protect
     (proc tar-reader)
     (close-input-tar tar-reader))))

(define (open-output-tar src)
  (let ((tar (make <tar-writer>)))
    (set! (~ tar 'name) src)
    tar))

(define-method close-output-tar ((tar <tar-writer>))
  (set! (~ tar 'ioport) (open-output-file (~ tar 'name)))
  (unwind-protect
   (let ((tar-header (make <tar-header-posix>)))
     (dolist (file (reverse (~ tar 'filelist)))
       (cond
        ((eq? (file-is-regular? file) #t)
         (write-tar-file-content tar tar-header file))
        ((eq? (file-is-directory? file) #t)
         (write-tar-directory-content tar tar-header file))))
     (write-tar-eob tar tar-header))
   (close-output-port (~ tar 'ioport))))

(define-method add-file ((tar <tar-writer>) src)
  (set! #0=(~ tar 'filelist) (append (list src) #0#)))


tarを読み書きするコードを作成してみての感想として、読み書きを行うだけならそれほど難しいものではないという事が分かりました。ただ、tarの膨大な仕様を全てカバーするとなると、それなりの労力はかかるという事も分かりました。この文章が何かの役に立てば幸いです。