/*
 * Copyright (c) 1993 Eric Youngdale, Peter MacDonald, David Engel
 * and Hongjiu Lu.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Eric Youngdale,
 *      Peter MacDonald, David Engel and Hongjiu Lu.
 * 3. The name of the above contributors may not be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* Notice of general intent:
 *
 * The linux operating system generally contains large amounts of code
 * that fall under the GNU General Public License, or GPL for short.
 * This file contains source code that by it's very nature would always
 * be linked with an application program, and because of this a GPL type
 * of copyright on this file would place restrictions upon the
 * distribution of binary-only commercial software.  Since the goal of the
 * Linux project as a whole is not to discourage the development and
 * distribution of commercial software for Linux, this file has been placed
 * under a more relaxed BSD-style of copyright.
 *
 * It is the general understanding of the above contributors that a
 * program executable linked to a library containing code that falls
 * under the GPL or GLPL style of license is not subject to the terms of
 * the GPL or GLPL license if the program executable(s) that are supplied
 * are linked to a shared library form of the GPL or GLPL library, and as long
 * as the form of the shared library is such that it is possible for
 * the end user to modify and rebuild the library and use it in
 * conjunction with the program executable.
 */

static int errno = 0;

#include <alloca.h>
#include <unistd.h>
#include <syscall.h>
#include <stdarg.h>
#include <sharedlib.h>
#include <errno.h>
#include <limits.h>

#define TOTAL_COMPATIBLE	0
#define MAJOR_COMPATIBLE	1
#define NOT_COMPATIBLE		2

static const char *const DEFAULT_LD_LIBRARY_PATH [] = {
  "/usr/lib/",
  "/lib/",
  "/",
};

static int default_total = sizeof (DEFAULT_LD_LIBRARY_PATH) / sizeof (char *);

static volatile void
_call_exit(int exit_code)
{
  __asm__("int $0x80"::"a" (SYS_exit),"b" (exit_code):"bx");
}

static
_syscall0(uid_t,getuid)

static
_syscall0(uid_t,geteuid)

static
_syscall0(gid_t,getgid)

static
_syscall0(gid_t,getegid)

static
_syscall1(int,uselib,const char *,filename)

static
_syscall3(ssize_t,write,int,fd,const void *,buf,size_t,count)

static inline size_t
strlen (const char *str)
{
register size_t __res __asm__("cx");
__asm__("cld\n\t"
	"repne\n\t"
	"scasb\n\t"
	"notl %0\n\t"
	"decl %0"
	:"=c" (__res):"D" (str),"a" (0),"0" (0xffffffff):"di");
return __res;
}

static char *
strcat(char * dest,const char * src)
{
__asm__("cld\n\t"
	"repne\n\t"
	"scasb\n\t"
	"decl %1\n"
	"1:\tlodsb\n\t"
	"stosb\n\t"
	"testb %%al,%%al\n\t"
	"jne 1b"
	::"S" (src),"D" (dest),"a" (0),"c" (0xffffffff):"si","di","ax","cx");
return dest;
}

static inline int
strncmp(const char * cs,const char * ct,size_t count)
{
register int __res __asm__("ax");
__asm__("cld\n\t"
	"incl %3\n"
	"1:\tdecl %3\n\t"
	"je 2f\n\t"
	"lodsb\n\t"
	"scasb\n\t"
	"jne 3f\n\t"
	"testb %%al,%%al\n\t"
	"jne 1b\n"
	"2:\txorl %%eax,%%eax\n\t"
	"jmp 4f\n"
	"3:\tmovl $1,%%eax\n\t"
	"jb 4f\n\t"
	"negl %%eax\n"
	"4:"
	:"=a" (__res):"D" (cs),"S" (ct),"c" (count):"si","di","cx");
return __res;
}

static inline char *
findenv (const char *name, char **env)
{
  const size_t len = strlen(name);
  char **ep;

  for (ep = env; *ep != NULL; ++ep)
    if (!strncmp(*ep, name, len) && (*ep)[len] == '=')
      return &(*ep)[len + 1];

  return NULL;
}

static void
__lib_print (int fd, ...)
{
  char *cp;
  va_list ap;

  va_start(ap, fd);
  for (cp = va_arg(ap, char *); cp; cp = va_arg(ap, char *))
      write (fd, cp, strlen(cp));
  va_end(ap);
}

static inline void
__shlib_fatal (const char *argv0, const char *lib, int err)
{
  __lib_print (2, argv0, ": can't load library '", lib, "'\n\t",
	NULL);

  switch (err) {
  case 0:
    __lib_print (2, "Incompatible version.\n", NULL);
    break;
  case EINVAL:
    __lib_print (2, "Too many libraries.\n", NULL);
    break;
  case ENOENT:
    __lib_print (2, "No such library.\n", NULL);
    break;
  case EACCES:
    __lib_print (2, "Permission denied.\n", NULL);
    break;
  case ENOEXEC:
    __lib_print (2, "Exec format error.\n", NULL);
    break;
  default:
    __lib_print (2, "Unspecified error.\n", NULL);
    break;
  }

  while (1)
    _call_exit (128);
}

static inline int
incompatible(int in_core, int linked)
{
#if 0
  /* Don't use the classic library with this __load.c.
   * should be safe since the classic library has been abandoned.
   */
  if (linked & CLASSIC_BIT) {
    return (in_core == linked) ? TOTAL_COMPATIBLE : NOT_COMPATIBLE;
  }
  else
#endif
  {
    return ((in_core & MAJOR_MASK) == (linked & MAJOR_MASK))
    	? (((in_core & MINOR_MASK) >= (linked & MINOR_MASK))
    		? TOTAL_COMPATIBLE : MAJOR_COMPATIBLE)
    	: NOT_COMPATIBLE;
  }
}


/* This list is generated by the linker - it corresponds to the SETD
   list generated in conjunction with the variable __SHARABLE_CONFLICTS__.
   The linker does not do anything special with this symbol - it simply
   puts together the pieces that are needed.  The list of fixups to local
   variables is generated internally by the linker, and ld fills in this
   one field as a special case before writing the image.  The array size
   is listed as 1, but it would actually be  ->size - 2.  __load.c will
   pick up this pointer and pass it to __dynamic_resolve() and __do_fixups().
*/

struct fixuplist{  /* This list is generated by the linker */
  int size;
  int * magic;  /* Used for verification purposes */
  struct image_fixuplist * list;  /* Fixups to local variables */
  struct fixuplist *** shrlib_list[1];  /* One for each sharable
	        library this program is linked to.  The sharable libraries
		are linked to other sharable libraries, so we have to walk
		this tree and resolve all of the fixups. */
};			


/* This is a linker-generated list of fixups.  The linker essentially
   watches for	multiple definitions of symbols, and if there is a
   __GOT__ symbol then it does not flag an error condition.  Instead, it
   adds a fixup to the fixuplist which will correct the __GOT__ that 
   appears later on the link command line.  The number of pointer pairs
   is always specified by the size field, and after the last pointer pair
   is the pointer to the builtin_fixups.
   */

struct image_fixuplist{
  int size;
  struct elemental_fixup{
    union{
      int  newaddr;
      struct builtin_fixup* bifu;
    }un;
    int * fixaddr;
  } fix[1];
};

/* Each link image (sharable libraries are each considered a sharable
   link image) will have a list of builtin fixups as well, as many as one
   for each source file that went into the link image.  These lists
   are generated by jumpas as needed (perhaps one per source file), and a SETD
   symbol __BUILTIN_FIXUPS__.  These will never appear in a user program -
   only in a shared library.  This structure contains a series of pointers
   to these different lists, and we need to walk through and apply all
   of the fixups.  The array size is listed as 1, but it can be any number
 */

struct builtin_fixup{
	int len;
	struct file_fixup * fixpnt[1];  /* Potentially one for every source
					    file that was linked */
};


/* Each source file can potentially have one of these beasts.  There
   are a series of pointer pairs, terminated by a NULL.  The idea here is
   that we are supposed to copy the number out of gotaddr  and store it at
   fixaddr.  This is the list that is generated by the jumpas tool.
   This is listed with an array size of 1, but it can be any number.
 */

struct file_fixup{
  struct builtin_elemental_fixup{
    int * gotaddr;
    int * fixaddr;
  } fixup[1];
};

extern struct libentry * __SHARED_LIBRARIES__[];
extern struct fixuplist  _SHARABLE_CONFLICTS__;

/* Here we fix addresses of global variables that are stored in the
   data section (or stored as data in the text section).d  We wait to
   do this until after all of the GOT pointers have been updated by
   __dynamic_resolve because this is where we are copying our numbers
   from */

static void __reset_magic(struct fixuplist * xpnt)
{
  int size;
  /* Now go back through and reset the magic numbers.  The only reason we
     need to do this is that we must allow for the possiblility of the
     memory image being dumped to a new executable (i.e. emacs). */

  if(*xpnt->magic == 0xfeeb1ed3) return;  /* This means we have been through
							   here before */
  if(*xpnt->magic != 0xfeeb1ed6) {
    __lib_print (2, "Corrupt fixup table.\n", NULL);
    _call_exit (128);
  };
  
  (*xpnt->magic) = 0xfeeb1ed3;  /* Reset back to the original value */

  size = (unsigned int) xpnt->size - 3;
  
  /* First recurse through the various shared libraries we are linked to */
  while(size >= 0)
    __reset_magic(**xpnt->shrlib_list[size--]);
}

static void __do_fixups(struct fixuplist * xpnt, int flag)
{
  int size;
  struct builtin_fixup * bpnt;
  /* Now we walk through the list of pointers that need to be updated.
     This is essentially our dynamic linking.  Strictly speaking this is
     somewhat simpler, because all we are really doing is fixing the scope
     of variables and functions so that functions are used in the same
     was as they would be if we were linked statically.
     */

  if(*xpnt->magic == 0xfeeb1ed4 + (flag << 1)) return;  /* This means we have been through
							   here before */
  
  if(*xpnt->magic != 0xfeeb1ed3 + (flag << 1)) {
    __lib_print (2, "Corrupt fixup table.\n", NULL);
    _call_exit (128);
  };
  
  (*xpnt->magic)++;  /* This is so we know if we have been
			through here before */

  size = (unsigned int) xpnt->size - 3;
  
  /* First recurse through the various shared libraries we are linked to */
  while(size >= 0)
    __do_fixups(**xpnt->shrlib_list[size--], flag);
      
  /* Now get the pointer to the builtin fixups. */
  bpnt = xpnt->list->fix[xpnt->list->size].un.bifu;
  
  /* If there are fixups, then we scan through the various files that
     have tables, and apply all of the fixups in the table.  Consider that
     an address could be stored as:

     .long _foo+3

     we allow for the offset by first subracting the value of _foo that
     the linker assigned, then using __dynamic_resolve to fix all of the
     GOT entries that need to be fixed, and then adding in the new offset
     of the (possibly) new value of _foo 
*/
  if(bpnt)
    for(size = 0; size < bpnt->len; size++) {
      int i = 0;
      struct file_fixup * fpnt;
      fpnt = bpnt->fixpnt[size];
      while(1==1){
	if(!fpnt->fixup[i].gotaddr) break;
	if(!flag)
	  *fpnt->fixup[i].fixaddr -= *fpnt->fixup[i].gotaddr;
	else
	  *fpnt->fixup[i].fixaddr += *fpnt->fixup[i].gotaddr;
	i++;
      };
    };
}

static void __dynamic_resolve(struct fixuplist * xpnt)
{
  /* Now we walk through the list of pointers that need to be updated.
     This is essentially our dynamic linking.  Strictly speaking this is
     somewhat simpler, because all we are really doing is fixing the scope
     of variables and functions so that functions are used in the same
     was as they would be if we were linked statically.
     */
  int size;

  /* Check for the magic number to make sure that we really have a proper
     fixup table */

  if(*xpnt->magic == 0xfeeb1ed5) return;  /* This means we have been through
					     here before */

  if(*xpnt->magic != 0xfeeb1ed4) {
    __lib_print (2, "Corrupt fixup table.\n", NULL);
    _call_exit (128);
  };

  (*xpnt->magic)++;  /* This is so we catch circularly linked libraries */

  size = (unsigned int) xpnt->size - 3;
  
  /* Recurse through the shared libraries, one by one */
  while(size >= 0)
    __dynamic_resolve(**xpnt->shrlib_list[size--]);
      
  /* Now that this is done, perform the fixups that were generated
     when this image was linked. */

  for(size = 0; size < xpnt->list->size; size++) {
    if(!xpnt->list->fix[size].un.newaddr) break;
    *xpnt->list->fix[size].fixaddr = xpnt->list->fix[size].un.newaddr;
  };

  /* Now handle the linker-identified fixups.  These are listed as a
     pair of GOT addresses - the difference is that these are represent a
     pair of pointers to GOT entries rather than a new address and a GOT
     address.  There are a pair of longword NULL pointers that separate the
     linker identified fixups from the linker identified places where we
     simply copy a pointer.  We are effectively replacing pointers in the
     GOT variables here.  */

  while (++size < xpnt->list->size){
    *xpnt->list->fix[size].fixaddr = 
      *((int *) xpnt->list->fix[size].un.newaddr);
    size++;
  };
}

void
__load_shared_libraries (int argc, char **argv, char **env)
{
  struct libentry **ptr;

  if (argc > 0) {
    char buffer [PATH_MAX + 1];	/* Hope noone breaks that. */
    char *path_buffer;
    char *usr_ld_library_path;
    char ** LD_LIBRARY_PATH;
    uid_t uid = getuid();
    /* If we re root, we can do anything we want. */
    int set_ugid = uid && (uid != geteuid() || getgid() != getegid());
    int i, total;

    usr_ld_library_path = findenv ("LD_LIBRARY_PATH", env);
    /* Check if the program is set uid or set gid. */
    if (set_ugid && usr_ld_library_path)
      *usr_ld_library_path = '\0';

    if (usr_ld_library_path && *usr_ld_library_path) {
      int j, k;

      total = 1;
      /* The length of $LD_LIBRARY_PATH is `i'. There
       * is a trailing '\0'. */
      for (i = 0; usr_ld_library_path [i] != '\0'; i++)
        if (usr_ld_library_path [i] == ':') total++;

      /* We also add a '/' before each ':' and the
       * trailing '\0'. The total number of additional
       * '/' is `total' + 1.
       */
      path_buffer = alloca (i + total + 2);

#ifndef	SWITCH_LD_LIBRARY_PATH
      /* append the DEFUALT_LIBRARY_PATH to LD_LIBRARY_PATH.
       * Thanks Ted.
       */
      total += default_total;
#endif

      LD_LIBRARY_PATH = 
#ifdef __i386__
	/* sizeof (char *) == 4 bytes. */
	(char **) alloca (total << 2);
#else
	(char **) alloca (total * sizeof (char *));
#endif
      LD_LIBRARY_PATH [j = 0] = path_buffer;
      for (i = k = 0; usr_ld_library_path [i] != '\0'; i++)
	if (usr_ld_library_path [i] == ':') {
	  path_buffer [k++] = '/';
	  path_buffer [k++] = '\0';
	  LD_LIBRARY_PATH [++j] = &path_buffer [k];
        }
        else {
	  path_buffer [k++] = usr_ld_library_path [i];
        }
	path_buffer [k++] = '/';
	path_buffer [k] = '\0';

#ifndef	SWITCH_LD_LIBRARY_PATH
	/* append the DEFUALT_LIBRARY_PATH to LD_LIBRARY_PATH. */
	for (i = 0; i < default_total; i++)
	  LD_LIBRARY_PATH [++j] = (char *) DEFAULT_LD_LIBRARY_PATH [i];
#endif
    }
    else {
      LD_LIBRARY_PATH = (char **) DEFAULT_LD_LIBRARY_PATH;
      total = default_total;
    }

    for (ptr = __SHARED_LIBRARIES__+2; *ptr; ptr++)
    {
      unsigned *vers = (unsigned *)(*ptr)->addr;

      for (i = 0;;) {
	/* buffer == "". */
	buffer [0] = '\0';
	strcat (buffer, LD_LIBRARY_PATH [i++]);
	strcat (buffer, (*ptr)->name);
	if (uselib(buffer)) {
#ifdef	SWITCH_LD_LIBRARY_PATH
	  /* If we cannot find the shared image, we swicth to the
	   * DEFAULT_LD_LIBRARY_PATH. It may happen in the middle
	   * of loading several shared images. Do we want this?
	   */
	  if (i == total && errno == ENOENT &&
		LD_LIBRARY_PATH != (char **) DEFAULT_LD_LIBRARY_PATH) {
	    LD_LIBRARY_PATH = (char **) DEFAULT_LD_LIBRARY_PATH;
	    total = default_total;
	    i = 0;
	    *usr_ld_library_path = '\0';
	  }
	  else
#endif
		if (i >= total || errno != ENOENT)
			__shlib_fatal (argv [0], buffer, errno);
	}
	else break;
      }

      switch (incompatible(*vers, (*ptr)->vers)) {
      case MAJOR_COMPATIBLE:
	__lib_print (2, "Warning: ", argv [0], ": ", (*ptr)->name,
		 ": incompatible minor version numbers (",
		 (*ptr)->avers, ").\n", NULL);
	break;
      case NOT_COMPATIBLE:
	__shlib_fatal (argv [0], buffer, 0);
        break;
      }
    }
  }
  else {
    for (ptr = __SHARED_LIBRARIES__+2; *ptr; ptr++) {
      __lib_print (1, "\t", (*ptr)->name, " (", (*ptr)->avers, ")\n", NULL);
    }

    while (1) _call_exit (0);
  }
 
  /* Subtract curr addr from .long _foo */
  __do_fixups(&_SHARABLE_CONFLICTS__, 0); 

  __dynamic_resolve (&_SHARABLE_CONFLICTS__);

  /* Add (possibly) new addr to .long _foo */
  __do_fixups(&_SHARABLE_CONFLICTS__, 1);

  /* Set the magic numbers back to the starting values for emacs */
  __reset_magic(&_SHARABLE_CONFLICTS__);
}
