/* Load a shared object at runtime, relocate it, and run its initializer.
   Copyright (C) 1996, 1997, 1998 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the GNU C Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.  */

#include <dlfcn.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <bits/libc-lock.h>
#include <elf/ldsodefs.h>


extern ElfW(Addr) _dl_sysdep_start (void **start_argptr,
				    void (*dl_main) (const ElfW(Phdr) *phdr,
						     ElfW(Word) phnum,
						     ElfW(Addr) *user_entry));
weak_extern (_dl_sysdep_start)

extern int __libc_multiple_libcs;	/* Defined in init-first.c.  */

extern int __libc_argc;
extern char **__libc_argv;

extern char **__environ;

static size_t _dl_global_scope_alloc;


/* During the program run we must not modify the global data of
   loaded shared object simultanously in two threads.  Therefore we
   protect `_dl_open' and `_dl_close' in dl-close.c.

   This must be a recursive lock since the initializer function of
   the loaded object might as well require a call to this function.
   At this time it is not anymore a problem to modify the tables.  */
__libc_lock_define_initialized_recursive (, _dl_load_lock)


/* We must be carefull not to leave us in an inconsistent state.  Thus we
   catch any error and re-raise it after cleaning up.  */

struct dl_open_args
{
  const char *file;
  int mode;
  struct link_map *map;
};

static void
dl_open_worker (void *a)
{
  struct dl_open_args *args = a;
  const char *file = args->file;
  int mode = args->mode;
  struct link_map *new, *l;
  ElfW(Addr) init;
  struct r_debug *r;

  /* Load the named object.  */
  args->map = new = _dl_map_object (NULL, file, 0, lt_loaded, 0);
  if (new->l_searchlist)
    /* It was already open.  */
    return;

  /* Load that object's dependencies.  */
  _dl_map_object_deps (new, NULL, 0, 0);

  /* So far, so good.  Now check the versions.  */
  (void) _dl_check_all_versions (new, 0);

  /* Relocate the objects loaded.  We do this in reverse order so that copy
     relocs of earlier objects overwrite the data written by later objects.  */

  l = new;
  while (l->l_next)
    l = l->l_next;
  while (1)
    {
      if (! l->l_relocated)
	{
	  /* We use an indirect call call for _dl_relocate_object because
	     we must avoid using the PLT in the call.  If our PLT entry for
	     _dl_relocate_object hasn't been used yet, then the dynamic
	     linker fixup routine will clobber _dl_global_scope during its
	     work.  We must be sure that nothing will require a PLT fixup
	     between when _dl_object_relocation_scope returns and when we
	     enter the dynamic linker's code (_dl_relocate_object).  */
	  __typeof (_dl_relocate_object) *reloc = &_dl_relocate_object;

	  /* GCC is very clever.  If we wouldn't add some magic it would
	     simply optimize away our nice little variable `reloc' and we
	     would result in a not working binary.  So let's swing the
	     magic ward.  */
	  asm ("" : "=r" (reloc) : "0" (reloc));

#ifdef PIC
	  if (_dl_profile != NULL)
	    {
	      /* If this here is the shared object which we want to profile
		 make sure the profile is started.  We can find out whether
	         this is necessary or not by observing the `_dl_profile_map'
	         variable.  If was NULL but is not NULL afterwars we must
		 start the profiling.  */
	      struct link_map *old_profile_map = _dl_profile_map;

	      (*reloc) (l, _dl_object_relocation_scope (l), 1, 1);

	      if (old_profile_map == NULL && _dl_profile_map != NULL)
		/* We must prepare the profiling.  */
		_dl_start_profile (_dl_profile_map, _dl_profile_output);
	    }
	  else
#endif
	    (*reloc) (l, _dl_object_relocation_scope (l),
		      (mode & RTLD_BINDING_MASK) == RTLD_LAZY, 0);

	  *_dl_global_scope_end = NULL;
	}

      if (l == new)
	break;
      l = l->l_prev;
    }

  new->l_global = (mode & RTLD_GLOBAL) ? 1 : 0;
  if (new->l_global)
    {
      /* The symbols of the new object and its dependencies are to be
	 introduced into the global scope that will be used to resolve
	 references from other dynamically-loaded objects.  */

      if (_dl_global_scope_alloc == 0)
	{
	  /* This is the first dynamic object given global scope.  */
	  _dl_global_scope_alloc = 8;
	  _dl_global_scope = malloc (_dl_global_scope_alloc
				     * sizeof (struct link_map *));
	  if (! _dl_global_scope)
	    {
	      _dl_global_scope = _dl_default_scope;
	    nomem:
	      new->l_global = 0;
	      _dl_signal_error (ENOMEM, file, "cannot extend global scope");
	    }
	  _dl_global_scope[2] = _dl_default_scope[2];
	  _dl_global_scope[3] = new;
	  _dl_global_scope[4] = NULL;
	  _dl_global_scope[5] = NULL;
	  _dl_global_scope_end = &_dl_global_scope [4];
	}
      else
	{
	  if (_dl_global_scope_end + 2
	      == _dl_global_scope + _dl_global_scope_alloc)
	    {
	      /* Must extend the list.  */
	      struct link_map **new = realloc (_dl_global_scope,
					       _dl_global_scope_alloc * 2
					       * sizeof (struct link_map *));
	      if (! new)
		goto nomem;
	      _dl_global_scope = new;
	      _dl_global_scope_end = new + _dl_global_scope_alloc - 2;
	      _dl_global_scope_alloc *= 2;
	    }

	  /* Append the new object and re-terminate the list.  */
	  *_dl_global_scope_end++ = new;
	  /* We keep the list double-terminated so the last element
	     can be filled in for symbol lookups.  */
	  _dl_global_scope_end[0] = NULL;
	  _dl_global_scope_end[1] = NULL;
	}
    }


  /* Notify the debugger we have added some objects.  We need to call
     _dl_debug_initialize in a static program in case dynamic linking has
     not been used before.  */
  r = _dl_debug_initialize (0);
  r->r_state = RT_ADD;
  _dl_debug_state ();

  /* Run the initializer functions of new objects.  */
  while (init = _dl_init_next (new))
    (*(void (*) (int, char **, char **)) init) (__libc_argc, __libc_argv,
						__environ);

  if (_dl_sysdep_start == NULL)
    /* We must be the static _dl_open in libc.a.  A static program that
       has loaded a dynamic object now has competition.  */
    __libc_multiple_libcs = 1;
}


struct link_map *
internal_function
_dl_open (const char *file, int mode)
{
  struct dl_open_args args;
  char *errstring;
  int errcode;

  /* Make sure we are alone.  */
  __libc_lock_lock (_dl_load_lock);

  args.file = file;
  args.mode = mode;
  args.map = NULL;
  errcode = _dl_catch_error (&errstring, dl_open_worker, &args);

  /* Release the lock.  */
  __libc_lock_unlock (_dl_load_lock);

  if (errstring)
    {
      /* Some error occured during loading.  */
      char *local_errstring;

      /* Reset the global scope.  */
      *_dl_global_scope_end = NULL;

      /* Remove the object from memory.  It may be in an inconsistent
	 state if relocation failed, for example.  */
      if (args.map)
	_dl_close (args.map);

      /* Make a local copy of the error string so that we can release the
	 memory allocated for it.  */
      local_errstring = strdupa (errstring);
      free (errstring);

      /* Reraise the error.  */
      _dl_signal_error (errcode, NULL, local_errstring);
    }

  return args.map;
}