patchfs.in 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. #! @PERL@ -w
  2. #
  3. # Written by Adam Byrtek <alpha@debian.org>, 2002
  4. #
  5. # Extfs to handle patches in context and unified diff format.
  6. # Known issues: When name of file to patch is modified during editing,
  7. # hunk is duplicated on copyin. It is unavoidable.
  8. use bytes;
  9. use strict;
  10. use POSIX;
  11. use File::Temp 'tempfile';
  12. # standard binaries
  13. my $lzma = 'lzma';
  14. my $bzip = 'bzip2';
  15. my $gzip = 'gzip';
  16. my $fileutil = 'file';
  17. # date parsing requires Date::Parse from TimeDate module
  18. my $parsedates = eval 'require Date::Parse';
  19. # regular expressions
  20. my $unified_header=qr/^--- .*\n\+\+\+ .*\n@@ .* @@.*\n$/;
  21. my $unified_extract=qr/^--- ([^\s]+).*\n\+\+\+ ([^\s]+)\s*([^\t\n]*)/;
  22. my $unified_contents=qr/^([+\-\\ \n]|@@ .* @@)/;
  23. my $context_header=qr/^\*\*\* .*\n--- .*\n\*{15}\n$/;
  24. my $context_extract=qr/^\*\*\* ([^\s]+).*\n--- ([^\s]+)\s*([^\t\n]*)/;
  25. my $context_contents=qr/^([!+\-\\ \n]|-{3} .* -{4}|\*{3} .* \*{4}|\*{15})/;
  26. my $ls_extract_id=qr/^[^\s]+\s+[^\s]+\s+([^\s]+)\s+([^\s]+)/;
  27. my $basename=qr|^(.*/)*([^/]+)$|;
  28. sub patchfs_canonicalize_path ($) {
  29. my ($fname) = @_;
  30. $fname =~ s,/+,/,g;
  31. $fname =~ s,(^|/)(?:\.?\./)+,$1,;
  32. return $fname;
  33. }
  34. # output unix date in a mc-readable format
  35. sub timef
  36. {
  37. my @time=localtime($_[0]);
  38. return sprintf '%02d-%02d-%02d %02d:%02d', $time[4]+1, $time[3],
  39. $time[5]+1900, $time[2], $time[1];
  40. }
  41. # parse given string as a date and return unix time
  42. sub datetime
  43. {
  44. # in case of problems fall back to 0 in unix time
  45. # note: str2time interprets some wrong values (eg. " ") as 'today'
  46. if ($parsedates && defined (my $t=str2time($_[0]))) {
  47. return timef($t);
  48. }
  49. return timef(time);
  50. }
  51. # print message on stderr and exit
  52. sub error
  53. {
  54. print STDERR $_[0], "\n";
  55. exit 1;
  56. }
  57. # (compressed) input
  58. sub myin
  59. {
  60. my ($qfname)=(quotemeta $_[0]);
  61. $_=`$fileutil $qfname`;
  62. if (/lzma/) {
  63. return "$lzma -dc $qfname";
  64. } elsif (/bzip/) {
  65. return "$bzip -dc $qfname";
  66. } elsif (/gzip/) {
  67. return "$gzip -dc $qfname";
  68. } else {
  69. return "cat $qfname";
  70. }
  71. }
  72. # (compressed) output
  73. sub myout
  74. {
  75. my ($qfname,$append)=(quotemeta $_[0],$_[1]);
  76. my ($sep) = $append ? '>>' : '>';
  77. $_=`$fileutil $qfname`;
  78. if (/lzma/) {
  79. return "$lzma -c $sep $qfname";
  80. } elsif (/bzip/) {
  81. return "$bzip -c $sep $qfname";
  82. } elsif (/gzip/) {
  83. return "$gzip -c $sep $qfname";
  84. } else {
  85. return "cat $sep $qfname";
  86. }
  87. }
  88. # select diff filename conforming with rules found in diff.info
  89. sub diff_filename
  90. {
  91. my ($fsrc,$fdst)= @_;
  92. $fsrc = patchfs_canonicalize_path ($fsrc);
  93. $fdst = patchfs_canonicalize_path ($fdst);
  94. if (!$fdst && !$fsrc) {
  95. error 'Index: not yet implemented';
  96. } elsif (!$fsrc || $fsrc eq '/dev/null') {
  97. return ($fdst,'PATCH-CREATE/');
  98. } elsif (!$fdst || $fdst eq '/dev/null') {
  99. return ($fsrc,'PATCH-REMOVE/');
  100. } elsif (($fdst eq '/dev/null') && ($fsrc eq '/dev/null')) {
  101. error 'Malformed diff';
  102. } else {
  103. # fewest path name components
  104. if ($fdst=~s|/|/|g < $fsrc=~s|/|/|g) {
  105. return ($fdst,'');
  106. } elsif ($fdst=~s|/|/|g > $fsrc=~s|/|/|g) {
  107. return ($fsrc,'');
  108. } else {
  109. # shorter base name
  110. if (($fdst=~/$basename/,length $2) < ($fsrc=~/$basename/,length $2)) {
  111. return ($fdst,'');
  112. } elsif (($fdst=~/$basename/,length $2) > ($fsrc=~/$basename/,length $2)) {
  113. return ($fsrc,'');
  114. } else {
  115. # shortest names
  116. if (length $fdst < length $fsrc) {
  117. return ($fdst,'');
  118. } else {
  119. return ($fsrc,'');
  120. }
  121. }
  122. }
  123. }
  124. }
  125. # parse unified or context header
  126. sub parse_header
  127. {
  128. my ($unified,$context,$buf)=@_;
  129. if ($unified) {
  130. error "Can't parse unified diff header"
  131. unless ((($$buf.=<I>).=<I>)=~/$unified_header/);
  132. return $$buf=~/$unified_extract/;
  133. } elsif ($context) {
  134. error "Can't parse context diff header"
  135. unless ((($$buf.=<I>).=<I>)=~/$context_header/);
  136. return $$buf=~/$context_extract/;
  137. }
  138. }
  139. # list files affected by patch
  140. sub list
  141. {
  142. my ($archive)=(quotemeta $_[0]);
  143. my ($state,$pos,$len,$time);
  144. my ($f,$fsrc,$fdst,$prefix);
  145. my ($unified,$context)=(0,0);
  146. # use uid and gid from file
  147. my ($uid,$gid)=(`ls -l $archive`=~/$ls_extract_id/);
  148. import Date::Parse if ($parsedates);
  149. # state==1 means diff contents, state==0 means comments
  150. $state=0; $len=0; $f='';
  151. while (<I>) {
  152. # recognize diff type
  153. if (!$unified && !$context) {
  154. $unified=1 if (/^--- /);
  155. $context=1 if (/^\*\*\* /);
  156. if (!$unified && !$context) {
  157. $len+=length;
  158. next;
  159. }
  160. }
  161. if (($unified && /^--- /) || ($context && /^\*\*\* [^\*]*$/)) {
  162. # start of new file
  163. if ($state==1) {
  164. printf "-rw-r--r-- 1 %s %s %d %s %s%s\n", $uid, $gid, $len, datetime($time), $prefix, $f
  165. if $f;
  166. $len=0;
  167. }
  168. $state=1;
  169. ($fsrc,$fdst,$time)=parse_header($unified,$context,\$_);
  170. ($f,$prefix)=diff_filename($fsrc,$fdst);
  171. $f=$f.".diff";
  172. } elsif ($state==1 && (($unified && !/$unified_contents/) || ($context && !/$context_contents/))) {
  173. # start of comments, end of diff contents
  174. printf "-rw-r--r-- 1 %s %s %d %s %s%s\n", $uid, $gid, $len, datetime($time), $prefix, $f
  175. if $f;
  176. $state=$len=0;
  177. }
  178. $len+=length;
  179. }
  180. printf "-rw-r--r-- 1 %s %s %d %s %s%s\n", $uid, $gid, $len, datetime($time), $prefix, $f
  181. if ($f && $state==1);
  182. }
  183. # extract diff from patch
  184. sub copyout
  185. {
  186. my ($file,$out)=@_;
  187. my ($fsrc,$fdst,$found,$state,$buf);
  188. my ($unified,$context)=(0,0);
  189. $file=~s/^(PATCH-(CREATE|REMOVE)\/)?(.*)\.diff$/$3/;
  190. $file = patchfs_canonicalize_path ($file);
  191. # state==1 means diff contents, state==0 mens comments
  192. $state=0; $found=0; $buf='';
  193. while (<I>) {
  194. # recognize diff type
  195. if (!$unified && !$context) {
  196. $unified=1 if (/^--- /);
  197. $context=1 if (/^\*\*\* /);
  198. if (!$unified && !$context) {
  199. $buf.=$_;
  200. next;
  201. }
  202. }
  203. if (($unified && /^--- /) || ($context && /^\*\*\* [^\*]*$/)) {
  204. last if ($state==1 && $found);
  205. $state=1;
  206. ($fsrc,$fdst,)=parse_header($unified,$context,\$_);
  207. $fsrc = patchfs_canonicalize_path ($fsrc);
  208. $fdst = patchfs_canonicalize_path ($fdst);
  209. $found=1 if (($fsrc eq $file) || ($fdst eq $file));
  210. } elsif ($state==1 && (($unified && !/$unified_contents/) || ($context && !/$context_contents/))) {
  211. # start of comments, end of diff contents
  212. last if ($found);
  213. $state=0;
  214. $buf='';
  215. }
  216. $buf.=$_ if ($found || $state==0)
  217. }
  218. if ($found) {
  219. open O, "> $out";
  220. print O $buf;
  221. close O;
  222. }
  223. }
  224. # remove diff(s) from patch
  225. sub rm
  226. {
  227. my ($archive)=(shift);
  228. my ($fsrc,$fdst,$found,$state,$buf);
  229. my ($tmp,$tmpname)=tempfile();
  230. my ($unified,$context)=(0,0);
  231. @_=map {scalar(s/^(PATCH-(CREATE|REMOVE)\/)?(.*)\.diff$/$3/,$_)} @_;
  232. # state==1 means diff contents, state==0 mens comments
  233. $state=0; $found=0; $buf='';
  234. while (<I>) {
  235. # recognize diff type
  236. if (!$unified && !$context) {
  237. $unified=1 if (/^--- /);
  238. $context=1 if (/^\*\*\* /);
  239. if (!$unified && !$context) {
  240. $buf.=$_;
  241. next;
  242. }
  243. }
  244. if (($unified && /^--- /) || ($context && /^\*\*\* [^\*]*$/)) {
  245. $state=1;
  246. ($fsrc,$fdst,)=parse_header($unified,$context,\$_);
  247. # remove listed files
  248. foreach (@_) {
  249. if (($fsrc eq $_) || ($fdst eq $_)) {
  250. $found=1;
  251. last;
  252. }
  253. }
  254. if (!$found) {
  255. print $tmp $buf;
  256. $buf='';
  257. }
  258. } elsif ($state==1 && (($unified && !/$unified_contents/) || ($context && !/$context_contents/))) {
  259. # start of comments, end of diff contents
  260. $found=0;
  261. $state=0;
  262. $buf='';
  263. }
  264. if ($state==0) {
  265. $buf.=$_;
  266. } elsif (!$found) {
  267. print $tmp $_;
  268. }
  269. }
  270. print $tmp $buf if (!$found);
  271. close $tmp;
  272. close I;
  273. # replace archive with temporary file
  274. system('cat '.quotemeta($tmpname).'|'.myout($archive,0))==0
  275. or error "Can't write to archive";
  276. system 'rm -f '.quotemeta($tmpname);
  277. }
  278. # append diff to archive
  279. sub copyin
  280. {
  281. my ($archive,$name,$src)=(@_);
  282. my ($fsrc,$fdst,$f,@files);
  283. my ($unified,$context)=(0,0);
  284. # build filelist
  285. open I, myin($src).'|';
  286. while (<I>) {
  287. # recognize diff type
  288. if (!$unified && !$context) {
  289. $unified=1 if (/^--- /);
  290. $context=1 if (/^\*\*\* /);
  291. }
  292. if (($unified && /^--- /) || ($context && /^\*\*\* [^\*]*$/)) {
  293. ($fsrc,$fdst,)=parse_header($unified,$context,\$_);
  294. ($f,)=diff_filename($fsrc,$fdst);
  295. push(@files,$f);
  296. }
  297. }
  298. close I;
  299. # remove overwrited files
  300. open I, myin($archive).'|';
  301. rm ($archive, map($_.'.diff',@files));
  302. close I;
  303. my $cmd1=myin($src);
  304. my $cmd2=myout($archive,1);
  305. system("$cmd1 | $cmd2")==0
  306. or error "Can't write to archive";
  307. }
  308. if ($ARGV[0] eq 'list') {
  309. open I, myin($ARGV[1]).'|';
  310. list $ARGV[1];
  311. exit 0;
  312. } elsif ($ARGV[0] eq 'copyout') {
  313. open I, myin($ARGV[1])."|";
  314. copyout ($ARGV[2], $ARGV[3]);
  315. exit 0;
  316. } elsif ($ARGV[0] eq 'rm') {
  317. open I, myin($ARGV[1])."|";
  318. rm ($ARGV[1], $ARGV[2]);
  319. exit 0;
  320. } elsif ($ARGV[0] eq 'rmdir') {
  321. exit 0;
  322. } elsif ($ARGV[0] eq 'mkdir') {
  323. exit 0;
  324. } elsif ($ARGV[0] eq 'copyin') {
  325. copyin ($ARGV[1], $ARGV[2], $ARGV[3]);
  326. exit 0;
  327. }
  328. exit 1;