patchfs.in 8.8 KB

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