TAR(Tape ARchive) 아카이브 파일은 1970년대 자기테이프에 백업 & 검색하기
위하여 유래되었다고 합니다만 지금은 주로 여러 개의 파일을 묶어 전송하기
위하여 사용됩니다. 기본적으로 TAR 아카이브는 여러 개의 파일을 전혀
압축하지 않은 상태로 단지 하나의 파일로 묶어주는 역할만을 합니다.
우리가 보통 유닉스에서 제공하는 tar 유틸리티를 이용할 때 z 옵션을 지정하여
주면 압축까지 하는 것을 볼 수 있습니다. 그러나 이는 tar 유틸리티에서
압축해 주는 것이 아니라 tar 유틸리티와는 별개로 작성되어 있는
gzip 유틸리티를 불러다가 압축하게 되지요.
PHP에서의 상황을 보면 zlib 함수(gz로 시작하는 함수들)를 통해 파일을
압축할 수 있습니다. 그러나 zlib 함수는 기본적으로 하나의 파일만 다룰 수
있습니다. 웹상에서 여러 파일을 압축하고 해제하는 것이 다소 위험하여
그런지는 알 수 없으나 웹 스크립트에서는 TAR 아카이브를 잘 다루지 않는 것
같습니다. 생각해 보면 위험하기도 하겠습니다. 무슨 파일이 포함되었는지도
모르는 압축파일을 서버에 올려서 풀어버린다면 그 위험성은 상상하고도
남겠지요. 또한 내가 아닌 누군가가 나의 웹서버의 내용을 통째로 압축하여
가져가 버린다면...... 소름끼치는 상상이 될 수도 있겠지요. 그러나 구더기
무서워 장 못담그겠습니까? 서버상의 여러 개의 파일을 하나로 묶어서
다루어야 할 경우는 너무 많기때문에 TAR 아카이브를 다룰 수 있는 함수는
꼭 있어야 겠지요. 그리고 TAR 아카이브를 다룰 때 발생할 수 있는 여러가지
위험성을 충분히 고려하여 사용하기만 한다면 별(?) 문제 없을 것입니다.
현재 PHP에서 일반적으로 웹상에서 여러개의 파일을 압축하기 위해서는
exec() 또는 system()와 같은 함수와 리눅스에서 제공하는 tar 유틸리티를
이용하게 됩니다. 그러나 이러한 방법은 서버 상황에 따라 때로는 제대로
동작되지 않을 경우도 있으며 또한 윈도우 서버의 경우에는 해결방법이
될 수가 없습니다. 결국 가능한한 서버 환경에 관계없이 웹상에서
TAR 아카이브를 다루기 위해서는 TAR 아카이브를 직접 처리하는 것이
좋을 것 같습니다.
본인도 PHP에서 다중파일 다운로드 클래스를 만들다보니 여러 개의 파일을
다운로드하기 위해서는 결국 여러 개의 파일을 하나의 파일로 묶은 후에
이 파일을 다운로드 받은 것이 가장 좋겠다는 생각은 되었으나 현재 공개된
해결책으로는 위에서 언급한 리눅스의 tar 유틸리티를 이용하는 방법 외에는
눈에 띄는 것이 없었습니다. 그래서 인터넷을 검색하기 시작하였고
이를 통해 C로 작성된 tar 유틸리티의 소스 및 TAR 아카이브 구조에 관한
기술자료를 얻을 수 있었습니다. 이러한 자료를 통해 리눅스의 tar 유틸리티와
같은 기능을 수행하는 TAR 아카이브 클래스를 작성하게 되었으며
이를 공개하려고 준비하는 중에 PHPSCHOOL.COM 친구 여러분들도 꼭 다운로드가
아니더라도 다중 파일 백업과 같은 기능을 웹에서 구현하려고 하는 분들이
많이 있을 것 같아 TAR 아카이브를 직접 다룰 수 있는 기술 자료를 정리하여
올리게 되었습니다. 이 자료만 잘 습득하시면 여러분도 TAR 아카이브를
다루는 함수를 작성하는데 별 어려움이 없을 것으로 보입니다.
그러면 본론으로 들어가서 아래의 내용들에 대하여 하나씩 살펴보겠습니다.
◆ TAR 아카이브 구조
◆ 헤더 구조
◆ 본문 구조
◆ TAR 아카이브 후미에 붙여지는 블록
◆ TAR 아카이브 관련 PHP 소스
1. TAR 아카이브 구조
TAR 아카이브는 512바이트를 1블록으로 하여 다루게 됩니다. 따라서 TAR
아카이브의 크기를 보면 512 * n 바이트임을 알 수 있습니다. TAR 아카이브는
여러 개의 파일이 하나로 묶여있으므로 각 멤버파일마다 본문 내용에 앞서
파일에 대한 정보를 기록하게 되는 1블록의 헤더가 반드시 나타나며 그 뒤를
이어 n블록의 본문 내용이 나타납니다. 때에 따라서는 하나의 디렉토리에 있는
파일뿐만 아니라 다른 디렉토리에 있는 파일들도 하나의 TAR 아카이브에 묶을
때도 있을 것입니다. 이러한 경우에는 디렉토리에 대한 정보도 기록하여야
하는데 이 경우에도 파일 헤더와 같이 1블록의 디렉토리 헤더를 가지게 됩니다.
디렉토리의 경우에는 파일과는 달리 본문 내용이 없으므로 각 디렉토리마다
1블록의 디렉토리 헤더만이 존재합니다. 파일 및 디렉토리에 관한
모든 정보(헤더 및 본문)가 다 기록된 후에는 마지막으로 아스키 0으로 채워진
1블록 이상의 빈블록(empty block)들이 붙여지게 됩니다.
이젠 몇가지 실예를 들어 전체 구조가 어떻게 구성되는지 살펴보겠습니다.
1) 동일한 디렉토리에 있는 파일들(디렉토리 정보를 기록하지 않을 때)
s1.txt
s2.txt
s3.txt
s1.txt, s2.txt, s3.txt 파일이 모두 동일한 디렉토리에 존재하며 이를 묶을
때
디렉토리 정보를 전혀 기록하지 않는다면 TAR 아카이브의 전체 구조는 아래와
같을 것입니다.
+--------------------+
| s1.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| 빈블록 | n 블록(512*n 바이트)
+--------------------+
2) 동일한 디렉토리에 있는 파일들(디렉토리 정보를 기록할 때)
sub/s1.txt
sub/s2.txt
sub/s3.txt
s1.txt, s2.txt, s3.txt 파일이 모두 동일한 디렉토리 sub에 존재하며 이를
묶을 때 디렉토리 정보 sub를 기록한다면 TAR 아카이브의 전체 구조는 아래와
같을 것입니다.
+--------------------+
| sub 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| 빈블록 | n 블록(512*n 바이트)
+--------------------+
3) 여러 개의 디렉토리에 있는 파일들
s1.txt
s2.txt
sub1/s3.txt
sub1/sub2/s4.txt
sub1/sub2/s5.txt
sub1/sub2/sub3/s6.txt
sub4/s7.txt
s1.txt, s2.txt, s3.txt 파일이 여러 개의 디렉토리에 분산되어 존재하며 이를
묶을 때 이들 디렉토리 정보를 함께 기록한다면 TAR 아카이브의 전체 구조는
아래와 같을 것입니다.
+--------------------+
| s1.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub1 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub2 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s4.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s4.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s5.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s5.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub3 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s6.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s6.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub4 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s7.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s7.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| 빈블록 | n 블록(512*n 바이트)
+--------------------+
2. 헤더 구조
TAR 아카이브의 포맷을 보면 이전의 유닉스 호환 포맷(UNIX-compatible formats)과
POSIX (IEEE P1003.1) 기준으로 새로이 정의된 USTAR 포맷(USTAR format)이
있습니다. 새로이 정의된 USTAR 포맷은 유닉스 호환 포맷보다 더 많은 정보를
저장할 수 있으며 더 긴 파일 패스명을 지원합니다. 그러나 어느 포맷이
되었든지 512바이트 크기의 1블록으로 구성되어 있습니다.
우선 이전의 유닉스 호환 포맷에 의한 헤더 구조를 보면 아래와 같습니다.
----------+------+------+-------------------------
FIELD NAME OFFSET SIZE MEANING
----------+------+------+-------------------------
name 0 100 name of file
mode 100 8 file mode
uid 108 8 owner user ID
gid 116 8 owner group ID
size 124 12 length of file in bytes
mtime 136 12 modify time of file
chksum 148 8 checksum for header
link 156 1 indicator for links
linkname 157 100 name of linked file
......
----------+------+------+-------------------------
link 필드에는 링크 파일(linked file)이면 1, 심볼릭 링크(symbolic link)
이면 2, 기타일 때는 0이 기록됩니다. 디렉토리의 경우에는 링크명에
슬래시(/)가 붙게되지요.
새로운 USTAR 포맷에 의한 헤더 구조는 아래와 같습니다. magic 필드에는
null 문자로 종료되는 문자열 "ustra"이 기록되며 magic 필드 이전의
모든 필드는 typeflag 필드를 제외하고는 모두 이전의 유닉스 호환 포맷과
동일합니다. 유닉스 호환 포맷에서의 link 필드가 USTAR 포맷에서는
typeflag 필드로 변경되었습니다.
----------+------+------+-------------------------
FIELD NAME OFFSET SIZE MEANING
----------+------+------+-------------------------
name 0 100 name of file
mode 100 8 file mode
uid 108 8 owner user ID
gid 116 8 owner group ID
size 124 12 length of file in bytes
mtime 136 12 modify time of file
chksum 148 8 checksum for header
typeflag 156 1 type of file
linkname 157 100 name of linked file
magic 257 6 USTAR indicator
varsion 263 2 USTAR version
uname 265 32 owner user name
gname 297 32 owner group name
devmajor 329 8 device major number
devminor 337 8 device minor number
prefix 345 155 prefix for file name
......
----------+------+------+-------------------------
이와 같이 어느 포맷이 되었든지 파일명, 파일크기 등 파일 정보에 관한
내용이 저장되는 헤더는 1블록(512바이트)로 구성되어 있습니다.
이 문서에서는 USTAR 포맷을 중심으로 살펴보겠습니다.
TAR 아카이브를 다룰 때 name 필드부터 typeflag 필드까지만 정확히 다루게
된다면 윈도우를 포함한 어느 시스템에서나 정상적으로 동작하는 것
같습니다. 즉 여러분이 PHP를 이용하여 작성된 TAR 아카이브를 가지고
유닉스 또는 리눅스의 tar 유틸리티를 이용하여 풀 수 있으며, 또한
윈도우에서 알집이나 window commander에서 풀 수도 있습니다.
테이블에 나타난 바와 같이 각 필드마다 그 위치와 크기가 고정되어 있습니다.
magic, uname, gname 필드는 마지막 코드는 반드시 null(아스키문자 0)이
나타나는 null 종료 문자열(null-terminated character strings)입니다.
name, linkname, prefix 필드는 마지막 코드가 반드시 null일 필요는 없으나
정해진 필드크기가 다 채워지지 않았다면 이 공간을 반드시 null로 채워야
합니다. name 필드의 예를 들어보면 만약 파일명이 "s1.txt"라면 name 필드에
기록될 때는 "s1.txt" . str_repeat(chr(0), 100 - strlen("s1.txt")) 에서
얻은 값으로 기록됩니다.
USTAR 포맷에서 uname, gname 필드는 각각 로그인 사용자명과 그룹 사용자명을
기록합니다.
mode, uid, gid, chksum, devmajor, devminor 필드의 마지막 코드는 반드시
null이 기록되어야 하나
size, mtime, version 필드는 반드시 null로 종료할 필요는 없으나
일반적으로 null이 기록되는 것 같습니다. 따라서 숫자를 나타내는 이들
필드는 모두 null로 종료한다고 생각하시면 될 것입니다. 이와 같이 숫자를
나타내는 필드들은 모두 null로 종료되므로 실제로 숫자가 기록되는 장소의
크기는 테이블에 정하여진 SIZE-1이 됩니다. 예를 들어 uid의 경우를 보면
필드 크기는 8바이트이지만 실제로 OFFSET 108부터 114까지의 7바이트에만
기록되며 마지막 OFFSET 115에는 null이가 기록됩니다. 숫자 필드에서
한가지 주의할 것은 기록되는 숫자는 10진수가 아니라 8진수로 기록된다는
것이지요. 그리고 기록되는 숫자의 자리수가 필드 크기보다 작을 때는
앞쪽으로 남는 자리수만큼 "0"으로 채워지게 됩니다. 만약 uid 값이 8이라면
uid 필드에는 "0000008"이 기록됩니다.
어떤 필드가 되었든지 헤더에서는 사용되지 않는 공간은 모두 null로
채우도록 되어있습니다. 이제는 각 필드별로 다른 특징을 살펴보지요.
"name" 필드는 파일명 또는 디렉토리명이 기록되는 곳으로 총 100자까지
기록될 수 있습니다. 파일명이 100자가 되지 않아 비워있는 곳은 모두
null 값으로 채워집니다. 여기에 기록되는 파일명에는 패스명이 포함됩니다.
USTAR 포맷의 경우를 보면 prefix 필드의 값이 null이 아니라면 100자가
넘는 파일명이 가능하도록 name 필드 앞에 붙게 됩니다. 그러나 이전의
유닉스 호환 포맷이나 윈도우 시스템에서 TAR 아카이브를 만들 수 있는
윈도우커맨더 등과의 호환성을 생각한다면 prefix 필드를 null로 남겨두는
것이 좋을 듯하며 100자가 넘는 파일명인 경우 뒷쪽을 짤라내어
100자까지만 name 필드에 저장하는 것이 좋을 듯합니다.
USTAR 포맷에서만 사용하고 있는 typeflag 필드는 이전의 유닉스 호환
포맷의 link 필드와 호환성을 유지한 채 확장시킨 것입니다. 아래는
typeflag 필드에 기록될 수 있는 값들이며 이 중에 0, 1, 2까지는
이전의 유닉스 호환 포맷과 호환성을 유지합니다.
----------+--------------------------------------
TYPE FLAG FILE TYPE
----------+--------------------------------------
0 or null Regular file
1 Link to another file already archived
2 Symbolic link
3 Character special device
4 Block special device
5 Directory
6 FIFO special file
7 Reserved
A-Z Available for custom usage
----------+--------------------------------------
"typeflag"는 파일 형식을 나타내는 필드로 디렉토리일 때는 "5"가
기록되면 파일일 경우에는 "0"이 기록됩니다.
"mode" 필드는 파일 모드가 기록되는 곳으로 8진수로 기록됩니다. 예를
들면 0755, 0644와 같지요. "mode" 필드에서 사용할 수 있는 크기는
총 7자입니다. 이 경우 보통 "0000755"와 같이 남는 공간은 앞쪽에
"0"으로 채우게 됩니다. 그러나 이러한 기록 방법은 프로그램에 따라
약간씩 차이가 나기도 하네요. 윈도우 유틸리티인 windows commander에서
TAR 아카이브를 만들게 되면 "000755 " 또는 "000644 "와 같이 6자만
8진수로 만들어 주고 7번째 자리에는 " "로 채우게 됩니다. 윈도우에서야
원래 파일 모드라는 개념이 없으니까 관계가 없다고 생각할지 모르겠으나
윈도우에서 압축한 TAR 아카이브를 리눅스에서 풀 때는 생각한다면 이를
적절히 처리하는 것이 좋을 것 같습니다.
"uid"와 "gid"도 "mode"와 같이 8진수로 기록되며 총 7자리를 만들게
됩니다. 빈 공간은 앞쪽에 "0"을 채우게 되지요.
"size"는 바이트 단위의 파일 크기를 나타내며 8진수로 기록됩니다.
"mtime"는 파일을 수정한 시간을 나타내며 이에 해당하는 php 함수는
filemtime()입니다.
"magic" 필드는 압축 알고리즘의 이름과 버전을 나타내며 보통 "ustar"
또는 "gnutar" 등이 기록됩니다. 8자가 안되는 부분은 " "로 뒤쪽에
채워지게 됩니다.
"linkname", "uname", "gname", "devmajor", "devminor", "prefix",
"noname" 필드는 모두 chr(0)으로 채워넣습니다.
위와 같이 "chksum" 필드를 제외한 필드를 모두 기록한 후에 마지막으로
"chksum" 필드를 기록합니다. "chksum" 필드는 묶여진 파일이 정상적인가를
확인하는데 이용하는 체크섬 필드입니다. 8진수로 기록되며 채워지지 않는
부분은 앞쪽으로 "0"으로 채우게 됩니다. 체크섬 값은 아래와 같은
방법으로 구하게 됩니다. 이 때 변수 $HEADER에는 "chksum" 필드를 제외한
필드가 위에서 기술한 방법대로 먼저 기록되어 있다고 가정합니다.
function get_checksum() {
$sum = 0;
for ($i=0;$i<512;$i++) {
if ($i < 148 || 156 < $i) {
$sum += ord($HEADER[$i]);
} else {
$sum += ord(" ");
}
}
return $sum;
}
만약 기록하고자 하는 것이 파일이 아니라 디렉토리라면 파일과 같은
내용은 없기 때문에 512바이트의 헤더만 존재합니다. 한번 디렉토리
정보가 기록되면 그 뒤의 파일들은 앞에서 나타난 디렉토리에 속하게
됩니다. 따라서 디렉토리가 변경되는 부분에서만 단 한번 디렉토리 정보가
기록됩니다. 앞에서도 설명하였지만 파일 헤더와 다른 점이라면 "typeflag"
필드값이 "5"라는 것과 "name" 필드에는 파일명 대신에 패스명이
기록됩니다. 예를 들면 "sub1/sub2/"와 같지요. 다른 필드는 파일일 때와
같습니다.
3. 본문 구조
파일 헤더가 완성되었으면 뒤이어 파일 내용이 들어가게 됩니다. 파일
내용은 512 바이트의 배수로 기록됩니다. 만약 파일크기가 1바이트부터
512 바이트사이라면 1블록(512바이트)을 사용하고 513바이트부터
1024바이트까지는 2블록(1024바이트)을 사용하는 식입니다.
결국 TAR 아카이브에서의 파일 내용 부분이 차지하는 크기는 아래와 같은
식으로 구할 수 있습니다.
function calc_blocksize($realsize) {
return ceil($realsize / 512) * 512;
}
파일크기가 0바이트인 파일인 경우에는 디렉토리인 경우와 마찬가지로
파일 헤더만 존재하며 본문은 기록되지 않습니다.
4. TAR 아카이브 후미에 붙여지는 빈블록
TAR 아카이브를 구성하는 멤버 파일 및 디렉토리에 관한 모든 정보(헤더 및
본문)가 다 기록된 후에는 마지막으로 파일 내용과 관계없이 null로 채워진
1블록 이상의 빈블록(empty block)들이 붙여지게 됩니다.
5. TAR 아카이브 관련 PHP 소스
TAR 관련 PHP 소스는 그리 흔한 것 같지 않습니다. 저도 관련 소스를
검색하여 보았으나 www.phpclasses.org에서 단 하나 발견하였을 뿐입니다.
본인도 이 소스를 참조하여 제 나름대로 TAR 아카이브를 다루는
클래스(hTarFile)를 작성하여 제 홈페이지에 공개하였습니다. 관심있는
분은 관련 소스들을 참조하시고 혹시 모르니 여러분도 인터넷을 더 검색해
보시기 바랍니다.
- tar Class
Josh Barger
joshb@npt.com
http://www.phpclasses.org
- hTarFile class
hwooky
hwooky@phpclass.com
http://www.phpclass.com
TAR 아카이브는 아니지만 zip 파일을 만들어 주는 소스가 보이네요. 소스는
TAR 아카이브를 다루는 것보다 훨씬 간단하구요.
저도 실험해 보지는 않았지만 꽤 쓸모있는 소스인 것 같네요. 참조하세요.
- class zipfile
Eric Mueller
eric@themepark.com
http://www.zend.com/codex.php?id=696&single=1
위하여 유래되었다고 합니다만 지금은 주로 여러 개의 파일을 묶어 전송하기
위하여 사용됩니다. 기본적으로 TAR 아카이브는 여러 개의 파일을 전혀
압축하지 않은 상태로 단지 하나의 파일로 묶어주는 역할만을 합니다.
우리가 보통 유닉스에서 제공하는 tar 유틸리티를 이용할 때 z 옵션을 지정하여
주면 압축까지 하는 것을 볼 수 있습니다. 그러나 이는 tar 유틸리티에서
압축해 주는 것이 아니라 tar 유틸리티와는 별개로 작성되어 있는
gzip 유틸리티를 불러다가 압축하게 되지요.
PHP에서의 상황을 보면 zlib 함수(gz로 시작하는 함수들)를 통해 파일을
압축할 수 있습니다. 그러나 zlib 함수는 기본적으로 하나의 파일만 다룰 수
있습니다. 웹상에서 여러 파일을 압축하고 해제하는 것이 다소 위험하여
그런지는 알 수 없으나 웹 스크립트에서는 TAR 아카이브를 잘 다루지 않는 것
같습니다. 생각해 보면 위험하기도 하겠습니다. 무슨 파일이 포함되었는지도
모르는 압축파일을 서버에 올려서 풀어버린다면 그 위험성은 상상하고도
남겠지요. 또한 내가 아닌 누군가가 나의 웹서버의 내용을 통째로 압축하여
가져가 버린다면...... 소름끼치는 상상이 될 수도 있겠지요. 그러나 구더기
무서워 장 못담그겠습니까? 서버상의 여러 개의 파일을 하나로 묶어서
다루어야 할 경우는 너무 많기때문에 TAR 아카이브를 다룰 수 있는 함수는
꼭 있어야 겠지요. 그리고 TAR 아카이브를 다룰 때 발생할 수 있는 여러가지
위험성을 충분히 고려하여 사용하기만 한다면 별(?) 문제 없을 것입니다.
현재 PHP에서 일반적으로 웹상에서 여러개의 파일을 압축하기 위해서는
exec() 또는 system()와 같은 함수와 리눅스에서 제공하는 tar 유틸리티를
이용하게 됩니다. 그러나 이러한 방법은 서버 상황에 따라 때로는 제대로
동작되지 않을 경우도 있으며 또한 윈도우 서버의 경우에는 해결방법이
될 수가 없습니다. 결국 가능한한 서버 환경에 관계없이 웹상에서
TAR 아카이브를 다루기 위해서는 TAR 아카이브를 직접 처리하는 것이
좋을 것 같습니다.
본인도 PHP에서 다중파일 다운로드 클래스를 만들다보니 여러 개의 파일을
다운로드하기 위해서는 결국 여러 개의 파일을 하나의 파일로 묶은 후에
이 파일을 다운로드 받은 것이 가장 좋겠다는 생각은 되었으나 현재 공개된
해결책으로는 위에서 언급한 리눅스의 tar 유틸리티를 이용하는 방법 외에는
눈에 띄는 것이 없었습니다. 그래서 인터넷을 검색하기 시작하였고
이를 통해 C로 작성된 tar 유틸리티의 소스 및 TAR 아카이브 구조에 관한
기술자료를 얻을 수 있었습니다. 이러한 자료를 통해 리눅스의 tar 유틸리티와
같은 기능을 수행하는 TAR 아카이브 클래스를 작성하게 되었으며
이를 공개하려고 준비하는 중에 PHPSCHOOL.COM 친구 여러분들도 꼭 다운로드가
아니더라도 다중 파일 백업과 같은 기능을 웹에서 구현하려고 하는 분들이
많이 있을 것 같아 TAR 아카이브를 직접 다룰 수 있는 기술 자료를 정리하여
올리게 되었습니다. 이 자료만 잘 습득하시면 여러분도 TAR 아카이브를
다루는 함수를 작성하는데 별 어려움이 없을 것으로 보입니다.
그러면 본론으로 들어가서 아래의 내용들에 대하여 하나씩 살펴보겠습니다.
◆ TAR 아카이브 구조
◆ 헤더 구조
◆ 본문 구조
◆ TAR 아카이브 후미에 붙여지는 블록
◆ TAR 아카이브 관련 PHP 소스
1. TAR 아카이브 구조
TAR 아카이브는 512바이트를 1블록으로 하여 다루게 됩니다. 따라서 TAR
아카이브의 크기를 보면 512 * n 바이트임을 알 수 있습니다. TAR 아카이브는
여러 개의 파일이 하나로 묶여있으므로 각 멤버파일마다 본문 내용에 앞서
파일에 대한 정보를 기록하게 되는 1블록의 헤더가 반드시 나타나며 그 뒤를
이어 n블록의 본문 내용이 나타납니다. 때에 따라서는 하나의 디렉토리에 있는
파일뿐만 아니라 다른 디렉토리에 있는 파일들도 하나의 TAR 아카이브에 묶을
때도 있을 것입니다. 이러한 경우에는 디렉토리에 대한 정보도 기록하여야
하는데 이 경우에도 파일 헤더와 같이 1블록의 디렉토리 헤더를 가지게 됩니다.
디렉토리의 경우에는 파일과는 달리 본문 내용이 없으므로 각 디렉토리마다
1블록의 디렉토리 헤더만이 존재합니다. 파일 및 디렉토리에 관한
모든 정보(헤더 및 본문)가 다 기록된 후에는 마지막으로 아스키 0으로 채워진
1블록 이상의 빈블록(empty block)들이 붙여지게 됩니다.
이젠 몇가지 실예를 들어 전체 구조가 어떻게 구성되는지 살펴보겠습니다.
1) 동일한 디렉토리에 있는 파일들(디렉토리 정보를 기록하지 않을 때)
s1.txt
s2.txt
s3.txt
s1.txt, s2.txt, s3.txt 파일이 모두 동일한 디렉토리에 존재하며 이를 묶을
때
디렉토리 정보를 전혀 기록하지 않는다면 TAR 아카이브의 전체 구조는 아래와
같을 것입니다.
+--------------------+
| s1.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| 빈블록 | n 블록(512*n 바이트)
+--------------------+
2) 동일한 디렉토리에 있는 파일들(디렉토리 정보를 기록할 때)
sub/s1.txt
sub/s2.txt
sub/s3.txt
s1.txt, s2.txt, s3.txt 파일이 모두 동일한 디렉토리 sub에 존재하며 이를
묶을 때 디렉토리 정보 sub를 기록한다면 TAR 아카이브의 전체 구조는 아래와
같을 것입니다.
+--------------------+
| sub 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| 빈블록 | n 블록(512*n 바이트)
+--------------------+
3) 여러 개의 디렉토리에 있는 파일들
s1.txt
s2.txt
sub1/s3.txt
sub1/sub2/s4.txt
sub1/sub2/s5.txt
sub1/sub2/sub3/s6.txt
sub4/s7.txt
s1.txt, s2.txt, s3.txt 파일이 여러 개의 디렉토리에 분산되어 존재하며 이를
묶을 때 이들 디렉토리 정보를 함께 기록한다면 TAR 아카이브의 전체 구조는
아래와 같을 것입니다.
+--------------------+
| s1.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s1.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s2.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub1 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s3.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub2 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s4.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s4.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| s5.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s5.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub3 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s6.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s6.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| sub4 디렉토리 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s7.txt 파일 헤더 | 1 블록(512 바이트)
+--------------------+
+--------------------+
| s7.txt 파일 본문 | n 블록(512*n 바이트)
+--------------------+
+--------------------+
| 빈블록 | n 블록(512*n 바이트)
+--------------------+
2. 헤더 구조
TAR 아카이브의 포맷을 보면 이전의 유닉스 호환 포맷(UNIX-compatible formats)과
POSIX (IEEE P1003.1) 기준으로 새로이 정의된 USTAR 포맷(USTAR format)이
있습니다. 새로이 정의된 USTAR 포맷은 유닉스 호환 포맷보다 더 많은 정보를
저장할 수 있으며 더 긴 파일 패스명을 지원합니다. 그러나 어느 포맷이
되었든지 512바이트 크기의 1블록으로 구성되어 있습니다.
우선 이전의 유닉스 호환 포맷에 의한 헤더 구조를 보면 아래와 같습니다.
----------+------+------+-------------------------
FIELD NAME OFFSET SIZE MEANING
----------+------+------+-------------------------
name 0 100 name of file
mode 100 8 file mode
uid 108 8 owner user ID
gid 116 8 owner group ID
size 124 12 length of file in bytes
mtime 136 12 modify time of file
chksum 148 8 checksum for header
link 156 1 indicator for links
linkname 157 100 name of linked file
......
----------+------+------+-------------------------
link 필드에는 링크 파일(linked file)이면 1, 심볼릭 링크(symbolic link)
이면 2, 기타일 때는 0이 기록됩니다. 디렉토리의 경우에는 링크명에
슬래시(/)가 붙게되지요.
새로운 USTAR 포맷에 의한 헤더 구조는 아래와 같습니다. magic 필드에는
null 문자로 종료되는 문자열 "ustra"이 기록되며 magic 필드 이전의
모든 필드는 typeflag 필드를 제외하고는 모두 이전의 유닉스 호환 포맷과
동일합니다. 유닉스 호환 포맷에서의 link 필드가 USTAR 포맷에서는
typeflag 필드로 변경되었습니다.
----------+------+------+-------------------------
FIELD NAME OFFSET SIZE MEANING
----------+------+------+-------------------------
name 0 100 name of file
mode 100 8 file mode
uid 108 8 owner user ID
gid 116 8 owner group ID
size 124 12 length of file in bytes
mtime 136 12 modify time of file
chksum 148 8 checksum for header
typeflag 156 1 type of file
linkname 157 100 name of linked file
magic 257 6 USTAR indicator
varsion 263 2 USTAR version
uname 265 32 owner user name
gname 297 32 owner group name
devmajor 329 8 device major number
devminor 337 8 device minor number
prefix 345 155 prefix for file name
......
----------+------+------+-------------------------
이와 같이 어느 포맷이 되었든지 파일명, 파일크기 등 파일 정보에 관한
내용이 저장되는 헤더는 1블록(512바이트)로 구성되어 있습니다.
이 문서에서는 USTAR 포맷을 중심으로 살펴보겠습니다.
TAR 아카이브를 다룰 때 name 필드부터 typeflag 필드까지만 정확히 다루게
된다면 윈도우를 포함한 어느 시스템에서나 정상적으로 동작하는 것
같습니다. 즉 여러분이 PHP를 이용하여 작성된 TAR 아카이브를 가지고
유닉스 또는 리눅스의 tar 유틸리티를 이용하여 풀 수 있으며, 또한
윈도우에서 알집이나 window commander에서 풀 수도 있습니다.
테이블에 나타난 바와 같이 각 필드마다 그 위치와 크기가 고정되어 있습니다.
magic, uname, gname 필드는 마지막 코드는 반드시 null(아스키문자 0)이
나타나는 null 종료 문자열(null-terminated character strings)입니다.
name, linkname, prefix 필드는 마지막 코드가 반드시 null일 필요는 없으나
정해진 필드크기가 다 채워지지 않았다면 이 공간을 반드시 null로 채워야
합니다. name 필드의 예를 들어보면 만약 파일명이 "s1.txt"라면 name 필드에
기록될 때는 "s1.txt" . str_repeat(chr(0), 100 - strlen("s1.txt")) 에서
얻은 값으로 기록됩니다.
USTAR 포맷에서 uname, gname 필드는 각각 로그인 사용자명과 그룹 사용자명을
기록합니다.
mode, uid, gid, chksum, devmajor, devminor 필드의 마지막 코드는 반드시
null이 기록되어야 하나
size, mtime, version 필드는 반드시 null로 종료할 필요는 없으나
일반적으로 null이 기록되는 것 같습니다. 따라서 숫자를 나타내는 이들
필드는 모두 null로 종료한다고 생각하시면 될 것입니다. 이와 같이 숫자를
나타내는 필드들은 모두 null로 종료되므로 실제로 숫자가 기록되는 장소의
크기는 테이블에 정하여진 SIZE-1이 됩니다. 예를 들어 uid의 경우를 보면
필드 크기는 8바이트이지만 실제로 OFFSET 108부터 114까지의 7바이트에만
기록되며 마지막 OFFSET 115에는 null이가 기록됩니다. 숫자 필드에서
한가지 주의할 것은 기록되는 숫자는 10진수가 아니라 8진수로 기록된다는
것이지요. 그리고 기록되는 숫자의 자리수가 필드 크기보다 작을 때는
앞쪽으로 남는 자리수만큼 "0"으로 채워지게 됩니다. 만약 uid 값이 8이라면
uid 필드에는 "0000008"이 기록됩니다.
어떤 필드가 되었든지 헤더에서는 사용되지 않는 공간은 모두 null로
채우도록 되어있습니다. 이제는 각 필드별로 다른 특징을 살펴보지요.
"name" 필드는 파일명 또는 디렉토리명이 기록되는 곳으로 총 100자까지
기록될 수 있습니다. 파일명이 100자가 되지 않아 비워있는 곳은 모두
null 값으로 채워집니다. 여기에 기록되는 파일명에는 패스명이 포함됩니다.
USTAR 포맷의 경우를 보면 prefix 필드의 값이 null이 아니라면 100자가
넘는 파일명이 가능하도록 name 필드 앞에 붙게 됩니다. 그러나 이전의
유닉스 호환 포맷이나 윈도우 시스템에서 TAR 아카이브를 만들 수 있는
윈도우커맨더 등과의 호환성을 생각한다면 prefix 필드를 null로 남겨두는
것이 좋을 듯하며 100자가 넘는 파일명인 경우 뒷쪽을 짤라내어
100자까지만 name 필드에 저장하는 것이 좋을 듯합니다.
USTAR 포맷에서만 사용하고 있는 typeflag 필드는 이전의 유닉스 호환
포맷의 link 필드와 호환성을 유지한 채 확장시킨 것입니다. 아래는
typeflag 필드에 기록될 수 있는 값들이며 이 중에 0, 1, 2까지는
이전의 유닉스 호환 포맷과 호환성을 유지합니다.
----------+--------------------------------------
TYPE FLAG FILE TYPE
----------+--------------------------------------
0 or null Regular file
1 Link to another file already archived
2 Symbolic link
3 Character special device
4 Block special device
5 Directory
6 FIFO special file
7 Reserved
A-Z Available for custom usage
----------+--------------------------------------
"typeflag"는 파일 형식을 나타내는 필드로 디렉토리일 때는 "5"가
기록되면 파일일 경우에는 "0"이 기록됩니다.
"mode" 필드는 파일 모드가 기록되는 곳으로 8진수로 기록됩니다. 예를
들면 0755, 0644와 같지요. "mode" 필드에서 사용할 수 있는 크기는
총 7자입니다. 이 경우 보통 "0000755"와 같이 남는 공간은 앞쪽에
"0"으로 채우게 됩니다. 그러나 이러한 기록 방법은 프로그램에 따라
약간씩 차이가 나기도 하네요. 윈도우 유틸리티인 windows commander에서
TAR 아카이브를 만들게 되면 "000755 " 또는 "000644 "와 같이 6자만
8진수로 만들어 주고 7번째 자리에는 " "로 채우게 됩니다. 윈도우에서야
원래 파일 모드라는 개념이 없으니까 관계가 없다고 생각할지 모르겠으나
윈도우에서 압축한 TAR 아카이브를 리눅스에서 풀 때는 생각한다면 이를
적절히 처리하는 것이 좋을 것 같습니다.
"uid"와 "gid"도 "mode"와 같이 8진수로 기록되며 총 7자리를 만들게
됩니다. 빈 공간은 앞쪽에 "0"을 채우게 되지요.
"size"는 바이트 단위의 파일 크기를 나타내며 8진수로 기록됩니다.
"mtime"는 파일을 수정한 시간을 나타내며 이에 해당하는 php 함수는
filemtime()입니다.
"magic" 필드는 압축 알고리즘의 이름과 버전을 나타내며 보통 "ustar"
또는 "gnutar" 등이 기록됩니다. 8자가 안되는 부분은 " "로 뒤쪽에
채워지게 됩니다.
"linkname", "uname", "gname", "devmajor", "devminor", "prefix",
"noname" 필드는 모두 chr(0)으로 채워넣습니다.
위와 같이 "chksum" 필드를 제외한 필드를 모두 기록한 후에 마지막으로
"chksum" 필드를 기록합니다. "chksum" 필드는 묶여진 파일이 정상적인가를
확인하는데 이용하는 체크섬 필드입니다. 8진수로 기록되며 채워지지 않는
부분은 앞쪽으로 "0"으로 채우게 됩니다. 체크섬 값은 아래와 같은
방법으로 구하게 됩니다. 이 때 변수 $HEADER에는 "chksum" 필드를 제외한
필드가 위에서 기술한 방법대로 먼저 기록되어 있다고 가정합니다.
function get_checksum() {
$sum = 0;
for ($i=0;$i<512;$i++) {
if ($i < 148 || 156 < $i) {
$sum += ord($HEADER[$i]);
} else {
$sum += ord(" ");
}
}
return $sum;
}
만약 기록하고자 하는 것이 파일이 아니라 디렉토리라면 파일과 같은
내용은 없기 때문에 512바이트의 헤더만 존재합니다. 한번 디렉토리
정보가 기록되면 그 뒤의 파일들은 앞에서 나타난 디렉토리에 속하게
됩니다. 따라서 디렉토리가 변경되는 부분에서만 단 한번 디렉토리 정보가
기록됩니다. 앞에서도 설명하였지만 파일 헤더와 다른 점이라면 "typeflag"
필드값이 "5"라는 것과 "name" 필드에는 파일명 대신에 패스명이
기록됩니다. 예를 들면 "sub1/sub2/"와 같지요. 다른 필드는 파일일 때와
같습니다.
3. 본문 구조
파일 헤더가 완성되었으면 뒤이어 파일 내용이 들어가게 됩니다. 파일
내용은 512 바이트의 배수로 기록됩니다. 만약 파일크기가 1바이트부터
512 바이트사이라면 1블록(512바이트)을 사용하고 513바이트부터
1024바이트까지는 2블록(1024바이트)을 사용하는 식입니다.
결국 TAR 아카이브에서의 파일 내용 부분이 차지하는 크기는 아래와 같은
식으로 구할 수 있습니다.
function calc_blocksize($realsize) {
return ceil($realsize / 512) * 512;
}
파일크기가 0바이트인 파일인 경우에는 디렉토리인 경우와 마찬가지로
파일 헤더만 존재하며 본문은 기록되지 않습니다.
4. TAR 아카이브 후미에 붙여지는 빈블록
TAR 아카이브를 구성하는 멤버 파일 및 디렉토리에 관한 모든 정보(헤더 및
본문)가 다 기록된 후에는 마지막으로 파일 내용과 관계없이 null로 채워진
1블록 이상의 빈블록(empty block)들이 붙여지게 됩니다.
5. TAR 아카이브 관련 PHP 소스
TAR 관련 PHP 소스는 그리 흔한 것 같지 않습니다. 저도 관련 소스를
검색하여 보았으나 www.phpclasses.org에서 단 하나 발견하였을 뿐입니다.
본인도 이 소스를 참조하여 제 나름대로 TAR 아카이브를 다루는
클래스(hTarFile)를 작성하여 제 홈페이지에 공개하였습니다. 관심있는
분은 관련 소스들을 참조하시고 혹시 모르니 여러분도 인터넷을 더 검색해
보시기 바랍니다.
- tar Class
Josh Barger
joshb@npt.com
http://www.phpclasses.org
- hTarFile class
hwooky
hwooky@phpclass.com
http://www.phpclass.com
TAR 아카이브는 아니지만 zip 파일을 만들어 주는 소스가 보이네요. 소스는
TAR 아카이브를 다루는 것보다 훨씬 간단하구요.
저도 실험해 보지는 않았지만 꽤 쓸모있는 소스인 것 같네요. 참조하세요.
- class zipfile
Eric Mueller
eric@themepark.com
http://www.zend.com/codex.php?id=696&single=1