From 424d62c72ec0db3fa2b19791b6f96c7eb5a5e412 Mon Sep 17 00:00:00 2001 From: John Starks Date: Mon, 28 Mar 2016 12:39:27 -0700 Subject: [PATCH 1/4] Use existing PAX headers for time This removes the MSWINDOWS.* time fields and uses PAX times directly. I didn't previously realize that PAX times could be negative, which eliminates the need to store Windows times in a different format. This also uses the LIBARCHIVE.creationtime field for creation times to match OS X behavior. --- archive/tar/common.go | 64 ++++++++++++----------- archive/tar/reader.go | 6 +++ archive/tar/testdata/gnu-multi-hdrs.tar | Bin 0 -> 4608 bytes archive/tar/testdata/gnu.tar | Bin 0 -> 3072 bytes archive/tar/testdata/hardlink.tar | Bin 0 -> 2560 bytes archive/tar/testdata/hdr-only.tar | Bin 0 -> 10240 bytes archive/tar/testdata/issue10968.tar | Bin 0 -> 512 bytes archive/tar/testdata/issue11169.tar | Bin 0 -> 602 bytes archive/tar/testdata/issue12435.tar | Bin 0 -> 512 bytes archive/tar/testdata/neg-size.tar | Bin 0 -> 512 bytes archive/tar/testdata/nil-uid.tar | Bin 0 -> 1024 bytes archive/tar/testdata/pax-multi-hdrs.tar | Bin 0 -> 4608 bytes archive/tar/testdata/pax-path-hdr.tar | Bin 0 -> 1024 bytes archive/tar/testdata/pax.tar | Bin 0 -> 10240 bytes archive/tar/testdata/small.txt | 1 + archive/tar/testdata/small2.txt | 1 + archive/tar/testdata/sparse-formats.tar | Bin 0 -> 17920 bytes archive/tar/testdata/star.tar | Bin 0 -> 3072 bytes archive/tar/testdata/ustar-file-reg.tar | Bin 0 -> 1536 bytes archive/tar/testdata/ustar.tar | Bin 0 -> 2048 bytes archive/tar/testdata/v7.tar | Bin 0 -> 3584 bytes archive/tar/testdata/writer-big-long.tar | Bin 0 -> 4096 bytes archive/tar/testdata/writer-big.tar | Bin 0 -> 4096 bytes archive/tar/testdata/writer.tar | Bin 0 -> 3584 bytes archive/tar/testdata/xattrs.tar | Bin 0 -> 5120 bytes archive/tar/writer.go | 41 ++++++++++++--- archive/tar/writer_test.go | 17 ++++++ backuptar/tar.go | 56 ++++++-------------- backuptar/tar_test.go | 21 +++++++- 29 files changed, 126 insertions(+), 81 deletions(-) create mode 100644 archive/tar/testdata/gnu-multi-hdrs.tar create mode 100644 archive/tar/testdata/gnu.tar create mode 100644 archive/tar/testdata/hardlink.tar create mode 100644 archive/tar/testdata/hdr-only.tar create mode 100644 archive/tar/testdata/issue10968.tar create mode 100644 archive/tar/testdata/issue11169.tar create mode 100644 archive/tar/testdata/issue12435.tar create mode 100644 archive/tar/testdata/neg-size.tar create mode 100644 archive/tar/testdata/nil-uid.tar create mode 100644 archive/tar/testdata/pax-multi-hdrs.tar create mode 100644 archive/tar/testdata/pax-path-hdr.tar create mode 100644 archive/tar/testdata/pax.tar create mode 100644 archive/tar/testdata/small.txt create mode 100644 archive/tar/testdata/small2.txt create mode 100644 archive/tar/testdata/sparse-formats.tar create mode 100644 archive/tar/testdata/star.tar create mode 100644 archive/tar/testdata/ustar-file-reg.tar create mode 100644 archive/tar/testdata/ustar.tar create mode 100644 archive/tar/testdata/v7.tar create mode 100644 archive/tar/testdata/writer-big-long.tar create mode 100644 archive/tar/testdata/writer-big.tar create mode 100644 archive/tar/testdata/writer.tar create mode 100644 archive/tar/testdata/xattrs.tar diff --git a/archive/tar/common.go b/archive/tar/common.go index 5141bf9..0378401 100644 --- a/archive/tar/common.go +++ b/archive/tar/common.go @@ -44,22 +44,23 @@ const ( // A Header represents a single header in a tar archive. // Some fields may not be populated. type Header struct { - Name string // name of header file entry - Mode int64 // permission and mode bits - Uid int // user id of owner - Gid int // group id of owner - Size int64 // length in bytes - ModTime time.Time // modified time - Typeflag byte // type of header entry - Linkname string // target name of link - Uname string // user name of owner - Gname string // group name of owner - Devmajor int64 // major number of character or block device - Devminor int64 // minor number of character or block device - AccessTime time.Time // access time - ChangeTime time.Time // status change time - Xattrs map[string]string - Winheaders map[string]string + Name string // name of header file entry + Mode int64 // permission and mode bits + Uid int // user id of owner + Gid int // group id of owner + Size int64 // length in bytes + ModTime time.Time // modified time + Typeflag byte // type of header entry + Linkname string // target name of link + Uname string // user name of owner + Gname string // group name of owner + Devmajor int64 // major number of character or block device + Devminor int64 // minor number of character or block device + AccessTime time.Time // access time + ChangeTime time.Time // status change time + CreationTime time.Time // creation time + Xattrs map[string]string + Winheaders map[string]string } // File name constants from the tar spec. @@ -180,21 +181,22 @@ const ( // Keywords for the PAX Extended Header const ( - paxAtime = "atime" - paxCharset = "charset" - paxComment = "comment" - paxCtime = "ctime" // please note that ctime is not a valid pax header. - paxGid = "gid" - paxGname = "gname" - paxLinkpath = "linkpath" - paxMtime = "mtime" - paxPath = "path" - paxSize = "size" - paxUid = "uid" - paxUname = "uname" - paxXattr = "SCHILY.xattr." - paxWindows = "MSWINDOWS." - paxNone = "" + paxAtime = "atime" + paxCharset = "charset" + paxComment = "comment" + paxCtime = "ctime" // please note that ctime is not a valid pax header. + paxCreationTime = "LIBARCHIVE.creationtime" + paxGid = "gid" + paxGname = "gname" + paxLinkpath = "linkpath" + paxMtime = "mtime" + paxPath = "path" + paxSize = "size" + paxUid = "uid" + paxUname = "uname" + paxXattr = "SCHILY.xattr." + paxWindows = "MSWINDOWS." + paxNone = "" ) // FileInfoHeader creates a partially-populated Header from fi. diff --git a/archive/tar/reader.go b/archive/tar/reader.go index 6aee36c..e210c61 100644 --- a/archive/tar/reader.go +++ b/archive/tar/reader.go @@ -302,6 +302,12 @@ func mergePAX(hdr *Header, headers map[string]string) error { return err } hdr.ChangeTime = t + case paxCreationTime: + t, err := parsePAXTime(v) + if err != nil { + return err + } + hdr.CreationTime = t case paxSize: size, err := strconv.ParseInt(v, 10, 0) if err != nil { diff --git a/archive/tar/testdata/gnu-multi-hdrs.tar b/archive/tar/testdata/gnu-multi-hdrs.tar new file mode 100644 index 0000000000000000000000000000000000000000..8bcad55d06e8f9fde3641d2a8df370503a582ce6 GIT binary patch literal 4608 zcmdPX*VA|K$%Afaj^QI J<`xZ33jpEZfW-g+ literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/gnu.tar b/archive/tar/testdata/gnu.tar new file mode 100644 index 0000000000000000000000000000000000000000..fc899dc8dc2ad9952f5c5f67a0c76ca2d87249e9 GIT binary patch literal 3072 zcmeHH%L>9U5Ztq0(Jv@FdDKtv;8zq|ijXv5BIw^6DOh^2UK)_HbK1>@VQ0c5`qsHR zJrb1zXEcV16&lMRW}rdtKd=NSXg->JkvP|Esp4`g&CK_h+FMmo7oR?iU7RP&svn2t z!9Ke4)upeR_aRYKtT+(g`B!B>fS>t?p7IZ9V9LKWlK+)w+iY|SVQ_tY3I4Ddrx1w) M;($0H4*b6ZFWOBnBLDyZ literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/hardlink.tar b/archive/tar/testdata/hardlink.tar new file mode 100644 index 0000000000000000000000000000000000000000..9cd1a26572e44150ded8a628fefb28fa089645d1 GIT binary patch literal 2560 zcmYex%t_TNsVHHfAus>}GZPaAAZ2K7Y5<}Q3?Y0F6C}!DXk=n;YGz~#VjCD58=09i zC>YStO>m=2i%SxVfKDn)N-QZUh6`gbN{dsA@JNF_1@sD>#xP)T3IyjQ7L{Zs0g1H4 z;u5aG>Bv!6(JTZq5{ps>JpTi;4Ql>3F*i14P%uoRL*X>S^FPfJ)~LawAut*OgFXZR DcLg^L literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/hdr-only.tar b/archive/tar/testdata/hdr-only.tar new file mode 100644 index 0000000000000000000000000000000000000000..f25034083de6e0176e429f939875def6eb78cc73 GIT binary patch literal 10240 zcmeI2ZE}J@42Ji2Pq95gv)>o#1+ajk2rWokd-`UnK%I_CXNW`V?jLp5$;Lb+o4jM3 zRS%4K0WN2N31Pu$!vOG|0DSEi6VfBdZFVz=+!#Geo7WlKr zRl;AI>}kUnRryx%w0!65X8WAPynIb6zQg@I`q=ZhT;AVZ14uaInh{tzn(ux&A2{mb)wBl`42&RQXR%ispcL7W$7F z`oDwzV^q+8Xow$MornI@^B?pdod1LVbIgk3(>1Qxw*Nb){{{Vr0_`Z9LH`*QrhogT zdFVfV{nxJ3f3W@s{fGXsn}`0>@$ct<6aa(%Lr=2_XU@0=FB1PuCY>1poj5 literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/issue10968.tar b/archive/tar/testdata/issue10968.tar new file mode 100644 index 0000000000000000000000000000000000000000..1cc837bcff14cd822a26e43034955c82e852ab29 GIT binary patch literal 512 zcmbVI!41MN47Ah*kg@;^fX)>lI!AWsgI^V-_Q4}k$6}2x&>iv*cG6Oc`at9n#lG|1 zIi>(iak!RTol#boyD`0c^v(cHJJuvHh-e39;{t(!nc@gWsV;O@FkUc{-h`pC817Ix zgh|QIatu;A!G^JZ7UC1V_vGb4bURuTWAy6SS-Fx(D=wcI#QP1Y#wzX?HAf0_+~lp> yN?iGbw2JFgJjd0vnp9WIo>K3V$tfee6;KE|`1A3J$tp?9B&Y7`+Gwrtzls-lP-;g2 literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/issue11169.tar b/archive/tar/testdata/issue11169.tar new file mode 100644 index 0000000000000000000000000000000000000000..4d71fa15260609ecee0c8c751cfebf49be8763ac GIT binary patch literal 602 zcmdPX4@j)=NKH&hEh^SCG%+zV)=x}KWS}ZA00J`;69y0s1n9JZp|KHzp^>Svp`nSX svAH3G0gzz?R8~P%SKu(Lw74X(2$wLeQpM#*<5EsKtJ9uH<6)~%E3&=F&Kzfi~{+q#!cOa=AzbjSO$1IxR;aN z*g3HiZShf(vs!BvbKPiG1!!GY>l3F=j$kqh!InX?lk@{OAsyh#2%!qzcGBC1;;FXq z6(OUFiyZLnA-?TXGNUXTUA0xix-DD8En}aw{C@tp7XnB4F0(23$NLG-y>mPO&s&?~ literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/nil-uid.tar b/archive/tar/testdata/nil-uid.tar new file mode 100644 index 0000000000000000000000000000000000000000..cc9cfaa33cc5de0a28b4183c1705d801f788c96a GIT binary patch literal 1024 zcmWGAG%z(VGPcn33UJrU$xmmX0WbgpGcywmlR@HOU}(l*Xk=(?U}j`)Zf3?{U^3;ld-=Ii;m6ILFVT3VXy>=Udb`;P z_AF}Ko(kwITGLdEM1G)5o<9H!jr=439(@V?ONRXCAu*5YPw-#9x&N1V|EJgyTG0B^ zUZ)s9!5N^&Ghph+I3UGBWYR$X|2zH<_}9R{M*cI=m|k}8li%1Drp7@Vny>jkUkM=z Sl}Br1`$oQ%|3`N;j=%%-SrC5! literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/pax-path-hdr.tar b/archive/tar/testdata/pax-path-hdr.tar new file mode 100644 index 0000000000000000000000000000000000000000..ab8fc325b26159f4fed6bfb59fe5f616d35fec74 GIT binary patch literal 1024 zcmXR&EXmL>$=5GRO-#v6r43~O0Sq{30|OI7m>ft6gMqP;fsrYLLIndIKxuJFViC}K xO07co9Hr*bNx!kNLIE%d*akR880v$Gocz3WU67b=USe)47oFTOYR$le004hRKE?n5 literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/pax.tar b/archive/tar/testdata/pax.tar new file mode 100644 index 0000000000000000000000000000000000000000..9bc24b6587d726c7fca4e533d9c61a3801a34688 GIT binary patch literal 10240 zcmeH~&u-H|5XOD(Q}_vz`9HIV-Z}CL1|qeBRwxNlAD?kbq{vMJQj94uds*3I@2-Ed zpXb|Q{eF0Qw;4Wdw!4)@_!@~t&7&b8A|a!oqM>78BOoLqCLtvwr=Z5b$i&RT%Er#Y zO+ZjcSVUAHn~8MUp(~vBV+cg7LjpEKCCE6D7E@EwTcMv_>l+&bbg`j1Cv0A776ym5t@+ zSt9MDBFtXbKY&m4pMN0f`l~hhD>#q(-`x$5n+q@eEPmAevA;0XTM8XMYkTvSmQ-t5 zkihVw{(qQ#_JjT})&KMa&-FhG0c8or{CPvw|Jf69WL!B2Wa1KoKYcMW6^2fg(@@ia-%40!5$*6oDd81d2cr_`3;w E2V3|JA^-pY literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/small.txt b/archive/tar/testdata/small.txt new file mode 100644 index 0000000..b249bfc --- /dev/null +++ b/archive/tar/testdata/small.txt @@ -0,0 +1 @@ +Kilts \ No newline at end of file diff --git a/archive/tar/testdata/small2.txt b/archive/tar/testdata/small2.txt new file mode 100644 index 0000000..394ee3e --- /dev/null +++ b/archive/tar/testdata/small2.txt @@ -0,0 +1 @@ +Google.com diff --git a/archive/tar/testdata/sparse-formats.tar b/archive/tar/testdata/sparse-formats.tar new file mode 100644 index 0000000000000000000000000000000000000000..8bd4e74d50f9c8961f80a887ab7d6449e032048b GIT binary patch literal 17920 zcmeHO!BXQ!5M{6a3g^xmb&sU64qQV{sZ?#{1DvdPiv%!*Aw}}_dEEjdi|Nre#)hpG zE{}(KnjXC;wbY|&t*;k1>*dFY-EbwpT`b+-m>u zXfjaoe5W44g1VMFbuvaLV|3acePf?HHj7T34f|}^XTyHz*zDR5hW%jJ$GN!K=dPX7 zuwNSXOT&I?*sl!xm0`a!>{o{UdfWbohf`t0wKm47jd5yYoVY#C#(p&HN5g(h+o$d^ z>C~x6+ovLJp9;gi;Rj^+0U3Tkh98jO2W0pG8Gb;9ACTb()boS>@h8I{`Aj1#H@B=dZfDAvtjWMmW;RoC~7Py0M z`m*5%-1CF}@n^#y*zgB7{DBRBV8b8S@CP>hfen8^_^{DnOAo^z5MmhHr;h_0e!zww zu;B-6_yHS!z=j{N;RkH^0r&ji+3`30fen9P!ynl22R8fx0blw!^!(v@`{T)$#0AMUzUr{%bWEKK}dPBZfAtotM&Q)$6}V4GkAAL?ydIxj}Q`3 zJOATY>UD~$8khO$y?3COY_Ib_T!Mz?cSHC~#(oEVI84ue{e9LR^x69SzvU?x#e`$G z`ReZSkBilxf3HuQYO>v9_2tWYd3#C|uKGRxyy zk#CN0vO|t>vO|t?vO|t@vV)g2dr7mGGDo)W_L8o>q-!tf+DkfmNk=c~=p`M!q@$Pg+)H}y zB|Z0&o_k5py`&p2>BdW1A|f+1N!@lEFX<*ndTZ#%;H1d0PWQ;sPWQ<1PWQ+WPxo*$ zCpU9)GbcB5ax*74^K5XIR5u%)rF*!UXXCT<7;fg-2rW5AHbhJJa5K*aY3VWC%(G!y za*S-8mhRzZo{iMfW4M`TW3}WM*$8{;Fcc4%{&{rCCA9dZs{Iw=Go{iJw}H4J9rlMBkscMKka?4V*dGW zC;x{dmiMq8gvD(vpG{xk(ev}2>9_pg&wuy1`g67#*MIt_+k5+eaQ%mN-{S%QM+!~( zxc)<2*YN+U4#l|sv%B)c7PePszGeL<)ZO)vtHtH=w09GsNfox9d|WQBPwAMB1HKi$ z5#I)1l17qNl4g>25`gi0%mT0gEC34-1PB5I0fGQQfKq@`fKq@`fKq@;fJ%T$fJ%T$ zfLefBfLefBfLeekKolSf5Cw<=%mtVWFc)Ahz+8YvfJT5ufJT5u0A#CaDG)Nzv=opE zMO*%@0IdS81gZg+MP*A>0a;*L*S;zQ^1P%)r9keM))iGXNaa8-mb9xN$g|SAj;op= zlS*1t6=X?iT~QSVc~H`#(jdo4>x!y6$YPQf)rV9dQiVt*BGrggBvO?~WSR`0jpG)F zR$z95<=;=bg;QIfR|BZ{k=7%FW59w-S{C9wpVT}I{Ao4pNVj%vb z{pbH+{)aiAzW>2BZdt7HACK|hLCzZHZZvnf_-l0|IXl~}=T~SgCPR@QPL^Kc(9Lpj zv56@U!e<=Br@-+2fA>qk!2KVorji&Q8CK+X>e0g#)6 z@dUuK3}6apYu09*vX znm!5vu=b8Z0L;v^6bQklmI7jCCS}XN6`)n1l|VJX%uKdX6)-c?y7pBeFf)@Dl>##} ztt+Z(U}h#Qst0CfT31vh!CNoVqM~4CrgcSC7r2GAs4|$DXJmbGf$=ejIaDU{qjK;EfgdABYgg9smFU literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/star.tar b/archive/tar/testdata/star.tar new file mode 100644 index 0000000000000000000000000000000000000000..59e2d4e604611eeac3e2a0f3d6f71d2623c50449 GIT binary patch literal 3072 zcmeHHT?)e>4DRzz;R#BjQ;)ERouag*479>@u-$$NbG3!-WyoMlUh?xvNIv}HZD&jy zuA!-C5KZlY0Y@bP833Zfm_JQ2M2yBQ2dTK6K{>VDLBV=D{z>IvVF` zUD#xgUGh?F1AikeIW6NnOIrMRGU4UU`62nAWxyx>^STEhN#m{lQEc_EhA!%P7hKg3OW)iVh<3F#{;S{=CgM+^V}b=Pyosep=F7x+*OI&?Q6F7LxR?fb`B^0 zttmJo<+m$~$Msw9KQ_wfqfW1sDJ#zsWq25)8&TsKn~+7*oF6~$0+nD?L2C$TBQM>$ zcQqo7Eo8w%PL1H9D9KY4`f7Ku6*oaxOv|7S1ZpTT=%sbGS#L2%*Uxm5P}U`mG$%9I zIRuF>849Ugh}k{mmgLJGtca9XnA{r2KBJ`fRXOnPqEZjWtz4z5Mq_`uZWX)VuFVko z#$DNd>@Saj1w+|ef@dPC=g!5Kb4c+mQ$bcu#R_I=;>D)V&agmv_`vpStP-sQh!z(| z5{9v2$DGKS|DrLqZ8y7?ox@|a-R^30zMeK388O@+r+cK=9NdZ)ow#}n{kwf`tD4=t zR9Oz?v}2QNv&rDR7u$%4%_>%5(k#={r!rZ(5WA5+U_Rz+w6{_kV7C!KK2e+5VuS-5 zWLRhpI#>Iq%|mhyZxDfRHCi7gC+R&sD4|k;~pXMNUD{^NPC5(KX~`*cXkwAurz(+XVNg z1P`yNv%FZy)9`uTGCWUJ^@>e2&T3O`W@re{ZN S9gn%XW0CXwKYp`+7X1&(X!Xnh literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/ustar.tar b/archive/tar/testdata/ustar.tar new file mode 100644 index 0000000000000000000000000000000000000000..29679d9a305fc0293f31212541335af824ab32c7 GIT binary patch literal 2048 zcmYex%t_TNsVHHfAus>}GZPaA5N&Q|3Z@N=AbgM*P?o{a$k4#V#K6eR)QrKv#MIE( zgh9c8hHiozU0Pg{SOj!ZaYkZZDqIwk0aTWjhA9jefq29K;yD8YhMfGo^t{B}RQ&;E gz@3MSk&&8{lh1`qc2s;c1V%$(Gz3ONV7P_=0FTf|dgec!sXT^AK{$jKc@-kWdUe(&uh-&e0L+xARj zwLzm>LI~3|1sT#R&XkBIzWbfCPrYEK7fr^Q@7vXO;&pw$QCTT3-?&yO+jq(<{6qS`FS_vP zIBhMBjnmsnS~{|C9LMN8#r!W{zj5l&zcE?^U_t*||1zJ{zqInH{-Zy}2$O|c?WSFx zxn8RtM3-UpAJiW`Z@Zar#$ojz)NjtWBfnULUzD=jj5!>iG>O2k{o(=ZAg=$-urC7q zVm{n!{kK`S@p|Vk`q%aFg#nw)bMB-40yAj*%7=F37m@ziFINBH7pTSD@Cfil^^9T6 zxL-iu+Aq)#ev#CF(l2&S@A^eC<`;^e4{ZQ#s9$Y4r}$iP3;;e3V;a&MNN*s$f%FFc H(;N5+1FUK9 literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/writer-big-long.tar b/archive/tar/testdata/writer-big-long.tar new file mode 100644 index 0000000000000000000000000000000000000000..5960ee824784ffeacb976a9c648be41b0281508b GIT binary patch literal 4096 zcmeIuJqp7x3tu-|!r}ytVByrmfae ipO37m$1T~NWs?FFpa2CZKmiI+fC3bt00k&;vcMnFf)<_t literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/writer.tar b/archive/tar/testdata/writer.tar new file mode 100644 index 0000000000000000000000000000000000000000..e6d816ad0775d56d09242d6f5d1dbe56af310a32 GIT binary patch literal 3584 zcmeHIK@P$o5ajGDd_l9j6nKIMUt!cVjT92WM1L@81h#LhDgML6Bon)c?rO_kPgyt^3D0fH9$GJM`O*&4VCw= zv#H)UKC-TtzNwGuV$*%C{bm zsdIMLR{C5VZL^vBE!S4cfUeCYt@>GOiAt%sq7tp|_iN{x5cDreh9ME=K+wOCQm`$x j!znSk-v6Dy)}|V_!f*AilYjI7l|Jj-R%ReG@B;%+QQ}au literal 0 HcmV?d00001 diff --git a/archive/tar/testdata/xattrs.tar b/archive/tar/testdata/xattrs.tar new file mode 100644 index 0000000000000000000000000000000000000000..9701950edd1f0dc82858b7117136b37391be0b08 GIT binary patch literal 5120 zcmeHJv2KGf5M|~o_yWg1+khiw>d;i}P^nX=$R$ooYd`|ilD{uBAv6g^kxC>6-(uu< zHg^v_-l5r}td>fyRbC(vfcdOQq}Iq(#u+Ja9X?}Dv(|CCVoJF~09ZgF;2a!G7^%~| zYNYoMUQ-rE=5KzzBJ^EKyr-Mx-NQ4gq%k=v3zee}wOxElT`HH-ei(K*xV|_} zC{$GDvDuoW?o>&odUrVuVHkt_w?IH zW3PV_@V!Jxt@A^i>Yrj(>;K=H?5X8!tJS~MYVd#a^`?|QJKb&Uduf~MfN4M7$J!Lr zF40zZMF!9x{tqJ#0F5+;{2!=)=Knre|G(mAKU`hAc#r>!#{V(9d;sW1hxVv7@B_zF ze)#eKF~#1~>@WTI`#+&4`lkel_5U6!N8h^5vRAE8lqGgr9-Ul!p=H1_U>TS&1K)l2 B)fNB% literal 0 HcmV?d00001 diff --git a/archive/tar/writer.go b/archive/tar/writer.go index 05027a3..30d7e60 100644 --- a/archive/tar/writer.go +++ b/archive/tar/writer.go @@ -47,7 +47,7 @@ type formatter struct { } // NewWriter creates a new Writer writing to w. -func NewWriter(w io.Writer) *Writer { return &Writer{w: w} } +func NewWriter(w io.Writer) *Writer { return &Writer{w: w, preferPax: true} } // Flush finishes writing the current file (optional). func (tw *Writer) Flush() error { @@ -201,23 +201,29 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error { tw.usedBinary = true f.formatNumeric(b, x) } + var formatTime = func(b []byte, t time.Time, paxKeyword string) { + var unixTime int64 + if !t.Before(minTime) && !t.After(maxTime) { + unixTime = t.Unix() + } + formatNumeric(b, unixTime, paxNone) + + // Write a PAX header if the time didn't fit precisely. + if paxKeyword != "" && tw.preferPax && allowPax && (t.Nanosecond() != 0 || !t.Before(minTime) || !t.After(maxTime)) { + paxHeaders[paxKeyword] = formatPAXTime(t) + } + } // keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax pathHeaderBytes := s.next(fileNameSize) formatString(pathHeaderBytes, hdr.Name, paxPath) - // Handle out of range ModTime carefully. - var modTime int64 - if !hdr.ModTime.Before(minTime) && !hdr.ModTime.After(maxTime) { - modTime = hdr.ModTime.Unix() - } - f.formatOctal(s.next(8), hdr.Mode) // 100:108 formatNumeric(s.next(8), int64(hdr.Uid), paxUid) // 108:116 formatNumeric(s.next(8), int64(hdr.Gid), paxGid) // 116:124 formatNumeric(s.next(12), hdr.Size, paxSize) // 124:136 - formatNumeric(s.next(12), modTime, paxNone) // 136:148 --- consider using pax for finer granularity + formatTime(s.next(12), hdr.ModTime, paxMtime) // 136:148 s.next(8) // chksum (148:156) s.next(1)[0] = hdr.Typeflag // 156:157 @@ -265,6 +271,15 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error { } if allowPax { + if !hdr.AccessTime.IsZero() { + paxHeaders[paxAtime] = formatPAXTime(hdr.AccessTime) + } + if !hdr.ChangeTime.IsZero() { + paxHeaders[paxCtime] = formatPAXTime(hdr.ChangeTime) + } + if !hdr.CreationTime.IsZero() { + paxHeaders[paxCreationTime] = formatPAXTime(hdr.CreationTime) + } for k, v := range hdr.Xattrs { paxHeaders[paxXattr+k] = v } @@ -288,6 +303,16 @@ func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error { return tw.err } +func formatPAXTime(t time.Time) string { + sec := t.Unix() + usec := t.Nanosecond() + s := strconv.FormatInt(sec, 10) + if usec != 0 { + s = fmt.Sprintf("%s.%09d", s, usec) + } + return s +} + // splitUSTARPath splits a path according to USTAR prefix and suffix rules. // If the path is not splittable, then it will return ("", "", false). func splitUSTARPath(name string) (prefix, suffix string, ok bool) { diff --git a/archive/tar/writer_test.go b/archive/tar/writer_test.go index 6e91d90..a5c9382 100644 --- a/archive/tar/writer_test.go +++ b/archive/tar/writer_test.go @@ -720,3 +720,20 @@ func TestFormatNumeric(t *testing.T) { } } } + +func TestFormatPAXTime(t *testing.T) { + t1 := time.Date(2000, 1, 1, 11, 0, 0, 0, time.UTC) + t2 := time.Date(2000, 1, 1, 11, 0, 0, 100, time.UTC) + t3 := time.Date(1960, 1, 1, 11, 0, 0, 0, time.UTC) + t4 := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC) + verify := func(time time.Time, s string) { + p := formatPAXTime(time) + if p != s { + t.Errorf("for %v, expected %s, got %s", time, s, p) + } + } + verify(t1, "946724400") + verify(t2, "946724400.000000100") + verify(t3, "-315579600") + verify(t4, "0") +} diff --git a/backuptar/tar.go b/backuptar/tar.go index c988574..e578067 100644 --- a/backuptar/tar.go +++ b/backuptar/tar.go @@ -30,10 +30,6 @@ const ( const ( hdrFileAttributes = "fileattr" - hdrAccessTime = "accesstime" - hdrChangeTime = "changetime" - hdrCreateTime = "createtime" - hdrWriteTime = "writetime" hdrSecurityDescriptor = "sd" hdrMountPoint = "mountpoint" ) @@ -82,21 +78,7 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { return nil } -func win32TimeFromTar(key string, hdrs map[string]string, unixTime time.Time) syscall.Filetime { - if s, ok := hdrs[key]; ok { - n, err := strconv.ParseUint(s, 10, 64) - if err == nil { - return syscall.Filetime{uint32(n & 0xffffffff), uint32(n >> 32)} - } - } - return syscall.NsecToFiletime(unixTime.UnixNano()) -} - -func win32TimeToTar(ft syscall.Filetime) (string, time.Time) { - return fmt.Sprintf("%d", uint64(ft.LowDateTime)+(uint64(ft.HighDateTime)<<32)), time.Unix(0, ft.Nanoseconds()) -} - -// Writes a file to a tar writer using data from a Win32 backup stream. +// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream. // // This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. // @@ -104,30 +86,22 @@ func win32TimeToTar(ft syscall.Filetime) (string, time.Time) { // // MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value // -// MSWINDOWS.accesstime: The last access time, as a Filetime expressed as a 64-bit decimal value. -// -// MSWINDOWS.createtime: The creation time, as a Filetime expressed as a 64-bit decimal value. -// -// MSWINDOWS.changetime: The creation time, as a Filetime expressed as a 64-bit decimal value. -// -// MSWINDOWS.writetime: The creation time, as a Filetime expressed as a 64-bit decimal value. -// // MSWINDOWS.sd: The Win32 security descriptor, in SDDL (string) format // // MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink) func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error { name = filepath.ToSlash(name) hdr := &tar.Header{ - Name: name, - Size: size, - Typeflag: tar.TypeReg, - Winheaders: make(map[string]string), + Name: name, + Size: size, + Typeflag: tar.TypeReg, + ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()), + ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()), + AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()), + CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()), + Winheaders: make(map[string]string), } hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes) - hdr.Winheaders[hdrAccessTime], hdr.AccessTime = win32TimeToTar(fileInfo.LastAccessTime) - hdr.Winheaders[hdrChangeTime], hdr.ChangeTime = win32TimeToTar(fileInfo.ChangeTime) - hdr.Winheaders[hdrCreateTime], _ = win32TimeToTar(fileInfo.CreationTime) - hdr.Winheaders[hdrWriteTime], hdr.ModTime = win32TimeToTar(fileInfo.LastWriteTime) if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 { hdr.Mode |= c_ISDIR @@ -252,7 +226,7 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size return nil } -// Retrieves basic Win32 file information from a tar header, using the additional metadata written by +// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by // WriteTarFileFromBackupStream. func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) { name = hdr.Name @@ -260,10 +234,10 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win size = hdr.Size } fileInfo = &winio.FileBasicInfo{ - LastAccessTime: win32TimeFromTar(hdrAccessTime, hdr.Winheaders, hdr.AccessTime), - LastWriteTime: win32TimeFromTar(hdrWriteTime, hdr.Winheaders, hdr.ModTime), - ChangeTime: win32TimeFromTar(hdrChangeTime, hdr.Winheaders, hdr.ChangeTime), - CreationTime: win32TimeFromTar(hdrCreateTime, hdr.Winheaders, hdr.ModTime), + LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()), + LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()), + ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()), + CreationTime: syscall.NsecToFiletime(hdr.CreationTime.UnixNano()), } if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok { attr, err := strconv.ParseUint(attrStr, 10, 32) @@ -279,7 +253,7 @@ func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *win return } -// Writes a Win32 backup stream from the current tar file. Since this function may process multiple +// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple // tar file entries in order to collect all the alternate data streams for the file, it returns the next // tar file that was not processed, or io.EOF is there are no more. func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) { diff --git a/backuptar/tar_test.go b/backuptar/tar_test.go index f02b045..0e87a9e 100644 --- a/backuptar/tar_test.go +++ b/backuptar/tar_test.go @@ -4,6 +4,8 @@ import ( "bytes" "io/ioutil" "os" + "path/filepath" + "reflect" "testing" "github.com/Microsoft/go-winio" @@ -61,5 +63,22 @@ func TestRoundTrip(t *testing.T) { t.Fatal(err) } - ensurePresent(t, hdr.Winheaders, "fileattr", "sd", "accesstime", "changetime", "createtime", "writetime") + name, size, bi2, err := FileInfoFromHeader(hdr) + if err != nil { + t.Fatal(err) + } + + if name != filepath.ToSlash(f.Name()) { + t.Errorf("got name %s, expected %s", name, filepath.ToSlash(f.Name())) + } + + if size != fi.Size() { + t.Errorf("got size %d, expected %d", size, fi.Size()) + } + + if !reflect.DeepEqual(*bi, *bi2) { + t.Errorf("got %#v, expected %#v", *bi, *bi2) + } + + ensurePresent(t, hdr.Winheaders, "fileattr", "sd") } From 2245090e75322caaa2c74100fe38ae289fb47891 Mon Sep 17 00:00:00 2001 From: John Starks Date: Wed, 30 Mar 2016 12:25:13 -0700 Subject: [PATCH 2/4] Add GetFileID function --- fileinfo.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/fileinfo.go b/fileinfo.go index dc05a8b..d5acb72 100644 --- a/fileinfo.go +++ b/fileinfo.go @@ -9,22 +9,46 @@ import ( //sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx //sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle +const ( + fileBasicInfo = 0 + fileIDInfo = 0x12 +) + +// FileBasicInfo contains file access time and file attributes information. type FileBasicInfo struct { CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime FileAttributes uintptr // includes padding } +// GetFileBasicInfo retrieves times and attributes for a file. func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { bi := &FileBasicInfo{} - if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 0, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { - return nil, &os.PathError{"GetFileInformationByHandleEx", f.Name(), err} + if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} } return bi, nil } +// SetFileBasicInfo sets times and attributes for a file. func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { - if err := setFileInformationByHandle(syscall.Handle(f.Fd()), 0, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { - return &os.PathError{"SetFileInformationByHandle", f.Name(), err} + if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { + return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} } return nil } + +// FileIDInfo contains the volume serial number and file ID for a file. This pair should be +// unique on a system. +type FileIDInfo struct { + VolumeSerialNumber uint64 + FileID [16]byte +} + +// GetFileID retrieves the unique (volume, file ID) pair for a file. +func GetFileID(f *os.File) (*FileIDInfo, error) { + fileID := &FileIDInfo{} + if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil { + return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} + } + return fileID, nil +} From 3b18fb5af2781e6236cad6f13ca6e5f157f0a6ce Mon Sep 17 00:00:00 2001 From: John Starks Date: Wed, 30 Mar 2016 12:25:32 -0700 Subject: [PATCH 3/4] Add OpenForBackup function This works with RunWithPrivileges to allow opening files as an administrator without access checks. --- backup.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/backup.go b/backup.go index bfefd42..8649351 100644 --- a/backup.go +++ b/backup.go @@ -26,10 +26,18 @@ const ( BackupReparseData BackupSparseBlock BackupTxfsData +) +const ( StreamSparseAttributes = uint32(8) ) +const ( + WRITE_DAC = 0x40000 + WRITE_OWNER = 0x80000 + ACCESS_SYSTEM_SECURITY = 0x1000000 +) + // BackupHeader represents a backup stream of a file. type BackupHeader struct { Id uint32 // The backup stream ID @@ -239,3 +247,20 @@ func (w *BackupFileWriter) Close() error { } return nil } + +// OpenForBackup opens a file or directory, potentially skipping access checks if the backup +// or restore privileges have been acquired. +// +// If the file opened was a directory, it cannot be used with Readdir(). +func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) { + winPath, err := syscall.UTF16FromString(path) + if err != nil { + return nil, err + } + h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) + if err != nil { + err = &os.PathError{Op: "open", Path: path, Err: err} + return nil, err + } + return os.NewFile(uintptr(h), path), nil +} From 346aee80030c6b8827ea9eba0f53b5f65765639f Mon Sep 17 00:00:00 2001 From: John Starks Date: Wed, 30 Mar 2016 12:28:52 -0700 Subject: [PATCH 4/4] Add backuptar.BasicInfoHeader helper function This is useful for creating tar headers for things that don't have backup streams, such as links. --- backuptar/tar.go | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/backuptar/tar.go b/backuptar/tar.go index e578067..96069b0 100644 --- a/backuptar/tar.go +++ b/backuptar/tar.go @@ -78,21 +78,10 @@ func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error { return nil } -// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream. -// -// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. -// -// The additional Win32 metadata is: -// -// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value -// -// MSWINDOWS.sd: The Win32 security descriptor, in SDDL (string) format -// -// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink) -func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error { - name = filepath.ToSlash(name) +// BasicInfoHeader creates a tar header from basic file information. +func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header { hdr := &tar.Header{ - Name: name, + Name: filepath.ToSlash(name), Size: size, Typeflag: tar.TypeReg, ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()), @@ -108,7 +97,23 @@ func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size hdr.Size = 0 hdr.Typeflag = tar.TypeDir } + return hdr +} +// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream. +// +// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS. +// +// The additional Win32 metadata is: +// +// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value +// +// MSWINDOWS.sd: The Win32 security descriptor, in SDDL (string) format +// +// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink) +func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error { + name = filepath.ToSlash(name) + hdr := BasicInfoHeader(name, size, fileInfo) br := winio.NewBackupStreamReader(r) var dataHdr *winio.BackupHeader for dataHdr == nil {