# $OpenBSD: Library.pm,v 1.14 2023/07/08 08:15:32 espie Exp $

# Copyright (c) 2007-2010 Steven Mestdagh <steven@openbsd.org>
# Copyright (c) 2012 Marc Espie <espie@openbsd.org>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

use v5.36;

package LT::Library::Stash;

sub new($class)
{
	bless {}, $class;
}

sub create($self, $key)
{
	if (!exists $self->{$key}) {
		$self->{$key} = LT::Library->new($key);
	}
	return $self->{$key};
}

package LT::Library;

use LT::Util;
use LT::Trace;

# find actual library filename
# XXX pick the right one if multiple are found!
sub resolve_library($self, $dirs, $shared, $staticflag, $linkmode, $gp = undef)
{

	my $libtofind = $self->{key};
	my $libfile = 0;
	my @globbedlib;

	my $pic = '';	# used when finding static libraries
	if ($linkmode eq 'LT::LaFile') {
		$pic = '_pic';
	}

	if (defined $self->{lafile}) {
		require LT::LaFile;
		# if there is a .la file, use the info from there
		tsay {"found .la file $self->{lafile} for library key: ",
		    $self->{key}};
		my $lainfo = LT::LaFile->parse($self->{lafile});
		my $dlname = $lainfo->{dlname};
		my $oldlib = $lainfo->{old_library};
		my $libdir = $lainfo->{libdir};
		my $installed = $lainfo->{installed};
		my $d = abs_dir($self->{lafile});
		# get the name we need (this may include a -release)
		if (!$dlname && !$oldlib) {
			die "Link error: neither static nor shared library found in $self->{lafile}\n";
		}
		if ($d !~ m/\Q$ltdir\E$/ && $installed eq 'no') {
			$d .= "/$ltdir";
		}
		if ($shared) {
			if ($dlname) {
				$libfile = "$d/$dlname";
			} else {
				# fall back to static
				$libfile = "$d/$oldlib";
			}
			# if -static has been passed, don't link dynamically
			# against not-installed libraries
			if ($staticflag && $installed eq 'no') {
				$libfile = "$d/$oldlib";
			}
		} else {
			$libfile = "$d/$oldlib";
		}
		if (! -f $libfile) {
			tsay {".la file ", $self->{lafile},
			    "points to nonexistent file ", $libfile, " !"};
		}
	} else {
		# search in .libs when priority is high
		push @$dirs, $gp->libsearchdirs if defined $gp;
		tsay {"searching for $libtofind"};
		tsay {"search path= ", join(':', @$dirs)};
		tsay {"search type= ", $shared ? 'shared' : 'static'};
		foreach my $sd (@$dirs) {
			if ($shared) {
				# select correct library by sorting by version number only
				my $bestlib = $self->findbest($sd, $libtofind);
				if ($bestlib) {
					tsay {"found $libtofind in $sd"};
					$libfile = $bestlib;
					last;
				} else {
					# XXX find static library instead?
					my $spath = "$sd/lib$libtofind$pic.a";
					if (-f $spath) {
						tsay {"found static $libtofind in $sd"};
						$libfile = $spath;
						last;
					}
				}
			} else {
				# look for a static library
				my $spath = "$sd/lib$libtofind.a";
				if (-f $spath) {
					tsay {"found static $libtofind in $sd"};
					$libfile = $spath;
					last;
				}
			}
		}
	}
	if (!$libfile) {
		delete $self->{fullpath};
		if ($linkmode eq 'LT::LaFile') {
			say "warning: dependency on $libtofind dropped";
			$self->{dropped} = 1;
		} elsif ($linkmode eq 'LT::Program') {
			die "Link error: $libtofind not found!\n";
		}
	} else {
		$self->{fullpath} = $libfile;
		tsay {"\$libs->{$self->{key}}->{fullpath} = ",
		    $self->{fullpath}};
	}
}

sub findbest($self, $sd, $name)
{
	my $best = undef;
	if (opendir(my $dir, $sd)) {
		my ($major, $minor) = (-1, -1);
		while (my $e = readdir($dir)) {
			next unless $e =~ m/^lib\Q$name\E\.so\.(\d+)\.(\d+)$/;
			if ($1 > $major || ($1 == $major && $2 > $minor)) {
				($major, $minor) = ($1, $2);
				$best = "$sd/$e";
			}
		}
		closedir($dir);
	}
	if (!defined $best) {
		my $cand = "$sd/lib$name.so";
		if (-e $cand) {
			$best = $cand;
		}
	}
	return $best;
}

# give a list of library dependencies found in the actual shared library
sub inspect($self)
{
	my $filename = $self->{fullpath};
	my @deps;

	if (!defined($filename)){
		say "warning: library was specified that could not be found: $self->{key}";
		return;
	}
	tsay {"inspecting $filename for library dependencies..."};
	open(my $fh, '-|', "objdump", "-p", "--", $filename);
	while (<$fh>) {
		if (m/\s+NEEDED\s+(\S+)\s*$/) {
			push @deps, $1;
		}
	}
	tsay {"found ", (@deps == 0) ? 'no ' : '',
		"deps for $filename\n@deps"};
	return @deps;
}

# give the list of RPATH/RUNPATH directories
sub findrpaths($self)
{
	my $filename = $self->{fullpath};
	my @dirs;

	if (!defined($filename)){
		say "warning: library was specified that could not be found: $self->{key}";
		return;
	}
	tsay {"inspecting $filename for non standard RPATH/RUNPATH..."};
	open(my $fh, '-|', "objdump", "-p", "--", $filename);
	while (<$fh>) {
		if (m/R(?:UN)?PATH\s+(.*)$/) {
			@dirs = split(":", $1);
			last;
		}
	}
	tsay {"found ", (@dirs == 0) ? 'none ' : '',
		"RPATH/RUNPATH for $filename\n@dirs"};
	return @dirs;
}

sub new($class, $key)
{
	bless { key => $key }, $class;
}

1;
