#! /usr/bin/env perl
use v5.26;
use warnings;
use lib (__FILE__ =~ s,[^\\/]+$,lib,r);
use Test2AndUtils;
use experimental qw( signatures );
use Cwd 'abs_path';
use Fcntl 'S_IFREG';
use Sys::Export::Linux;

=head1 gnu-libc

Test the special features of the ::Linux exporter related to aspects of GNU libc.

=cut

package Sys::Export::MockDst {
   sub new($class)        { bless { files => {} }, $class }
   sub files($self)       { $self->{files} }
   sub add($self, $attrs) { $self->{files}{$attrs->{name}}= $attrs; }
   sub finish($self)      {}
}

=head2 ld_so_conf

Test the ability to parse /etc/ld.so.conf and then add those library paths to the exporter's
src_lib_path.  I rounded up several real example filesystems with varying ld.so.conf, though
there are probably still more edge cases that exist out there.

=cut

subtest ld_so_conf => sub {
   subtest newer_arch => sub {
      my $tmp= tmpdir;
      mkdir "$tmp/etc";
      mkfile "$tmp/etc/ld.so.conf", <<~END;
         # Dynamic linker/loader configuration.
         # See ld.so(8) and ldconfig(8) for details.
         
         include /etc/ld.so.conf.d/*.conf
         include /usr/lib/ld.so.conf.d/*.conf
         END
      mkdir "$tmp/etc/ld.so.conf.d";
      mkfile "$tmp/etc/ld.so.conf.d/perf.conf", <<~END;
         /usr/lib/perf
         END
      mkfile "$tmp/etc/ld.so.conf.d/android-sdk.conf", <<~END;
         /opt/android-sdk/tools/lib
         END
      mkfile "$tmp/etc/ld.so.conf.d/README.txt", <<~END;
         Some Text
         /this/should/not/get/included
         END
      mkdir "$tmp/usr";
      mkdir "$tmp/usr/lib";
      mkdir "$tmp/usr/lib/ld.so.conf.d";
      mkfile "$tmp/usr/lib/ld.so.conf.d/fakeroot.conf", <<~END;
         /usr/lib/libfakeroot
         END
      mkdir "$tmp/usr/lib/perf";
      mkdir "$tmp/usr/lib/libfakeroot";

      my $exporter= Sys::Export::Linux->new(src => "$tmp", dst => Sys::Export::MockDst->new());
      is { map +($_ => 1), $exporter->parse_ld_so_conf },
         { 'usr/lib/perf' => 1, 'opt/android-sdk/tools/lib' => 1, 'usr/lib/libfakeroot' => 1 },
         'parse_ld_so_conf';
      like { map +($_ => 1), $exporter->src_lib_path_list },
         {
            'usr/lib/perf' => 1,
            'opt/android-sdk/tools/lib' => DNE,
            'usr/lib/libfakeroot' => 1,
         },
         'src_lib_path_list';
   };

   subtest gentoo2026 => sub {
      my $tmp= tmpdir;
      mkdir "$tmp/etc";
      mkfile "$tmp/etc/ld.so.conf", <<~END;
         # ld.so.conf autogenerated by env-update; make all changes to
         # contents of /etc/env.d directory.
         include ld.so.conf.d/*.conf
         /usr/lib64
         /usr/local/lib64
         /usr/lib
         /usr/local/lib
         END
      mkdir "$tmp/etc/ld.so.conf.d";
      mkfile "$tmp/etc/ld.so.conf.d/05gcc-x86_64-pc-linux-gnu.conf", <<~END;
         /usr/lib/gcc/x86_64-pc-linux-gnu/15
         END
      mkdir "$tmp/usr";
      mkdir "$tmp/usr/lib";
      mkdir "$tmp/usr/lib/gcc";
      mkdir "$tmp/usr/lib/gcc/x86_64-pc-linux-gnu";
      mkdir "$tmp/usr/lib/gcc/x86_64-pc-linux-gnu/15";

      my $exporter= Sys::Export::Linux->new(src => "$tmp", dst => Sys::Export::MockDst->new());
      is { map +($_ => 1), $exporter->parse_ld_so_conf },
         { 'usr/lib/gcc/x86_64-pc-linux-gnu/15' => 1,
           'usr/lib64' => 1,
           'usr/local/lib64' => 1,
           'usr/lib' => 1,
           'usr/local/lib' => 1,
         },
         'parse_ld_so_conf';
      like { map +($_ => 1), $exporter->src_lib_path_list },
         { 'usr/lib/gcc/x86_64-pc-linux-gnu/15' => 1,
           'usr/lib64' => DNE,
           'usr/local/lib64' => DNE,
           'usr/lib' => 1,
           'usr/local/lib' => DNE,
         },
         'src_lib_path_list';
   };

   subtest older_gentoo => sub {
      my $tmp= tmpdir;
      mkdir "$tmp/etc";
      mkfile "$tmp/etc/ld.so.conf", <<~END;
      # ld.so.conf autogenerated by env-update; make all changes to
      # contents of /etc/env.d directory
      /usr/local/lib
      /usr/lib/gcc/i386-pc-linux-gnu/4.2.4
      /usr/i386-pc-linux-gnu/lib
      /usr/qt/3/lib
      /usr/lib/libstdc++-v3/
      END
      mkdir "$tmp/usr";
      mkdir "$tmp/usr/i386-pc-linux-gnu";
      mkdir "$tmp/usr/i386-pc-linux-gnu/lib";
      mkdir "$tmp/usr/qt";
      mkdir "$tmp/usr/qt/3";
      mkdir "$tmp/usr/qt/3/lib";
      mkdir "$tmp/usr/lib";
      mkdir "$tmp/usr/lib/libstdc++-v3";

      my $exporter= Sys::Export::Linux->new(src => "$tmp", dst => Sys::Export::MockDst->new());
      is { map +($_ => 1), $exporter->parse_ld_so_conf },
         { 'usr/local/lib' => 1,
           'usr/lib/gcc/i386-pc-linux-gnu/4.2.4' => 1,
           'usr/i386-pc-linux-gnu/lib' => 1,
           'usr/qt/3/lib' => 1,
           'usr/lib/libstdc++-v3/' => 1,
         },
         'parse_ld_so_conf';
      like { map +($_ => 1), $exporter->src_lib_path_list },
         {
            'usr/local/lib' => DNE,
            'usr/lib/gcc/i386-pc-linux-gnu/4.2.4' => DNE,
            'usr/i386-pc-linux-gnu/lib' => 1,
            'usr/qt/3/lib' => 1,
            'usr/lib/libstdc++-v3' => 1,
         },
         'src_lib_path_list';
   };
};

=head2 nsswitch

Test the ability to parse /etc/nsswitch.conf and then add the related library files.
This is not performed automatically in case the user wants to exclude some of them from the
built image.

A future version might exclude the 'files' and 'dns' modules since libc has begun bundling them
into the core of libc to make chroots easier, but for now everything referenced gets exported.
It would require some kind of test to see if libc was new enough and compiled that way.

=cut

subtest nsswitch => sub {
   subtest gentoo2026 => sub {
      my $tmp= tmpdir;
      mkdir "$tmp/etc";
      eval { symlink "$tmp/etc", "$tmp/etc2" }
         or skip_all "No symlink support on $^O";

      mkfile "$tmp/etc/nsswitch.conf", <<~END;
         # In alphabetical order. Re-order as required to optimize performance.
         aliases:    files
         ethers:     files
         group:      files hesiod
         gshadow:    files
         hosts:      files dns
         # Allow initgroups to default to the setting for group.
         # initgroups: files
         netgroup:   files
         networks:   files dns
         passwd:     files hesiod
         protocols:  files
         publickey:  files
         rpc:        files
         shadow:     files
         services:   files
         END
      mkdir "$tmp/usr";
      mkdir "$tmp/usr/lib64";
      for (qw( libnss_compat.so.2 libnss_db.so.2 libnss_dns.so.2 libnss_files.so.2 libnss_hesiod.so.2 )) {
         mkfile "$tmp/usr/lib64/$_", "pretend this is ELF", 0755;
      }
      symlink "usr/lib64", "$tmp/lib64";
      # Yes Gentoo had symlinks pointing from usr/lib64 to ../../lib64
      # even though /lib64 was a symlink to usr/lib64
      symlink "../../lib64/libnss_compat.so.2", "$tmp/usr/lib64/libnss_compat.so";
      symlink "../../lib64/libnss_db.so.2",     "$tmp/usr/lib64/libnss_db.so";
      symlink "../../lib64/libnss_hesiod.so.2", "$tmp/usr/lib64/libnss_hesiod.so";

      my $exporter= Sys::Export::Linux->new(src => "$tmp", dst => Sys::Export::MockDst->new());
      is +{ $exporter->parse_nsswitch_conf },
         {  aliases   => ['files'],
            ethers    => ['files'],
            group     => ['files','hesiod'],
            gshadow   => ['files'],
            hosts     => ['files','dns'],
            netgroup  => ['files'],
            networks  => ['files','dns'],
            passwd    => ['files','hesiod'],
            protocols => ['files'],
            publickey => ['files'],
            rpc       => ['files'],
            shadow    => ['files'],
            services  => ['files'],
         },
         'parse_nsswitch_conf';
      $exporter->add_nsswitch_libs;
      like $exporter->src_path_set,
         {  'usr/lib64/libnss_dns.so.2'    => D,
            'usr/lib64/libnss_files.so.2'  => D,
            'usr/lib64/libnss_hesiod.so'   => D,
            'usr/lib64/libnss_hesiod.so.2' => D,
         },
         'add_nsswitch_libs';
   };
};

done_testing;
