2 # Copyright (c) 2002-2003
\r
3 # The President and Fellows of Harvard College.
\r
5 # Redistribution and use in source and binary forms, with or without
\r
6 # modification, are permitted provided that the following conditions
\r
8 # 1. Redistributions of source code must retain the above copyright
\r
9 # notice, this list of conditions and the following disclaimer.
\r
10 # 2. Redistributions in binary form must reproduce the above copyright
\r
11 # notice, this list of conditions and the following disclaimer in the
\r
12 # documentation and/or other materials provided with the distribution.
\r
13 # 3. Neither the name of the University nor the names of its contributors
\r
14 # may be used to endorse or promote products derived from this software
\r
15 # without specific prior written permission.
\r
17 # THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
\r
18 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
\r
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
\r
20 # ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
\r
21 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
\r
22 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
\r
23 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
\r
24 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
\r
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
\r
26 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
\r
29 # $Id: hier.pl,v 1.14 2003/07/26 20:52:03 ellard Exp $
\r
31 # hier.pl - Tools to map out the file system hierarchy. This is
\r
32 # accomplished by snooping out the lookup calls.
\r
34 # This is expensive because the hierarchy can require a LOT of space
\r
35 # to store for a large system with lots of files (especially if files
\r
36 # come and go). Don't construct the hierarchy unless you want it --
\r
37 # and be prepared to prune it from time to time.
\r
41 # Tables used by the outside world:
\r
53 #RFS: dependency table
\r
57 %fhType = (); # we use %fhIsDir instead
\r
65 # Library-private tables and variables.
\r
67 %pendingCallsXIDnow = ();
\r
68 %pendingCallsXIDfh = ();
\r
69 %pendingCallsXIDname = ();
\r
71 $nextPruneTime = -1;
\r
72 $PRUNE_INTERVAL = 5 * 60; # Five minutes.
\r
75 my ($line, $proto, $op, $xid, $client, $now, $response, $fh_type) = @_;
\r
77 &addRfsAllFHs($line, $proto, $op, $uxid,
\r
78 $now, $response, $fh_type);
\r
80 if ($now > $nextPruneTime) {
\r
81 &prunePending ($now - $PRUNE_INTERVAL);
\r
82 $nextPruneTime = $now + $PRUNE_INTERVAL;
\r
85 my $uxid = "$client-$xid";
\r
87 # 'lookup', 'create', 'rename', 'delete',
\r
88 # 'getattr', 'setattr'
\r
90 #RFS: add mkdir/rmdir/symlink
\r
91 if ( $op eq 'lookup' || $op eq 'create' || $op eq 'mkdir' ||
\r
92 ($op eq 'symlink' && ($proto eq 'C3' || $proto eq 'R3' ) ) ) {
\r
93 return (&doLookup ($line, $proto, $op, $uxid,
\r
94 $now, $response, $fh_type));
\r
96 elsif ($op eq 'rename') {
\r
98 elsif ($op eq 'remove' || $op eq 'rmdir') {
\r
99 # RFS: why remove these entries? Just let them exist since
\r
100 # there is generation number available to distinguish btw removed dir/file
\r
101 # and new dir/file with the same inode number.
\r
102 #return (&doRemove ($line, $proto, $op, $uxid,
\r
103 # $now, $response, $fh_type));
\r
105 elsif ($op eq 'getattr' || $op eq 'read' || $op eq 'write' ||
\r
106 ($op eq 'readlink' && ($proto eq 'C3' || $proto eq 'R3' ) ) ) {
\r
107 return (&doGetAttr ($line, $proto, $op, $uxid,
\r
108 $now, $response, $fh_type));
\r
110 elsif ($op eq 'setattr') {
\r
120 if (! ($line =~ /^[0-9]/)) {
\r
121 print "getTimeStamp return undef\n";
\r
125 my @l = split (' ', $line, 2);
\r
131 my ($line, $proto, $op, $uxid, $now, $response, $fh_type) = @_;
\r
135 my $checkfh = undef;
\r
136 $checkfh = nfsd::nfsDumpParseLineField ($line, 'fh');
\r
137 if (defined $checkfh) {
\r
138 $fh = nfsd::nfsDumpCompressFH ($fh_type, $checkfh);
\r
143 if ($op eq 'rename' || $op eq 'link') {
\r
144 $checkfh = nfsd::nfsDumpParseLineField ($line, 'fh2');
\r
145 if (defined $checkfh) {
\r
146 $fh2 = nfsd::nfsDumpCompressFH ($fh_type, $checkfh);
\r
151 # record the first appearance of the fh
\r
152 if ( !exists $rfsAllFHs{$fh} ) {
\r
153 $rfsAllFHs{$fh} = $rfsLineNum ;
\r
157 if (defined $fh2) {
\r
158 if ( !exists $rfsAllFHs{$fh2} ) {
\r
159 $rfsAllFHs{$fh2} = $rfsLineNum;
\r
168 my ($line, $proto, $op, $uxid, $now, $response, $fh_type) = @_;
\r
170 if ($proto eq 'C3' || $proto eq 'C2') {
\r
171 my $tag = ($proto eq 'C3') ? 'name' : 'fn';
\r
172 my $name = nfsd::nfsDumpParseLineField ($line, $tag);
\r
174 # All directories have (at least) three names: the
\r
175 # given name, and "." and "..". We're only interested
\r
176 # in the given name.
\r
178 if ($name eq '"."' || $name eq '".."') {
\r
182 my $fh = nfsd::nfsDumpCompressFH ($fh_type,
\r
183 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
185 $pendingCallsXIDnow{$uxid} = $now;
\r
186 $pendingCallsXIDfh{$uxid} = $fh;
\r
187 $pendingCallsXIDname{$uxid} = $name;
\r
189 elsif ($proto eq 'R3' || $proto eq 'R2') {
\r
190 if (! exists $pendingCallsXIDnow{$uxid}) {
\r
194 my $pfh = $pendingCallsXIDfh{$uxid};
\r
195 my $name = $pendingCallsXIDname{$uxid};
\r
197 delete $pendingCallsXIDnow{$uxid};
\r
198 delete $pendingCallsXIDfh{$uxid};
\r
199 delete $pendingCallsXIDname{$uxid};
\r
201 if ($response eq 'OK') {
\r
202 my $cfh = nfsd::nfsDumpCompressFH ($fh_type,
\r
203 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
205 my $type = nfsd::nfsDumpParseLineField ($line, 'ftype');
\r
209 $fhIsDir{$cfh} = $type;
\r
213 # $fh2Parent{$cfh} = $pfh;
\r
214 # $fh2Name{$cfh} = $name;
\r
215 # $parent2fh{"$pfh,$name"} = $cfh;
\r
216 # RFS code: in case of the rename, we will record the name of the old name
\r
217 $fh2Parent{$cfh} = $pfh;
\r
218 if (! exists $fh2Name{$cfh}) {
\r
219 $fh2Name{$cfh} = $name;
\r
220 $parent2fh{"$pfh,$name"} = $cfh;
\r
222 # keep the old name in the fh2Name{$cfh}
\r
223 # and we also add the newname and pfh mapping
\r
224 #$fh2Name{$cfh} = $name;
\r
225 $parent2fh{"$pfh,$name"} = $cfh;
\r
228 my ($size, $mode, $atime, $mtime, $ctime, $nlink) =
\r
229 nfsd::nfsDumpParseLineFields ($line,
\r
231 'atime', 'mtime', 'ctime', 'nlink');
\r
232 my $ts = getTimeStamp($line);
\r
234 # RFS: modify here to get/maintain more file attributes
\r
235 # we can just check the ctime and compare it with trace-start-time
\r
236 # to decide whether to create a file/diretory.
\r
237 # atime - last access time of the file
\r
238 # mtime - last modification time of the file
\r
239 # ctime - last file status change time
\r
241 #$fh2Attr{$cfh} = "$size $mode $atime $mtime $ctime";
\r
242 if (! exists $fh2AttrOrig{$cfh} ) {
\r
243 $fh2AttrOrig{$cfh} = "$size $mode $op $atime $mtime $ctime $nlink $ts";
\r
245 $fh2Attr{$cfh} = "$size $mode $op $atime $mtime $ctime $nlink $ts";
\r
254 my ($line, $proto, $op, $uxid, $now, $response, $fh_type) = @_;
\r
256 if ($proto eq 'C3' || $proto eq 'C2') {
\r
257 my $tag = ($proto eq 'C3') ? 'name' : 'fn';
\r
258 my $name = nfsd::nfsDumpParseLineField ($line, $tag);
\r
260 # All directories have (at least) three names: the
\r
261 # given name, and "." and "..". We're only interested
\r
262 # in the given name.
\r
264 if ($name eq '"."' || $name eq '".."') {
\r
268 my $pfh = nfsd::nfsDumpCompressFH ($fh_type,
\r
269 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
271 if (! exists $parent2fh{"$pfh,$name"}) {
\r
275 $pendingCallsXIDnow{$uxid} = $now;
\r
276 $pendingCallsXIDfh{$uxid} = $pfh;
\r
277 $pendingCallsXIDname{$uxid} = $name;
\r
279 elsif ($proto eq 'R3' || $proto eq 'R2') {
\r
280 if (! exists $pendingCallsXIDnow{$uxid}) {
\r
284 my $pfh = $pendingCallsXIDfh{$uxid};
\r
285 my $name = $pendingCallsXIDname{$uxid};
\r
287 delete $pendingCallsXIDfh{$uxid};
\r
288 delete $pendingCallsXIDname{$uxid};
\r
289 delete $pendingCallsXIDnow{$uxid};
\r
291 if (! exists $parent2fh{"$pfh,$name"}) {
\r
295 my $cfh = $parent2fh{"$pfh,$name"};
\r
297 if ($response eq 'OK') {
\r
298 if ($op eq 'remove') {
\r
299 printFileInfo ($cfh, 'D');
\r
301 delete $fh2Parent{$cfh};
\r
302 delete $fh2Name{$cfh};
\r
303 delete $fh2Attr{$cfh};
\r
304 delete $fhs2AttrOrig{$cfg};
\r
305 delete $parent2fh{"$pfh,$name"};
\r
314 my ($line, $proto, $op, $uxid, $now, $response, $fh_type) = @_;
\r
316 if ($proto eq 'C3' || $proto eq 'C2') {
\r
317 my $fh = nfsd::nfsDumpCompressFH ($fh_type,
\r
318 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
320 #if (nfsd::nfsDumpParseLineField ($line, 'fh')
\r
321 # eq '00018961-57570100-d2440000-61890100') {
\r
322 # printf STDERR "Seen it ($op)\n";
\r
325 if (! defined $fh) {
\r
329 $pendingCallsXIDnow{$uxid} = $now;
\r
330 $pendingCallsXIDfh{$uxid} = $fh;
\r
332 #my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
333 #if ($fh eq $wantfh) {
\r
334 # print "JIAWU: doGetAttr call $wantfh\n";
\r
338 if (! exists $pendingCallsXIDnow{$uxid}) {
\r
342 my $fh = $pendingCallsXIDfh{$uxid};
\r
343 delete $pendingCallsXIDfh{$uxid};
\r
344 delete $pendingCallsXIDnow{$uxid};
\r
346 #my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
347 #if ($fh eq $wantfh) {
\r
348 # print "JIAWU: doGetAttr response $wantfh\n";
\r
351 if ($response ne 'OK') {
\r
355 my ($ftype) = nfsd::nfsDumpParseLineFields ($line, 'ftype');
\r
356 if (!defined $ftype) {
\r
357 print STDERR "BAD $line";
\r
363 $fhIsDir{$fh} = $ftype;
\r
366 #RFS comment: here if fh is a directory, then it will not be add
\r
367 # in the two hash table %fh2Attr(%fh2AttrOrig) and %fh2Name
\r
368 # if ($ftype != 1) {
\r
376 my ($mode, $size, $atime, $mtime, $ctime, $nlink) =
\r
377 nfsd::nfsDumpParseLineFields ($line,
\r
378 'mode', 'size', 'atime', 'mtime', 'ctime', 'nlink');
\r
379 my $ts = getTimeStamp($line);
\r
381 # RFS: modify here to get/maintain more file attributes
\r
382 # we can just check the ctime and compare it with trace-start-time
\r
383 # to decide whether to create a file/diretory.
\r
384 # atime - last access time of the file
\r
385 # mtime - last modification time of the file
\r
386 # ctime - last file status change time
\r
388 # $fh2Attr{$fh} = "$size $mode $atime $mtime $ctime";
\r
390 if (! exists $fh2AttrOrig{$fh} ) {
\r
391 $fh2AttrOrig{$fh} = "$size $mode $op $atime $mtime $ctime $nlink $ts";
\r
393 $fh2Attr{$fh} = "$size $mode $op $atime $mtime $ctime $nlink $ts";
\r
397 # Purge all the pending XID records dated earlier than $when (which is
\r
398 # typically at least $PRUNE_INTERVAL seconds ago). This is important
\r
399 # because otherwise missing XID records can pile up, eating a lot of
\r
405 foreach my $uxid ( keys %pendingCallsXIDnow ) {
\r
406 if ($pendingCallsXIDnow{$uxid} < $when) {
\r
408 my $fh = $pendingCallsXIDfh{$uxid};
\r
409 my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
410 if ($fh eq $wantfh) {
\r
411 print "JIAWU: prunePending $wantfh\n";
\r
414 delete $pendingCallsXIDnow{$uxid};
\r
421 # Return as much of the path for the given fh as possible. It may or
\r
422 # may not reach the root (or the mount point of the file system), but
\r
423 # right now we don't check. Usually on busy systems the data is
\r
424 # complete enough so that most paths are complete back to the mount
\r
431 my $MaxPathLen = 40;
\r
433 if (exists $fhIsDir{$fh} && $fhIsDir{$fh}==2) {
\r
438 while ($fh && exists $fh2Name{$fh}) {
\r
439 unshift (@path, $fh2Name{$fh});
\r
441 if ( ($fh2Name{$fh} ne '"RFSNN0"' ) ) {
\r
442 if (! exists $fh2Parent{$fh}) {
\r
443 print STDERR "$fh2Name{$fh} ";
\r
444 if ( ($fh2Name{$fh} eq '"RFSNN0"' ) ) {
\r
445 print STDERR "eq RFSNN0\n";
\r
447 print STDERR "NOT eq RFSNN0\n";
\r
450 if ($fh eq $fh2Parent{$fh}) {
\r
451 unshift (@path, '(LOOP)');
\r
456 if ($cnt++ > $MaxPathLen) {
\r
457 print STDERR "findPath: path too long (> $MaxPathLen)\n";
\r
458 unshift (@path, '(TOO-LONG)');
\r
462 $fh = $fh2Parent{$fh};
\r
465 # RFS: append the ~user (fh and !exists $fh2Name{$fh} and type is Directory)
\r
466 if ($fh && !exists $fh2Name{$fh} && (exists $fhIsDir{$fh} && $fhIsDir{$fh}==2) ) {
\r
467 if (exists $rootsName{$fh}) {
\r
468 #print "JIAWU: $rootsName{$fh}\n";
\r
469 unshift(@path, $rootsName{$fh});
\r
471 print "JIAWU: WARNING! No rootsName for this fh: $fh\n";
\r
472 unshift(@path, $fh);
\r
475 if ($fh && !exists $fh2Name{$fh} && (!exists $fhIsDir{$fh} || (exists $fhIsDir{$fh} && $fhIsDir{$fh}!=2)) ) {
\r
476 if (exists $discardFHs{$fh}) {
\r
477 open NOATTRDIR, ">>noattrdirdiscard" || die "open noattrdirdiscard failed\n";
\r
478 print NOATTRDIR "$fh DISCARD\n";
\r
481 # RFS: if a possible fh without attr and name, then regard it as a special root ~/RFSNN0
\r
482 unshift(@path, '"RFSNN0"');
\r
484 $fh2Name{$fh} = '"RFSNN0"';
\r
485 $rootsName{$fh} = '"RFSNN0"';
\r
486 open NOATTRDIR, ">>noattrdir-root";
\r
487 print NOATTRDIR "$fh /RFSNN0/\n";
\r
496 foreach my $p ( @path ) {
\r
511 return ($str, $cnt);
\r
515 $total_unknown_fh = 0;
\r
516 $total_known_fh = 0;
\r
519 my ($start_time, $out) = @_;
\r
526 # RFS print more information here
\r
527 open (OUT_RFS, ">rfsinfo") ||
\r
528 die "Can't create $OutFileBaseName.rfs.";
\r
530 foreach $fh ( keys %fh2Attr ) {
\r
533 foreach $fh ( keys %fh2Name ) {
\r
537 #RFS: before printFileInfo, name those roots' name
\r
539 #RFS there are three kind of fh
\r
540 # 1. fh/name paired (fh/attr must)
\r
541 # 2. fh/attr but no fh/name: type file (discard related operations)
\r
542 # 3. fh/attr but no fh/name: type dir (keep as persuedo root)
\r
545 foreach $fh ( keys %allfh ) {
\r
546 if (exists $fh2Parent{$fh} ) {
\r
551 my $type = (exists $fhIsDir{$fh} && $fhIsDir{$fh}==2) ? 'D' : 'F';
\r
552 if ($type eq 'D') {
\r
553 $rootsName{$fh} = sprintf("\"RFSNN%d\"", $sn++);
\r
554 $rootsFHs{$fh} = 2;
\r
557 $discardFHs{$fh} = 1;
\r
561 print OUT_RFS "#stat: fh with parent = $k, fh without parent = $u\n";
\r
562 $u = keys %rootsFHs;
\r
563 print OUT_RFS "#RFS: root fh list($u)\n";
\r
564 foreach $fh (keys %rootsName) {
\r
565 print OUT_RFS "#RFS: $rootsName{$fh} $fh\n";
\r
567 $u = keys %discardFHs;
\r
568 print OUT_RFS "#RFS: discard fh list($u)\n";
\r
569 print OUT_RFS join("\n", keys %discardFHs, "");
\r
572 print $out "#F type state fh path pathcount attrOrig(size,mode,op,atime,mt,ct) attrLast(size,mode,op,at,mt,ct)\n";
\r
574 print $out "#T starttime = $start_time\n";
\r
575 foreach $fh ( keys %allfh ) {
\r
576 printFileInfoOutputFile ($fh, 'A', $out);
\r
579 my $numfh2Name = keys %fh2Name;
\r
580 my $numfh2Attr = keys %fh2Attr;
\r
581 print OUT_RFS "fh2name has $numfh2Name, fh2Attr has $numfh2Attr\n";
\r
585 foreach $fh ( keys %allfh ) {
\r
586 if ( exists $fh2Name{$fh} ) {$k++;}
\r
589 print OUT_RFS "#stat: total fh with name = $k, without name = $u\n";
\r
591 print OUT_RFS "#stat: finally, total known fh = $total_known_fh, unknown = $total_unknown_fh\n";
\r
593 # Note: fh with name (8303), fh without name (103)
\r
595 # discard fh list: 85
\r
596 # known fh (8321): ( fh with name(8303) + root fh list (18) = 8321)
\r
599 # All fh from the those data structures: 8321 + 85 = 8303+103
\r
600 # Or, in keys %allfh
\r
603 print OUT_RFS "#RFS\n";
\r
606 open (MISSED, ">missdiscardfh") ||
\r
607 die "Can't create missdiscardfh.";
\r
608 foreach $fh (keys %rfsAllFHs) {
\r
609 if ( !exists $allfh{$fh} &&
\r
610 ( (defined $fh2Name{$fh}) && ($fh2Name{$fh} ne '"RFSNN0"')) ) {
\r
611 print MISSED "$fh LN: $rfsAllFHs{$fh}\n"
\r
616 # check for a special fh
\r
617 #my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
618 #if ($allfh{$wantfh} == 1) {
\r
619 # print OUT_RFS "JIAWU: found $wantfh\n";
\r
621 # print OUT_RFS "JIAWU: NOT found $wantfh\n";
\r
623 #foreach $fh ( keys %allfh ) {
\r
624 # if ( $fh eq $wantfh ) {
\r
625 # print OUT_RFS "JIAWU: found $wantfh\n";
\r
626 # printFileInfoOutputFile ($fh, 'JIAWU', *OUT_RFS);
\r
630 #print OUT_RFS "JIAWU: after \n";
\r
635 sub printFileInfoOutputFile {
\r
636 my ($fh, $state, $out) = @_;
\r
638 my ($p, $c) = findPath ($fh);
\r
640 if ($c == 0) {$total_unknown_fh++;}
\r
641 else {$total_known_fh++;}
\r
643 #my $type = (exists $fhIsDir{$fh} && $fhIsDir{$fh}==2) ? 'D' : 'F';
\r
644 my $type = $fhIsDir{$fh};
\r
645 if (!defined $type)
\r
647 print STDERR "unknown ftype(U) for fh: $fh\n";
\r
650 my $attr = (exists $fh2Attr{$fh}) ?
\r
651 $fh2Attr{$fh} : "-1 -1 -1 -1 -1 -1 -1 -1";
\r
652 my $attrOrig = (exists $fh2AttrOrig{$fh}) ?
\r
653 $fh2AttrOrig{$fh} : "-1 -1 -1 -1 -1 -1 -1 -1";
\r
655 print $out "F $type $state $fh $p $c $attrOrig $attr\n";
\r
658 sub printFileInfo {
\r
659 my ($fh, $state) = @_;
\r
661 my ($p, $c) = findPath ($fh);
\r
663 if ($c == 0) {$total_unknown_fh++;}
\r
664 else {$total_known_fh++;}
\r
666 my $type = (exists $fhIsDir{$fh} && $fhIsDir{$fh}==2) ? 'D' : 'F';
\r
667 my $attr = (exists $fh2Attr{$fh}) ?
\r
668 $fh2Attr{$fh} : "-1 -1 -1 -1 -1 -1 -1 -1";
\r
669 my $attrOrig = (exists $fh2AttrOrig{$fh}) ?
\r
670 $fh2AttrOrig{$fh} : "-1 -1 -1 -1 -1 -1 -1 -1";
\r
672 print "F $type $state $fh $p $c $attrOrig $attr\n";
\r
676 # The flow to create the dependency table
\r
678 # create(dirfh, name, attr) -->newfh, new attr
\r
679 # mkdir(dirfh, name, attr) -> newfh, new attr
\r
681 # remove(dirfh, name) --> status
\r
682 # rmdir(dirfh, name) --> status
\r
683 # rename(dirfh, name, todirfh, toname) --> status
\r
685 # link(newdirfh, newname, dirfh, name) --> status (newdir/newname=>dir/name)
\r
686 # syslink(newdirfh, newname, string) --> status (newdir/newname=>"string")
\r
687 # readlink(fh) --> string
\r
688 # lookup(dirfh, name) --> fh, attr
\r
689 # getattr(fh) --> attr
\r
690 # setattr(fh, attr) --> attr
\r
691 # read(fh, offset, count) -> attr, data
\r
692 # write(fh, offset, count, data) --> attr
\r
693 # readdir(dirfh, cookie, count) --> entries
\r
694 # statfs(fh) --> status
\r
699 # for each line the trace file:
\r
700 # if (op == R2 or R3) continue; #skip the response line
\r