/* first crack at #! implementation --roland */

void
check_hashbang (struct execdata *e,
		mach_port_t replyport, /* Reply port for the RPC request.  */
		file_t file,
		task_t oldtask,
		int flags,
		char *argv, u_int argvlen, boolean_t argv_copy,
		char *envp, u_int envplen, boolean_t envp_copy,
		mach_port_t *dtable, u_int dtablesize,
		mach_port_t *portarray, u_int nports,
		int *intarray, u_int nints,
		mach_port_t *deallocnames, u_int ndeallocnames,
		mach_port_t *destroynames, u_int ndestroynames)
{
  file_t userport (int idx)
    {
      return ((flags & (EXEC_SECURE|EXEC_DEFAULTS)) ||
	      portarray[idx] == MACH_PORT_NULL
	      ? stdports : portarray)[idx];
    }

  static char *ibuf = NULL;	/* XXX not if multithreaded */
  static size ibufsiz = 0;
  char *p;
  char *interp, *arg;		/* Interpreter file name, and first argument */
  size_t len;			/* Length of the argument */
  file_t interp_file;		/* Port open on the interpreter file.  */
  FILE *f = e->stream;

  rewind (f);

  /* Check for our ``magic number''--"#!".  */

  errno = 0;
  if (getc (f) != '#' || getc (f) != '!')
    {
      /* No `#!' here.  If there was a read error (not including EOF),
	 return that error indication.  Otherwise return ENOEXEC to
	 say it's not a file we know how to execute.  */
      e.error = ferror (f) ? errno : ENOEXEC;
      return;
    }

  /* Read the rest of the first line of the file.  */

  interp_len = getline (&ibuf, &ibufsiz, f);
  if (ferror (f))
    {
      e.error = errno ?: EIO;
      return;
    }

  /* Find the name of the interpreter.  */
  p = ibuf;
  interp = strsep (&p, " \t");
  if (interp == NULL)
    {
      /* No blanks found; there is no argument to the interpreter.  */
      interp = ibuf;
      arg = NULL;
      len = 0;
    }
  else
    {
      /* Skip remaining blanks, and the rest of the line is the argument.  */
      p += strspn (p, " \t");
      arg = p;
      len -= arg - interp;
      ++len;			/* Include the terminating null.  */
    }

  /* Open a port on the interpreter file.  */
  if (e.error = hurd_file_name_lookup (userport (INIT_PORT_CRDIR),
				       userport (INIT_PORT_CWDIR),
				       interp, O_EXEC, 0,
				       &interp_file))
    /* We cannot open the interpreter file to execute it.  Lose!  */
    return;

  {
    /* This code is in a local function here for convenience.  Some things in
       this function need to be protected against faults while accessing ARGV
       and ENVP; below, we register to preempt signals on these fault areas
       before calling prepare_args, and unregister afterwards.  When such a
       fault is detected, the handler does `longjmp (args_faulted, 1)'.  */

    jmp_buf args_faulted;

    inline error_t prepare_args (void)
      {
	/* Try to figure out the file's name.
	   We guess that if ARGV[0] contains a slash, it might be the name of
	   the file; and that if it contains no slash, looking for files named
	   by ARGV[0] in the `PATH' environment variable might find it.  */

	char *file_name = NULL;
	size_t namelen;
	error_t error;
	char *name;
	file_t name_file;
	struct stat st;
	int file_fstype;
	fsid_t file_fsid;
	ino_t file_fileno;

	if (error = io_stat (file, &st)) /* XXX insecure */
	  goto out;
	file_fstype = st.st_fstype;
	file_fsid = st.st_fsid;
	file_fileno = st.st_ino;

	if (memchr (argv, '\0', argvlen) == NULL)
	  {
	    name = alloca (argvlen + 1);
	    bcopy (argv, name, argvlen);
	    name[argvlen] = '\0';
	  }
	else
	  name = argv;

	if (strchr (name, '/') != NULL)
	  error = hurd_file_name_lookup (userport (INIT_PORT_CRDIR),
					 userport (INIT_PORT_CWDIR),
					 name, 0, 0, &name_file);
	else if (! setjmp (args_faulted))
	  {
	    /* Search PATH for it.  If we fault accessing ENVP, setjmp
	       will return again, nonzero this time, and we give up.  */

	    const char envar[] = "\0PATH=";
	    char *path, *p;
	    if (envplen >= sizeof (envar) &&
		!memcmp (&envar[1], envp, sizeof (envar) - 2))
	      p = envp - 1;
	    else
	      p = memmem (envar, sizeof (envar) - 1, envp, envplen);
	    if (p != NULL)
	      {
		size_t len;
		p += sizeof (envar) - 1;
		len = strlen (p) + 1;
		path = alloca (len);
		bcopy (p, path, len);
	      }
	    else
	      {
		const size_t len = confstr (_CS_PATH, NULL, 0);
		path = alloca (len);
		confstr (_CS_PATH, path, len);
	      }

	    while ((p = strsep (&path, ":")) != NULL)
	      {
		file_t dir;
		if (*p == '\0')
		  dir = portarray[INIT_PORT_CWDIR];
		else
		  if (hurd_path_lookup (portarray[INIT_PORT_CRDIR],
					portarray[INIT_PORT_CWDIR],
					p, O_EXEC, 0, &dir))
		    continue;
		error = hurd_path_lookup (portarray[INIT_PORT_CRDIR], dir,
					  name, O_EXEC, 0, &name_file);
		if (*p != '\0')
		  mach_port_deallocate (mach_task_self (), dir);
		if (!error)
		  {
		    if (*p != '\0')
		      {
			size_t dirlen = strlen (p), namelen = strlen (name);
			char *new = alloca (dirlen + 1 + namelen + 1);
			memcpy (new, p, dirlen);
			new[dirlen] = '/';
			memcpy (&new[dirlen + 1], name, namelen + 1);
			name = new;
		      }
		    break;
		  }
	      }
	  }
	else
	  name_file = MACH_PORT_NULL;

	if (!error && name_file != MACH_PORT_NULL)
	  {
	    if (!io_stat (name_file, &st) && /* XXX insecure */
		st.st_fstype == file_fstype &&
		st.st_fsid == file_fsid &&
		st.st_ino == file_ino)
	      file_name = name;
	    mach_port_deallocate (mach_task_self (), name_file);
	  }

	if (file_name == NULL)
	  {
	    /* We can't easily find the file.
	       Put it in a file descriptor and pass /dev/fd/N.  */
	    int fd;

	    for (fd = 0; fd < dtablesize; ++fd)
	      if (dtable[fd] == MACH_PORT_NULL)
		break;
	    if (fd == dtablesize)
	      {
		/* Extend the descriptor table.  */
		file_t *newdt = alloca ((dtablesize + 1) * sizeof (file_t));
		memcpy (newdt, dtable, dtablesize++ * sizeof (file_t));
		dtable = newdt;
	      }
	    dtable[fd] = file;

	    file_name = alloca (100);
	    sprintf (file_name, "/dev/fd/%d", fd);
	  }

	/* Prepare the arguments to pass to the interpreter from the original
	   arguments and the name of the script file.  The args will look
	   like `ARGV[0] {ARG} FILE_NAME ARGV[1..n]' (ARG might have been
	   omitted). */

	namelen = strlen (file_name) + 1;

	if (! setjmp (args_faulted))
	  {
	    const size_t new_argvlen = argvlen + len + namelen;
	    char *new_argv, *other_args;
	    if (e.error = vm_allocate (mach_task_self (),
				       &new_argv, new_argvlen, 1))
	      return;
	    other_args = memccpy (new_argv, argv, '\0', argvlen);
	    p = &new_argv[other_args ? other_args - argv : argvlen];
	    if (arg)
	      {
		memcpy (p, arg, len);
		p += len;
	      }
	    memcpy (p, file_name, namelen);
	    p += namelen;
	    if (other_args)
	      memcpy (p, other_args, argvlen - (other_args - argv));
	    if (argv_copy)
	      vm_deallocate (mach_task_self (), argv, argvlen);
	    argv = new_argv;
	    argvlen = new_argvlen;
	    argv_copy = 1;
	  }
	else
	  {
	    /* We got a fault reading ARGV.  So don't use it.  */
	    const char loser[] = "**fault in exec server reading argv[0]**";
	    if (argv_copy)
	      vm_deallocate (mach_task_self (), argv, argvlen);
	    argv_copy = 0;
	    argvlen = sizeof (loser) + namelen;
	    argv = alloca (argvlen);
	    memcpy (argv, loser, sizeof (loser));
	    memcpy (argv + sizeof (loser), file_name, namelen);
	  }

	return 0;
      }

    /* Preempt SIGSEGV signals for the address ranges of ARGV and ENVP.  When
       such a signal arrives, `preempter' is called to decide what handler to
       run; it always returns `handler', which is then invoked as a normal
       signal handler.  Our handler always simply longjmps to ARGS_FAULTED.  */

    void handler (int sig)
      {
	longjmp (args_faulted, 1);
      }
    const thread_t mythread = mach_thread_self ();
    sighandler_t preempter (thread_t thread, int signo, int sigcode)
      {
	return thread == mythread ? handler : SIG_DFL;
      }

    struct hurd_signal_preempt argv_preempter, envp_preempter;

    /* Register the preemptions.  */
    hurd_preempt_signals (&argv_preempter,
			  SIGSEGV, argv, argv + argvlen - 1, preempter);
    hurd_preempt_signals (&envp_preempter,
			  SIGSEGV, envp, envp + envplen - 1, preempter);

    /* Do the work.  Everywhere we might get a page fault inside ARGV or ENVP,
       is inside the zero-return case of an `if' on setjmp (ARGS_FAULTED).  */
    error = prepare_args ();

    /* Unregister the preemptions.  */
    hurd_unpreempt_signals (&argv_preempter, SIGSEGV);
    hurd_unpreempt_signals (&envp_preempter, SIGSEGV);
  }

  /* Execute the interpreter program.

     Pass along the reply port for this request, so the file_exec server
     replies to our client with its result.  (Needs our own file_exec stub
     that takes a user reply port argument, and dealloc arguments for ARGV and
     ENVP.) */

  e.error = XXX_file_exec (interp_file, replyport, oldtask, flags,
			   argv, argv_copy, argvlen, envp, envp_copy, envplen,
			   dtable, dtablesize,
			   portarray, nports, intarray, nints,
			   deallocnames, ndeallocnames,
			   destroynames, ndestroynames);
  mach_port_deallocate (mach_task_self (), interp_file);

  /* The stub function returns nonzero only if there is an error in sending
     the RPC request message; otherwise we tell MiG to send no reply
     message for this exec_exec RPC, because the file_exec server will
     reply for us. */
  if (! e.error)
    e.error = MIG_NO_REPLY;	/* Does MIG_NO_REPLY deallocate??? XXX */
}
