`

tar打包原理分析

阅读更多

http://blog.chinaunix.net/u3/101042/showart_2020063.html

----------------------------------------------------------------------------------------------------------------------------------------

首先是进入main
获得program_name //这个是执行程序的文件名
然后设置环境变量,初始化退出的状态,代码如下:

 

1566 program_name = argv[ 0] ;
  1567 setlocale ( LC_ALL , "" ) ;
  1568 textdomain ( PACKAGE) ;
  1570 exit_status = TAREXIT_SUCCESS;

 

下面这个函数是tar里封装的一个申请内存的函数:

 

74 /* Allocate N bytes of memory dynamically, with error checking. */
   75
   76 VOID *
   77 xmalloc ( n)
   78 size_t n;
   79 {
   80 VOID * p;
   81
   82 p = malloc ( n) ;
   83 if ( p = = 0)
   84 p = fixup_null_alloc ( n) ;
   85 return p;
= > 86 }

 

decode_options (argc, argv);这个函数是在main函数里面的,用来获得我们执行tar的时候带的参数的里面我们会看到一个getopt

 

1072 /*----------------------------.
  1073 | Parse the options for tar. |
  1074 `----------------------------*/

  1075
  1076 # define OPTION_STRING \
  1077 "-01234567ABC:F:GK:L:MN:OPRST:V:WX:Zb:cdf:g:hiklmoprstuvwxz"
  1078
  1079 # define SET_COMMAND_MODE( Mode) \
  1080 ( command_mode = command_mode = = COMMAND_NONE ? ( Mode) : COMMAND_TOO_MANY)
  1081
  1082 static void
  1083 decode_options ( int argc, char * const * argv)
  1084 {
  1085 int optchar; /* option letter */
  1086
  1087 /* Set some default option values. */
  1088
= > 1089 blocking = DEFAULT_BLOCKING;
  1090 flag_rsh_command = NULL ;
  1091

 

接着通过下面这个循环获得指令中的参数

 

 

1141 /* Parse all options and non-options as they appear. */
  1142
  1143 while ( optchar = getopt_long ( argc, argv, OPTION_STRING, long_options, NULL ) ,
  1144 optchar ! = EOF )
= > 1145 switch ( optchar)

 

看一下optchar

 

1848 ( gdb) print optchar
1849 $68 = 99

 

输出的是99,可以看一下对应的ascii,在终端里输入man ascii的时候,找到
143   99    63    c

这么一行,第一列是八进制,第二列是10进制,第三列式16进制,第四列就是对应的字符了。
这个是在一个while循环里面做的,我使用的是一个打包的参数,不带压缩参数:

 

1837 ( gdb) p argv[ 0]
1838 $63 = 0xbffff758 "/home/liuqi/dvntar/dvntar"
1839 ( gdb) p argv[ 1]
1840 $64 = 0xbffff772 "-cf"
1841 ( gdb) p argv[ 2]
1842 $65 = 0xbffff776 "example.tar"
1843 ( gdb) p argv[ 3]
1844 $66 = 0xbffff782 "example"
1845 ( gdb) p argv[ 4]
1846 $67 = 0x0
1847 ( gdb)

 

以上是命令行输入的参数,可以看到我输入的是:

 

/ home/ liuqi/ dvntar/ dvntar - cf example. tar example

然后继续向下走,就会到

1315 case 'c' :
= > 1316 SET_COMMAND_MODE ( COMMAND_CREATE) ;
  1317 break ;

这里需要注意,后面会提到,设置命令模式为COMMAND_CREATE模式,这个在main里面会看到,打包的工作主要是也在这里做

在循环获得参数后,会得到一个f参数
这个时候会进入:

 

1331 case 'f' :
= > 1332 if ( archive_names = = allocated_archive_names)
  1333 {
  1334 allocated_archive_names * = 2;
  1335 archive_name_array = ( const char * * ) xrealloc( archive_name_array, sizeof ( const char * ) * allocated_archive_names) ;
  1336 }
  1337 archive_name_array[ archive_names+ + ] = optarg;
  1338 break ;
  1339

1857 ( gdb) print archive_names
1858 $70 = 0
1859 ( gdb) print allocated_archive_names
1860 $71 = 10
1861 ( gdb)

因为两个值不同,所以不会进if条件的立面,会直接进入archive_name_array[archive_names++] = optarg;

再回到循环获得optarg的时候,会看到

1865 ( gdb) print optchar
1866 $72 = 1
1867 ( gdb)

然后就进入了

1157 case 1:
  1158 /* File name or non-parsed option, because of RETURN_IN_ORDER
  1159 ordering triggerred by the leading dash in OPTION_STRING. */

  1160
= > 1161 name_add ( optarg) ;
  1162 break ;

添加文件名在name_array字符串结尾

109 /*--------------------------------------------------------------.
   110 | Add NAME at end of name_array, reallocating it as necessary. |
   111 `--------------------------------------------------------------*/

   112
   113 static void
   114 name_add ( const char * name)
   115 {
= > 116 if ( names = = allocated_names)
   117 {
   118 allocated_names * = 2;
   119 name_array = ( const char * * ) xrealloc ( name_array, sizeof ( const char * ) * allocated_names) ;
   120 }
   121 name_array[ names+ + ] = name;
   122 }

从gdb里面可以看到,name是

1868 ( gdb) step
1869 name_add ( name= 0xbffff782 "example" ) at / home/ liuqi/ dvntar/ src/ tar. c: 116
1870 ( gdb)

由于参数里面没有带z或者j的压缩,所以,这里
  1551   if (flag_compress_block && !flag_compressprog)
没有设置flag_compressprog和flag_compress_block

如果带了对应的参数的话,将会多起一个进程,使用管道来进行gzip压缩,这个在代码里面会看到,用gdb可以跟到起新进程那里,fork函数启动的子进程。
执行完了以后会回到main函数,这个时候会进行下一步
=>1590   if (!names_argv)
  1591     name_init (argc, argv);
gdb打印一下names_argv,得出结果如下
1885 (gdb) print names_argv
1886   $73 = (char * const *) 0x0
1887 (gdb)        

看结果会进入  name_init (argc, argv);
进去看看:

由于参数里面没有带z或者j的压缩,所以,这里
  1551 if ( flag_compress_block & & ! flag_compressprog)
没有设置flag_compressprog和flag_compress_block

如果带了对应的参数的话,将会多起一个进程,使用管道来进行gzip压缩,这个在代码里面会看到,用gdb可以跟到起新进程那里,fork函数启动的子进程。
执行完了以后会回到main函数,这个时候会进行下一步
= > 1590 if ( ! names_argv)
  1591 name_init ( argc, argv) ;
gdb打印一下names_argv,得出结果如下
1885 ( gdb) print names_argv
1886 $73 = ( char * const * ) 0x0
1887 ( gdb)

看结果会进入 name_init ( argc, argv) ;
进去看看:

从代码看,并用gdb输出了一下

1890 ( gdb) print flag_namefile
1891 $74 = 0
1892 ( gdb)

结果是这个函数会将参数设置给公共变量names_argc和names_argv。

然后开始之前的设置的

= > 1598 switch ( command_mode)
  1599 {

前面设置过命令模式就是当我们解析参数为-c的时候设置的,在这里会用到:

1619 case COMMAND_CREATE:
= > 1620 create_archive ( ) ;
  1621 if ( flag_totals)
  1622 fprintf ( stderr , _( "Total bytes written: %d\n" ) , tot_written) ;
  1623 break ;

到这里了,会进入create_archive ();
打包的操作主要是在这里进行,进去看看就知道了

610 void
   611 create_archive ( void )
   612 {
   613 register char * p;
   614
= > 615 open_archive ( 0) ; /* open for writing */

遇到一个open_archive函数,在这之前,我们会看到,还没有创建我们要写入的文件包,创建包是在这里进行的,执行完这个函数后,就会看到打包的文件了。(如果不信的话可以试试,在这里设断点就可以了)
接着进入该函数后会看到如下的代码:

532 current_file_name = NULL ;
   533 current_link_name = NULL ;
   534 save_name = NULL ;
   535
   536 if ( flag_multivol)
   537 {
   538 ar_block
   539 = ( union record * ) valloc ( ( unsigned ) ( blocksize + ( 2 * RECORDSIZE) ) ) ;
   540 if ( ar_block)
   541 ar_block + = 2;
   542 }
   543 else
= > 544 ar_block = ( union record * ) valloc ( ( unsigned ) blocksize) ;

因为前面没有对flag_multivol进行赋值,所以,这里会进入else里面执行申请内存

= > 556 if ( flag_compressprog)
   557 {
   558 if ( reading = = 2 | | flag_verify)
   559 ERROR ( ( TAREXIT_FAILURE, 0,
   560 _( "Cannot update or verify compressed archives" ) ) ) ;
   561 if ( flag_multivol)
   562 ERROR ( ( TAREXIT_FAILURE, 0,
   563 _( "Cannot use multi-volume compressed archives" ) ) ) ;
   564 child_open ( ) ;
   565 if ( ! reading & & strcmp ( archive_name_array[ 0] , "-" ) = = 0)
   566 stdlis = stderr ;
   567 # if 0
   568 child_open ( rem_host, rem_file) ;
   569 # endif
   570 }

由于没有添加压缩参数,所以,这里不会进入if条件内而是进入的

596 else
= > 597 archive = rmtcreat ( archive_name_array[ 0] , 0666, flag_rsh_command) ;

这里开始创建一个文件执行完这一句以后,就创建了一个example.tar文件,创建完以后会继续回到create_archive里面

610 void
   611 create_archive ( void )
   612 {
   613 register char * p;
   614
   615 open_archive ( 0) ; /* open for writing */
   616
= > 617 if ( flag_gnudump)
   618 {

根据打印看

2039 ( gdb) print flag_gnudump
2040 $80 = 0
2041 ( gdb)

不会进入这个if条件内,而是进入了else

653 else
   654 {
= > 655 while ( p = name_next ( 1) , p)
   656 dump_file ( p, - 1, 1) ;
   657 }

接着是一个while循环,条件是如果有p的话,查找p里的name,进入看一下

197 /*-------------------------------------------------------------------------.
   198 | Get the next name from argv or the name file. Result is in static |
   199 | storage and can't be relied upon across two calls. |
   200 | |
   201 | If CHANGE_DIRS is non-zero, treat a filename of the form "-C" as meaning |
   202 | that the next filename is the name of a directory to change to. If |
   203 | `filename_terminator' is '\0', CHANGE_DIRS is effectively always 0. |
   204 `-------------------------------------------------------------------------*/

   205
   206 char *
   207 name_next ( int change_dirs)
   208 {
   209 const char * source;
   210 char * cursor;
   211 int chdir_flag = 0;
   212
   213 if ( filename_terminator = = '\0' )
   214 change_dirs = 0;
   215
= > 216 if ( name_file)

关于这个函数,在注释里面已经说得很明白了,首先我们看一下这个目录里面有什么文件

[ root@1jjk dvntar] # ls example
aaa bbb
[ root@1jjk dvntar] #

文件里面有两个文件,分别是aaa,bbb,继续往下跟,可以输出一下name_file

2046 ( gdb) print name_file
2047 $81 = ( FILE * ) 0x0
2048 ( gdb)

结果告诉我们她不会进入这个条件里面,而是进入了

250 else
   251 {
   252
   253 /* Read from argv, after options. */
   254
   255 while ( 1)
   256 {
   257 if ( name_index < names)
   258 source = name_array[ name_index+ + ] ;
   259 else if ( optind < names_argc)
   260 source = names_argv[ optind+ + ] ;
   261 else
   262 break ;
   263
= > 264 if ( strlen ( source) > name_buffer_length)
   265 {
   266 free ( name_buffer) ;
   267 name_buffer_length = strlen ( source) ;
   268 name_buffer = xmalloc ( name_buffer_length + 2) ;
   269 }
其实经过gdb输出,进入
259 else if ( optind < names_argc)
   260 source = names_argv[ optind+ + ] ;

  

这里是活的目录名

2059 ( gdb) print source
2060 $86 = 0xbffff782 "example"
2061 ( gdb)

以后,我们可以继续了

270 strcpy ( name_buffer, source) ;
   271
   272 /* Zap trailing slashes. */
   273
= > 274 cursor = name_buffer + strlen ( name_buffer) - 1;

其实通过打印,我们可以知道name_buffer其实是一个用来记录一个字符串的地址

2065 ( gdb) print name_buffer
2066 $88 = 0x806e888 "example"
2067 ( gdb)

而cursor相当于一个指针,也可以理解为光标

2068 ( gdb) print cursor
2069 $89 = 0x806e88e "e"
2070 ( gdb)

接着往下走

278 if ( chdir_flag)
   279 {
   280 if ( chdir ( name_buffer) < 0)
   281 ERROR ( ( TAREXIT_FAILURE, errno ,
   282 _( "Cannot chdir to %s" ) , name_buffer) ) ;
   283 chdir_flag = 0;
   284 }
   285 else if ( change_dirs & & strcmp ( name_buffer, "-C" ) = = 0)
   286 chdir_flag = 1;
   287 else
= > 291 return un_quote_string ( name_buffer) ;

因为我们这个不是chdir_flag,通过gdb输出可以看出来,所以进入到了un_quote_string (name_buffer);

2077 ( gdb) step
2078 un_quote_string ( string = 0x806e888 "example" ) at / home/ liuqi/ dvntar/ src/ port. c: 730
2079 ( gdb)

进去以后,会看到如下代码

714 /*-----------------------------------------------------------------------.
  715 | Un_quote_string takes a quoted c-string (like those produced by |
  716 | quote_string or quote_copy_string and turns it back into the un-quoted |
  717 | original. This is done in place. |
  718 `-----------------------------------------------------------------------*/

  719
  720 /* There is no un-quote-copy-string. Write it yourself */
  721
  722 char *
  723 un_quote_string ( char * string )
  724 {
  725 char * ret;
  726 char * from_here;
  727 char * to_there;
  728 int tmp;
  729
  730 ret = string ;
  731 to_there = string ;
  732 from_here = string ;
= > 733 while ( * from_here)
  734 {
  735 if ( * from_here ! = '\\' )
  736 {
  737 if ( from_here ! = to_there)
  738 * to_there+ + = * from_here+ + ;
  739 else
  740 from_here+ + , to_there+ + ;
  741 continue ;
  742 }

从代码分析,会进入这个循环里面执行,执行完以后会退出这个函数,

805 if ( * to_there)
  806 * to_there+ + = '\0' ;
= > 807 return ret;
  808 }

然后会回到create_archive中,执行 dump_file (p, -1, 1);
进去看一下:

666 /*-------------------------------------------------------------------------.
   667 | Dump a single file. If it's a directory, recurse. Result is 1 for |
   668 | success, 0 for failure. Sets global "hstat" to stat() output for this |
   669 | file. P is file name to dump. CURDEV is device our parent dir was on. |
   670 | TOPLEVEL tells wether we are a toplevel call. |
   671 `-------------------------------------------------------------------------*/

   672
   673 void
   674 dump_file ( char * p, int curdev, int toplevel)
   675 {
   676 union record * header;
   677 char type;
   678 union record * exhdr;
   679 char save_linkflag;
= > 680 int critical_error = 0;
   681 struct utimbuf restore_times;
从注释可以看出,这个事用来遍历目录的函数。
  1151 else if ( S_ISDIR ( hstat. st_mode) )
  1152 {
  1153 register DIR * dirp;
  1154 register struct dirent * d;
  1155 char * namebuf;
  1156 int buflen;
  1157 register int len;
  1158 int our_device = hstat. st_dev;
  1159
  1160 /* Build new prototype name. */
  1161
  1162 len = strlen ( p) ;
  1163 buflen = len + NAMSIZ;
  1164 namebuf = xmalloc ( ( size_t ) ( buflen + 1) ) ;
  1165 strncpy ( namebuf, p, ( size_t ) buflen) ;
  1166 while ( len > = 1 & & namebuf[ len - 1] = = '/' )
  1167 len- - ; /* delete trailing slashes */
= > 1168 namebuf[ len+ + ] = '/' ; /* now add exactly one back */
  1169 namebuf[ len] = '\0' ; /* make sure null-terminated */

如果遇到了目录的话,会在后面加上一个'/'

1175 if ( ! flag_oldarch)
  1176 {
  1177 hstat. st_size = 0; /* force 0 size on dir */
  1178
  1179 /* If people could really read standard archives, this should
  1180 be: (FIXME)
  1181
  1182 header = start_header (flag_standard ? p : namebuf, &hstat);
  1183
  1184 but since they'd interpret LF_DIR records as regular files,
  1185 we'd better put the / on the name. */

  1186
= > 1187 header = start_header ( namebuf, & hstat) ;

这里会进入start_header,建立一个header.

185 /* Header handling. */
   186
   187 /*---------------------------------------------------------------------.
   188 | Make a header block for the file name whose stat info is st. Return |
   189 | header pointer for success, NULL if the name is too long. |
   190 `---------------------------------------------------------------------*/

   191
   192 static union record *
   193 start_header ( const char * name, register struct stat * st)

分享到:
评论

相关推荐

    tar-1.27.tar.gz_tar_tar 压缩源码_tar.gz

    在Linux/Unix环境中,通常先用tar打包,再用gzip压缩,以节省存储空间。在处理"tar-1.27.tar.gz"时,我们需要先用gunzip解压,得到"tar-1.27.tar",然后再用tar提取出原始文件。这一过程涉及到的压缩和解压缩原理,...

    tar-1.26.tar.gz

    《tar-1.26源代码详解:洞察命令行打包工具的奥秘》 在信息技术领域,tar是一个不可或缺的工具,尤其在Linux和Unix系统中,它被广泛用于打包和归档文件。当我们看到"tar-1.26.tar.gz"这样的文件名时,我们可以推断...

    apktool1.5.2.tar.bz2和apktool-install-linux-r05-ibot.tar.bz2打包下载

    APKTool是一款强大的Android应用分析和反编译工具,它由布拉德·科尔(Brad Collier)开发,主要用于帮助开发者和安全研究人员查看、修改以及重新打包Android应用。这两个压缩文件,"apktool1.5.2.tar.bz2" 和 ...

    daemon-0.8.tar.gz

    ".tar" 是一个归档文件格式,它能够将多个文件和目录打包成一个单一的文件,便于存储、传输和备份。而 ".gz" 是使用Gzip压缩算法对 ".tar" 文件进行压缩后的结果,可以显著减小文件大小,加快传输速度。 在处理 ...

    httpd.tar.gz

    【标签】"httpd.tar.gz.tar.jar" 这个标签可能表明压缩包中包含了多个级别的压缩,首先是 ".tar" 归档,它通常用于将多个文件打包到一起,便于管理和传输。接着是 ".gz" 压缩,这是GNU zip的缩写,用于减小文件大小...

    tar-1.13.19-4.src.rar_linux_linux 压缩文件_tar 压缩源码_tar1.

    《Linux系统下的tar压缩工具详解及其源码分析》 在Linux和Unix操作系统中,`tar`是一个非常重要的命令行工具,用于打包和压缩文件。它并非一个真正的压缩工具,而是一个文件打包程序,可以将多个文件和目录组合成一...

    tar-latest.tar.gz

    《Linux系统下tar命令详解与源码分析》 在Linux操作系统中,`tar`命令是不可或缺的工具之一,它主要用于处理文件归档和压缩。在本文中,我们将深入探讨`tar`命令的基本用法,以及如何从源码层面理解其工作原理。 1...

    File-Tail-0.99.3.tar.gz

    ".tar.gz"是Unix/Linux系统中常见的文件打包格式,它先用tar命令将多个文件打包成一个大文件,然后用gzip压缩这个大文件,以达到节省存储空间的目的。解压这个包后,我们可以看到File-Tail-0.99.3目录,里面包含了源...

    PyPI 官网下载 | ltsa-0.1.tar.gz

    因此,ltsa-0.1.tar.gz文件是ltsa-0.1版本的源代码经过tar打包和gzip压缩后的结果。 “ltsa”这个库可能指的是Long Short-Term Memory Analysis(长期短期记忆分析),这是在自然语言处理(NLP)领域常用的一种技术...

    nistnet.2.0.12c.tar.gz

    通过`.tar.gz`扩展名我们可以推断,这是一个使用`tar`命令打包,并通过`gzip`进行压缩的文件,这是Linux和Unix系统中常见的文件打包压缩方式。 在Linux环境中,`tar`命令用于将多个文件和目录打包成一个单一的归档...

    zmap-2.1.0.tar.gz

    3. 研究与教育:在学术研究中,zmap可用于分析网络拓扑结构,或者在教学环境中教授网络扫描原理。 然而,使用zmap时也需要注意合法性和道德问题。因为大规模扫描可能会对目标网络造成影响,甚至触犯法律,所以务必...

    rarlinux-3.8.b4.tar.gz

    "rarlinux"表示这是专为Linux系统设计的RAR工具,"3.8.b4"代表版本信息,"tar.gz"则表明该软件包采用了tar打包和gzip压缩的方式。这种组合方式使得文件在保持较小体积的同时,便于在Linux系统中分发和安装。 在...

    chrome.tar

    "chrome.tar"这个文件名暗示了我们可能在处理与Chrome浏览器相关的源代码或资源的打包文件。在这种情况下,".tar"扩展名表示这是一个归档文件,通常用于在Linux或Unix系统中组合多个文件和目录。这种格式不提供任何...

    TLS.tar.gz资料包

    标题和描述中提到的“TLS.tar.gz”资料包,指的是一个使用gzip压缩算法并打包成tar格式的文件,其中包含了与Transport Layer Security(TLS)相关的资源。TLS是一种网络通信协议,主要用于确保数据在互联网上传输时...

    logstash-6.1.2.tar.gz

    `tar` 命令用于将多个文件和目录打包成一个单一的归档文件,而 `.gz` 是 GNU zip 压缩算法的应用,可以进一步减小文件大小,方便存储和传输。 ELK(Elasticsearch, Logstash, Kibana)堆栈是日志管理和分析的流行...

    pcre-8.01.tar.gz

    "tar.gz"是Unix/Linux系统中常见的文件打包方式,"tar"用于将多个文件和目录打包成一个单一的文件,"gz"则是使用gzip压缩工具进行压缩,以减小文件大小,方便传输和存储。 在解压"Pcre-8.01.tar.gz"后,你会得到一...

    sourcenav-6.0.tar.gz

    `.tar`文件本身是一个归档文件,可以将多个文件和目录打包到一起,而`.gz`则是gzip压缩算法的结果,用于减少文件大小,方便存储和传输。解压`sourcenav-6.0.tar.gz`通常需要先使用`tar`命令进行解包,再用`gzip`或`...

    mysql-5.1.45.tar.gz

    - **源代码分析**:通过获取MySQL的源代码,开发者可以深入了解其内部工作原理,进行自定义修改或二次开发,满足特定需求。 - **编译与安装**:解压`.tar.gz`文件后,开发者需要遵循标准的编译流程(如配置、编译...

    boa-0.94.13.tar.gz

    ".tar"是打包工具,可以将多个文件和目录组合成一个单一的归档文件,方便管理和传输。".gz"是Gzip的压缩格式,用于减少文件大小,提高存储和传输效率。在Linux上,用户通常会使用"tar"命令来解压和提取这个文件,...

    最新版linux jdk-11.0.13_linux-x64_bin.tar.gz

    安装完成后,开发人员可以使用`javac`命令进行编译,`java`命令执行程序,`jar`命令打包应用程序,以及一系列的JDK工具(如javadoc生成文档,jps查看Java进程,jmap分析内存等)。 7. **Java 11特性**: Java 11...

Global site tag (gtag.js) - Google Analytics