1package ProFTPD::Tests::Modules::mod_statcache::sftp;
2
3use lib qw(t/lib);
4use base qw(ProFTPD::TestSuite::Child);
5use strict;
6
7use Cwd;
8use File::Path qw(mkpath);
9use File::Spec;
10use IO::Handle;
11use POSIX qw(:fcntl_h);
12
13use ProFTPD::TestSuite::FTP;
14use ProFTPD::TestSuite::Utils qw(:auth :config :running :test :testsuite);
15
16$| = 1;
17
18my $order = 0;
19
20my $TESTS = {
21  statcache_sftp_stat_file => {
22    order => ++$order,
23    test_class => [qw(forking sftp)],
24  },
25
26  statcache_sftp_stat_dir => {
27    order => ++$order,
28    test_class => [qw(forking sftp)],
29  },
30
31  statcache_sftp_upload_file => {
32    order => ++$order,
33    test_class => [qw(forking sftp)],
34  },
35
36};
37
38sub new {
39  return shift()->SUPER::new(@_);
40}
41
42sub list_tests {
43  # Check for the required Perl modules:
44  #
45  #  Net-SSH2
46  #  Net-SSH2-SFTP
47
48  my $required = [qw(
49    Net::SSH2
50    Net::SSH2::SFTP
51  )];
52
53  foreach my $req (@$required) {
54    eval "use $req";
55    if ($@) {
56      print STDERR "\nWARNING:\n + Module '$req' not found, skipping all tests\n";
57
58      if ($ENV{TEST_VERBOSE}) {
59        print STDERR "Unable to load $req: $@\n";
60      }
61
62      return qw(testsuite_empty_test);
63    }
64  }
65
66  return testsuite_get_runnable_tests($TESTS);
67}
68
69sub set_up {
70  my $self = shift;
71  $self->SUPER::set_up(@_);
72
73  # Make sure that mod_sftp does not complain about permissions on the hostkey
74  # files.
75
76  my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_statcache/ssh_host_rsa_key');
77
78  unless (chmod(0400, $rsa_host_key)) {
79    die("Can't set perms on $rsa_host_key: $!");
80  }
81}
82
83sub statcache_sftp_stat_file {
84  my $self = shift;
85  my $tmpdir = $self->{tmpdir};
86  my $setup = test_setup($tmpdir, 'statcache');
87
88  my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt");
89  if (open(my $fh, "> $test_file")) {
90    print $fh "Hello, World!\n";
91    unless (close($fh)) {
92      die("Can't write $test_file: $!");
93    }
94
95  } else {
96    die("Can't open $test_file: $!");
97  }
98
99  my $test_filemode = (stat($test_file))[2];
100  if ($ENV{TEST_VERBOSE}) {
101    print STDERR "# $test_file mode: $test_filemode\n";
102  }
103
104  my $test_filesize = (stat($test_file))[7];
105
106  my $statcache_tab = File::Spec->rel2abs("$tmpdir/statcache.tab");
107
108  my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_statcache/ssh_host_rsa_key');
109
110  my $config = {
111    PidFile => $setup->{pid_file},
112    ScoreboardFile => $setup->{scoreboard_file},
113    SystemLog => $setup->{log_file},
114    TraceLog => $setup->{log_file},
115    Trace => 'fsio:10 sftp:20 statcache:20',
116
117    AuthUserFile => $setup->{auth_user_file},
118    AuthGroupFile => $setup->{auth_group_file},
119
120    IfModules => {
121      'mod_delay.c' => {
122        DelayEngine => 'off',
123      },
124
125      'mod_sftp.c' => {
126        SFTPEngine => 'on',
127        SFTPLog => $setup->{log_file},
128        SFTPHostKey => $rsa_host_key,
129      },
130
131      'mod_statcache.c' => {
132        StatCacheEngine => 'on',
133        StatCacheTable => $statcache_tab,
134      },
135    },
136  };
137
138  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
139    $config);
140
141  # Open pipes, for use between the parent and child processes.  Specifically,
142  # the child will indicate when it's done with its test by writing a message
143  # to the parent.
144  my ($rfh, $wfh);
145  unless (pipe($rfh, $wfh)) {
146    die("Can't open pipe: $!");
147  }
148
149  require Net::SSH2;
150
151  my $ex;
152
153  # Fork child
154  $self->handle_sigchld();
155  defined(my $pid = fork()) or die("Can't fork: $!");
156  if ($pid) {
157    eval {
158      my $ssh2 = Net::SSH2->new();
159
160      sleep(1);
161
162      unless ($ssh2->connect('127.0.0.1', $port)) {
163        my ($err_code, $err_name, $err_str) = $ssh2->error();
164        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
165      }
166
167      unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
168        my ($err_code, $err_name, $err_str) = $ssh2->error();
169        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
170      }
171
172      my $sftp = $ssh2->sftp();
173      unless ($sftp) {
174        my ($err_code, $err_name, $err_str) = $ssh2->error();
175        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
176      }
177
178      my $path = 'test.txt';
179      my $attrs = $sftp->stat($path, 1);
180      unless ($attrs) {
181        my ($err_code, $err_name) = $sftp->error();
182        die("STAT $path failed: [$err_name] ($err_code)");
183      }
184
185      my $expected = $test_filesize;
186      my $file_size = $attrs->{size};
187      $self->assert($expected == $file_size,
188        test_msg("Expected file size '$expected', got '$file_size'"));
189
190      $expected = $<;
191      my $file_uid = $attrs->{uid};
192      $self->assert($expected == $file_uid,
193        test_msg("Expected file UID '$expected', got '$file_uid'"));
194
195      $expected = $(;
196      my $file_gid = $attrs->{gid};
197      $self->assert($expected == $file_gid,
198        test_msg("Expected file GID '$expected', got '$file_gid'"));
199
200      $expected = $test_filemode;
201      my $file_mode = $attrs->{mode};
202      $self->assert($expected == $file_mode,
203        test_msg("Expected file mode '$expected', got '$file_mode'"));
204
205      # Do the stat again; we'll check the logs to see if mod_statcache
206      # did its job.
207      $attrs = $sftp->stat($path, 1);
208      unless ($attrs) {
209        my ($err_code, $err_name) = $sftp->error();
210        die("STAT $path failed: [$err_name] ($err_code)");
211      }
212
213      $expected = $test_filesize;
214      $file_size = $attrs->{size};
215      $self->assert($expected == $file_size,
216        test_msg("Expected file size '$expected', got '$file_size'"));
217
218      $expected = $<;
219      $file_uid = $attrs->{uid};
220      $self->assert($expected == $file_uid,
221        test_msg("Expected file UID '$expected', got '$file_uid'"));
222
223      $expected = $(;
224      $file_gid = $attrs->{gid};
225      $self->assert($expected == $file_gid,
226        test_msg("Expected file GID '$expected', got '$file_gid'"));
227
228      $expected = $test_filemode;
229      $file_mode = $attrs->{mode};
230      $self->assert($expected == $file_mode,
231        test_msg("Expected file mode '$expected', got '$file_mode'"));
232
233      $sftp = undef;
234      $ssh2->disconnect();
235
236      # Now connect again, do another stat, and see if we're still using
237      # the cached entry.
238      $ssh2 = Net::SSH2->new();
239      unless ($ssh2->connect('127.0.0.1', $port)) {
240        my ($err_code, $err_name, $err_str) = $ssh2->error();
241        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
242      }
243
244      unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
245        my ($err_code, $err_name, $err_str) = $ssh2->error();
246        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
247      }
248
249      $sftp = $ssh2->sftp();
250      unless ($sftp) {
251        my ($err_code, $err_name, $err_str) = $ssh2->error();
252        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
253      }
254
255      $attrs = $sftp->stat($path, 1);
256      unless ($attrs) {
257        my ($err_code, $err_name) = $sftp->error();
258        die("STAT $path failed: [$err_name] ($err_code)");
259      }
260
261      $expected = $test_filesize;
262      $file_size = $attrs->{size};
263      $self->assert($expected == $file_size,
264        test_msg("Expected file size '$expected', got '$file_size'"));
265
266      $expected = $<;
267      $file_uid = $attrs->{uid};
268      $self->assert($expected == $file_uid,
269        test_msg("Expected file UID '$expected', got '$file_uid'"));
270
271      $expected = $(;
272      $file_gid = $attrs->{gid};
273      $self->assert($expected == $file_gid,
274        test_msg("Expected file GID '$expected', got '$file_gid'"));
275
276      $expected = $test_filemode;
277      $file_mode = $attrs->{mode};
278      $self->assert($expected == $file_mode,
279        test_msg("Expected file mode '$expected', got '$file_mode'"));
280
281      $sftp = undef;
282      $ssh2->disconnect();
283    };
284    if ($@) {
285      $ex = $@;
286    }
287
288    $wfh->print("done\n");
289    $wfh->flush();
290
291  } else {
292    eval { server_wait($setup->{config_file}, $rfh) };
293    if ($@) {
294      warn($@);
295      exit 1;
296    }
297
298    exit 0;
299  }
300
301  # Stop server
302  server_stop($setup->{pid_file});
303  $self->assert_child_ok($pid);
304
305  eval {
306    if (open(my $fh, "< $setup->{log_file}")) {
307      my $adding_entry = 0;
308      my $cached_stat = 0;
309      my $cached_lstat = 0;
310
311      if ($^O eq 'darwin') {
312        # MacOSX-specific hack
313        $test_file = '/private' . $test_file;
314      }
315
316      while (my $line = <$fh>) {
317        chomp($line);
318
319        if ($ENV{TEST_VERBOSE}) {
320          print STDERR "# line: $line\n";
321        }
322
323        if ($line =~ /<statcache:9>/) {
324          if ($line =~ /adding entry.*?type file/) {
325            $adding_entry++;
326            next;
327          }
328        }
329
330        if ($line =~ /<statcache:11>/) {
331          if ($cached_stat == 0 &&
332              $line =~ /using cached stat.*?path '$test_file'/) {
333            $cached_stat++;
334            next;
335          }
336
337          if ($cached_lstat == 0 &&
338              $line =~ /using cached lstat.*?path '$test_file'/) {
339            $cached_lstat++;
340            next;
341          }
342        }
343
344        if ($adding_entry >= 2 &&
345            $cached_stat == 1 &&
346            $cached_lstat == 1) {
347          last;
348        }
349      }
350
351      close($fh);
352
353      $self->assert($adding_entry >= 2 &&
354                    $cached_stat == 1 &&
355                    $cached_lstat == 1,
356        test_msg("Did not see expected 'statcache' TraceLog messages"));
357
358    } else {
359      die("Can't read $setup->{log_file}: $!");
360    }
361  };
362  if ($@) {
363    $ex = $@;
364  }
365
366  test_cleanup($setup->{log_file}, $ex);
367}
368
369sub statcache_sftp_stat_dir {
370  my $self = shift;
371  my $tmpdir = $self->{tmpdir};
372  my $setup = test_setup($tmpdir, 'statcache');
373
374  my $test_dir = File::Spec->rel2abs("$setup->{home_dir}/test.d");
375  mkpath($test_dir);
376
377  my $test_filemode = (stat($test_dir))[2];
378  if ($ENV{TEST_VERBOSE}) {
379    print STDERR "# $test_dir mode: $test_filemode\n";
380  }
381
382  my $statcache_tab = File::Spec->rel2abs("$tmpdir/statcache.tab");
383
384  my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_statcache/ssh_host_rsa_key');
385
386  my $config = {
387    PidFile => $setup->{pid_file},
388    ScoreboardFile => $setup->{scoreboard_file},
389    SystemLog => $setup->{log_file},
390    TraceLog => $setup->{log_file},
391    Trace => 'fsio:10 sftp:20 statcache:20',
392
393    AuthUserFile => $setup->{auth_user_file},
394    AuthGroupFile => $setup->{auth_group_file},
395
396    IfModules => {
397      'mod_delay.c' => {
398        DelayEngine => 'off',
399      },
400
401      'mod_sftp.c' => {
402        SFTPEngine => 'on',
403        SFTPLog => $setup->{log_file},
404        SFTPHostKey => $rsa_host_key,
405      },
406
407      'mod_statcache.c' => {
408        StatCacheEngine => 'on',
409        StatCacheTable => $statcache_tab,
410      },
411    },
412  };
413
414  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
415    $config);
416
417  # Open pipes, for use between the parent and child processes.  Specifically,
418  # the child will indicate when it's done with its test by writing a message
419  # to the parent.
420  my ($rfh, $wfh);
421  unless (pipe($rfh, $wfh)) {
422    die("Can't open pipe: $!");
423  }
424
425  require Net::SSH2;
426
427  my $ex;
428
429  # Fork child
430  $self->handle_sigchld();
431  defined(my $pid = fork()) or die("Can't fork: $!");
432  if ($pid) {
433    eval {
434      my $ssh2 = Net::SSH2->new();
435
436      sleep(1);
437
438      unless ($ssh2->connect('127.0.0.1', $port)) {
439        my ($err_code, $err_name, $err_str) = $ssh2->error();
440        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
441      }
442
443      unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
444        my ($err_code, $err_name, $err_str) = $ssh2->error();
445        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
446      }
447
448      my $sftp = $ssh2->sftp();
449      unless ($sftp) {
450        my ($err_code, $err_name, $err_str) = $ssh2->error();
451        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
452      }
453
454      my $path = 'test.d';
455      my $attrs = $sftp->stat($path, 1);
456      unless ($attrs) {
457        my ($err_code, $err_name) = $sftp->error();
458        die("STAT $path failed: [$err_name] ($err_code)");
459      }
460
461      my $expected = $<;
462      my $file_uid = $attrs->{uid};
463      $self->assert($expected == $file_uid,
464        test_msg("Expected file UID '$expected', got '$file_uid'"));
465
466      $expected = $(;
467      my $file_gid = $attrs->{gid};
468      $self->assert($expected == $file_gid,
469        test_msg("Expected file GID '$expected', got '$file_gid'"));
470
471      $expected = $test_filemode;
472      my $file_mode = $attrs->{mode};
473      $self->assert($expected == $file_mode,
474        test_msg("Expected file mode '$expected', got '$file_mode'"));
475
476      # Do the stat again; we'll check the logs to see if mod_statcache
477      # did its job.
478      $attrs = $sftp->stat($path, 1);
479      unless ($attrs) {
480        my ($err_code, $err_name) = $sftp->error();
481        die("STAT $path failed: [$err_name] ($err_code)");
482      }
483
484      $expected = $<;
485      $file_uid = $attrs->{uid};
486      $self->assert($expected == $file_uid,
487        test_msg("Expected file UID '$expected', got '$file_uid'"));
488
489      $expected = $(;
490      $file_gid = $attrs->{gid};
491      $self->assert($expected == $file_gid,
492        test_msg("Expected file GID '$expected', got '$file_gid'"));
493
494      $expected = $test_filemode;
495      $file_mode = $attrs->{mode};
496      $self->assert($expected == $file_mode,
497        test_msg("Expected file mode '$expected', got '$file_mode'"));
498
499      $sftp = undef;
500      $ssh2->disconnect();
501
502      # Now connect again, do another stat, and see if we're still using
503      # the cached entry.
504      $ssh2 = Net::SSH2->new();
505      unless ($ssh2->connect('127.0.0.1', $port)) {
506        my ($err_code, $err_name, $err_str) = $ssh2->error();
507        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
508      }
509
510      unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
511        my ($err_code, $err_name, $err_str) = $ssh2->error();
512        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
513      }
514
515      $sftp = $ssh2->sftp();
516      unless ($sftp) {
517        my ($err_code, $err_name, $err_str) = $ssh2->error();
518        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
519      }
520
521      $attrs = $sftp->stat($path, 1);
522      unless ($attrs) {
523        my ($err_code, $err_name) = $sftp->error();
524        die("STAT $path failed: [$err_name] ($err_code)");
525      }
526
527      $expected = $<;
528      $file_uid = $attrs->{uid};
529      $self->assert($expected == $file_uid,
530        test_msg("Expected file UID '$expected', got '$file_uid'"));
531
532      $expected = $(;
533      $file_gid = $attrs->{gid};
534      $self->assert($expected == $file_gid,
535        test_msg("Expected file GID '$expected', got '$file_gid'"));
536
537      $expected = $test_filemode;
538      $file_mode = $attrs->{mode};
539      $self->assert($expected == $file_mode,
540        test_msg("Expected file mode '$expected', got '$file_mode'"));
541
542      $sftp = undef;
543      $ssh2->disconnect();
544    };
545    if ($@) {
546      $ex = $@;
547    }
548
549    $wfh->print("done\n");
550    $wfh->flush();
551
552  } else {
553    eval { server_wait($setup->{config_file}, $rfh) };
554    if ($@) {
555      warn($@);
556      exit 1;
557    }
558
559    exit 0;
560  }
561
562  # Stop server
563  server_stop($setup->{pid_file});
564  $self->assert_child_ok($pid);
565
566  eval {
567    if (open(my $fh, "< $setup->{log_file}")) {
568      my $adding_entry = 0;
569      my $cached_stat = 0;
570      my $cached_lstat = 0;
571
572      if ($^O eq 'darwin') {
573        # MacOSX-specific hack
574        $test_dir = '/private' . $test_dir;
575      }
576
577      while (my $line = <$fh>) {
578        chomp($line);
579
580        if ($ENV{TEST_VERBOSE}) {
581          print STDERR "# line: $line\n";
582        }
583
584        if ($line =~ /<statcache:9>/) {
585          if ($line =~ /adding entry.*?type dir/) {
586            $adding_entry++;
587            next;
588          }
589        }
590
591        if ($line =~ /<statcache:11>/) {
592          if ($cached_stat == 0 &&
593              $line =~ /using cached stat.*?path '$test_dir'/) {
594            $cached_stat++;
595            next;
596          }
597
598          if ($cached_lstat == 0 &&
599              $line =~ /using cached lstat.*?path '$test_dir'/) {
600            $cached_lstat++;
601            next;
602          }
603        }
604
605        if ($adding_entry >= 2 &&
606            $cached_stat == 1 &&
607            $cached_lstat == 1) {
608          last;
609        }
610      }
611
612      close($fh);
613
614      $self->assert($adding_entry >= 2 &&
615                    $cached_stat == 1 &&
616                    $cached_lstat == 1,
617        test_msg("Did not see expected 'statcache' TraceLog messages"));
618
619    } else {
620      die("Can't read $setup->{log_file}: $!");
621    }
622  };
623  if ($@) {
624    $ex = $@;
625  }
626
627  test_cleanup($setup->{log_file}, $ex);
628}
629
630sub statcache_sftp_upload_file {
631  my $self = shift;
632  my $tmpdir = $self->{tmpdir};
633  my $setup = test_setup($tmpdir, 'statcache');
634
635  my $test_file = File::Spec->rel2abs("$setup->{home_dir}/test.txt");
636  my $test_filemode = 33188;
637  my $test_filesize = 14;
638
639  my $statcache_tab = File::Spec->rel2abs("$tmpdir/statcache.tab");
640
641  my $rsa_host_key = File::Spec->rel2abs('t/etc/modules/mod_statcache/ssh_host_rsa_key');
642
643  my $config = {
644    PidFile => $setup->{pid_file},
645    ScoreboardFile => $setup->{scoreboard_file},
646    SystemLog => $setup->{log_file},
647    TraceLog => $setup->{log_file},
648    Trace => 'fsio:10 sftp:20 statcache:20',
649
650    AuthUserFile => $setup->{auth_user_file},
651    AuthGroupFile => $setup->{auth_group_file},
652
653    IfModules => {
654      'mod_delay.c' => {
655        DelayEngine => 'off',
656      },
657
658      'mod_sftp.c' => {
659        SFTPEngine => 'on',
660        SFTPLog => $setup->{log_file},
661        SFTPHostKey => $rsa_host_key,
662      },
663
664      'mod_statcache.c' => {
665        StatCacheEngine => 'on',
666        StatCacheTable => $statcache_tab,
667      },
668    },
669  };
670
671  my ($port, $config_user, $config_group) = config_write($setup->{config_file},
672    $config);
673
674  # Open pipes, for use between the parent and child processes.  Specifically,
675  # the child will indicate when it's done with its test by writing a message
676  # to the parent.
677  my ($rfh, $wfh);
678  unless (pipe($rfh, $wfh)) {
679    die("Can't open pipe: $!");
680  }
681
682  require Net::SSH2;
683
684  my $ex;
685
686  # Fork child
687  $self->handle_sigchld();
688  defined(my $pid = fork()) or die("Can't fork: $!");
689  if ($pid) {
690    eval {
691      my $ssh2 = Net::SSH2->new();
692
693      sleep(1);
694
695      unless ($ssh2->connect('127.0.0.1', $port)) {
696        my ($err_code, $err_name, $err_str) = $ssh2->error();
697        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
698      }
699
700      unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
701        my ($err_code, $err_name, $err_str) = $ssh2->error();
702        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
703      }
704
705      my $sftp = $ssh2->sftp();
706      unless ($sftp) {
707        my ($err_code, $err_name, $err_str) = $ssh2->error();
708        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
709      }
710
711      my $path = 'test.txt';
712      my $fh = $sftp->open($path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
713      unless ($fh) {
714        my ($err_code, $err_name) = $sftp->error();
715        die("Can't open test.txt: [$err_name] ($err_code)");
716      }
717
718      print $fh "Hello, World!\n";
719
720      # To issue the FXP_CLOSE, we have to explicitly destroy the filehandle
721      $fh = undef;
722
723      my $attrs = $sftp->stat($path, 1);
724      unless ($attrs) {
725        my ($err_code, $err_name) = $sftp->error();
726        die("STAT $path failed: [$err_name] ($err_code)");
727      }
728
729      my $expected = $test_filesize;
730      my $file_size = $attrs->{size};
731      $self->assert($expected == $file_size,
732        test_msg("Expected file size '$expected', got '$file_size'"));
733
734      $expected = $<;
735      my $file_uid = $attrs->{uid};
736      $self->assert($expected == $file_uid,
737        test_msg("Expected file UID '$expected', got '$file_uid'"));
738
739      $expected = $(;
740      my $file_gid = $attrs->{gid};
741      $self->assert($expected == $file_gid,
742        test_msg("Expected file GID '$expected', got '$file_gid'"));
743
744      $expected = $test_filemode;
745      my $file_mode = $attrs->{mode};
746      $self->assert($expected == $file_mode,
747        test_msg("Expected file mode '$expected', got '$file_mode'"));
748
749      # Do the stat again; we'll check the logs to see if mod_statcache
750      # did its job.
751      $attrs = $sftp->stat($path, 1);
752      unless ($attrs) {
753        my ($err_code, $err_name) = $sftp->error();
754        die("STAT $path failed: [$err_name] ($err_code)");
755      }
756
757      $expected = $test_filesize;
758      $file_size = $attrs->{size};
759      $self->assert($expected == $file_size,
760        test_msg("Expected file size '$expected', got '$file_size'"));
761
762      $expected = $<;
763      $file_uid = $attrs->{uid};
764      $self->assert($expected == $file_uid,
765        test_msg("Expected file UID '$expected', got '$file_uid'"));
766
767      $expected = $(;
768      $file_gid = $attrs->{gid};
769      $self->assert($expected == $file_gid,
770        test_msg("Expected file GID '$expected', got '$file_gid'"));
771
772      $expected = $test_filemode;
773      $file_mode = $attrs->{mode};
774      $self->assert($expected == $file_mode,
775        test_msg("Expected file mode '$expected', got '$file_mode'"));
776
777      $sftp = undef;
778      $ssh2->disconnect();
779
780      # Now connect again, do another stat, and see if we're still using
781      # the cached entry.
782      $ssh2 = Net::SSH2->new();
783      unless ($ssh2->connect('127.0.0.1', $port)) {
784        my ($err_code, $err_name, $err_str) = $ssh2->error();
785        die("Can't connect to SSH2 server: [$err_name] ($err_code) $err_str");
786      }
787
788      unless ($ssh2->auth_password($setup->{user}, $setup->{passwd})) {
789        my ($err_code, $err_name, $err_str) = $ssh2->error();
790        die("Can't login to SSH2 server: [$err_name] ($err_code) $err_str");
791      }
792
793      $sftp = $ssh2->sftp();
794      unless ($sftp) {
795        my ($err_code, $err_name, $err_str) = $ssh2->error();
796        die("Can't use SFTP on SSH2 server: [$err_name] ($err_code) $err_str");
797      }
798
799      $attrs = $sftp->stat($path, 1);
800      unless ($attrs) {
801        my ($err_code, $err_name) = $sftp->error();
802        die("STAT $path failed: [$err_name] ($err_code)");
803      }
804
805      $expected = $test_filesize;
806      $file_size = $attrs->{size};
807      $self->assert($expected == $file_size,
808        test_msg("Expected file size '$expected', got '$file_size'"));
809
810      $expected = $<;
811      $file_uid = $attrs->{uid};
812      $self->assert($expected == $file_uid,
813        test_msg("Expected file UID '$expected', got '$file_uid'"));
814
815      $expected = $(;
816      $file_gid = $attrs->{gid};
817      $self->assert($expected == $file_gid,
818        test_msg("Expected file GID '$expected', got '$file_gid'"));
819
820      $expected = $test_filemode;
821      $file_mode = $attrs->{mode};
822      $self->assert($expected == $file_mode,
823        test_msg("Expected file mode '$expected', got '$file_mode'"));
824
825      $sftp = undef;
826      $ssh2->disconnect();
827    };
828    if ($@) {
829      $ex = $@;
830    }
831
832    $wfh->print("done\n");
833    $wfh->flush();
834
835  } else {
836    eval { server_wait($setup->{config_file}, $rfh) };
837    if ($@) {
838      warn($@);
839      exit 1;
840    }
841
842    exit 0;
843  }
844
845  # Stop server
846  server_stop($setup->{pid_file});
847  $self->assert_child_ok($pid);
848
849  eval {
850    if (open(my $fh, "< $setup->{log_file}")) {
851      my $adding_entry = 0;
852      my $cached_stat = 0;
853      my $cached_lstat = 0;
854
855      if ($^O eq 'darwin') {
856        # MacOSX-specific hack
857        $test_file = '/private' . $test_file;
858      }
859
860      while (my $line = <$fh>) {
861        chomp($line);
862
863        if ($ENV{TEST_VERBOSE}) {
864          print STDERR "# line: $line\n";
865        }
866
867        if ($line =~ /<statcache:9>/) {
868          if ($line =~ /adding entry.*?type file/) {
869            $adding_entry++;
870            next;
871          }
872        }
873
874        if ($line =~ /<statcache:11>/) {
875          if ($cached_stat == 0 &&
876              $line =~ /using cached stat.*?path '$test_file'/) {
877            $cached_stat++;
878            next;
879          }
880
881          if ($cached_lstat == 0 &&
882              $line =~ /using cached lstat.*?path '$test_file'/) {
883            $cached_lstat++;
884            next;
885          }
886        }
887
888        if ($adding_entry >= 2 &&
889            $cached_stat == 1 &&
890            $cached_lstat == 1) {
891          last;
892        }
893      }
894
895      close($fh);
896
897      $self->assert($adding_entry >= 2 &&
898                    $cached_stat == 1 &&
899                    $cached_lstat == 1,
900        test_msg("Did not see expected 'statcache' TraceLog messages"));
901
902    } else {
903      die("Can't read $setup->{log_file}: $!");
904    }
905  };
906  if ($@) {
907    $ex = $@;
908  }
909
910  test_cleanup($setup->{log_file}, $ex);
911}
912
9131;
914