1 /*
   2  * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * jexec for J2SE
  28  *
  29  * jexec is used by the system to allow execution of JAR files.
  30  *    Essentially jexec needs to run java and
  31  *    needs to be a native ISA executable (not a shell script), although
  32  *    this native ISA executable requirement was a mistake that will be fixed.
  33  *    (<ISA> is sparc or i386 or amd64).
  34  *
  35  *    When you execute a jar file, jexec is executed by the system as follows:
  36  *      /usr/java/jre/lib/<ISA>/jexec -jar JARFILENAME
  37  *    so this just needs to be turned into:
  38  *      /usr/java/jre/bin/java -jar JARFILENAME
  39  *
  40  * Solaris systems (new 7's and all 8's) will be looking for jexec at:
  41  *      /usr/java/jre/lib/<ISA>/jexec
  42  * Older systems may need to add this to their /etc/system file:
  43  *      set javaexec:jexec="/usr/java/jre/lib/<ISA>/jexec"
  44  *     and reboot the machine for this to work.
  45  *
  46  * This source should be compiled as:
  47  *      cc -o jexec jexec.c
  48  *
  49  * And jexec should be placed at the following location of the installation:
  50  *      <INSTALLATIONDIR>/jre/lib/<ISA>/jexec  (for Solaris)
  51  *      <INSTALLATIONDIR>/lib/jexec            (for Linux)
  52  *
  53  * NOTE: Unless <INSTALLATIONDIR> is the "default" JDK on the system
  54  *       (i.e. /usr/java -> <INSTALLATIONDIR>), this jexec will not be
  55  *       found.  The 1.2 java is only the default on Solaris 8 and
  56  *       on systems where the 1.2 packages were installed and no 1.1
  57  *       java was found.
  58  *
  59  * NOTE: You must use 1.2 jar to build your jar files. The system
  60  *       doesn't seem to pick up 1.1 jar files.
  61  *
  62  * NOTE: We don't need to set LD_LIBRARY_PATH here, even though we
  63  *       are running the actual java binary because the java binary will
  64  *       look for it's libraries through it's own runpath, which uses
  65  *       $ORIGIN.
  66  *
  67  * NOTE: This jexec should NOT have any special .so library needs because
  68  *       it appears that this executable will NOT get the $ORIGIN of jexec
  69  *       but the $ORIGIN of the jar file being executed. Be careful to keep
  70  *       this program simple and with no .so dependencies.
  71  */
  72 
  73 #include <stdlib.h>
  74 #include <stdio.h>
  75 #include <unistd.h>
  76 #include <string.h>
  77 #include <limits.h>
  78 #include <errno.h>
  79 
  80 static const int CRAZY_EXEC = ENOEXEC;
  81 static const int BAD_MAGIC  = ENOEXEC;
  82 
  83 static const char * BAD_EXEC_MSG     = "jexec failed";
  84 static const char * CRAZY_EXEC_MSG   = "missing args";
  85 static const char * MISSING_JAVA_MSG = "can't locate java";
  86 static const char * UNKNOWN_ERROR    = "unknown error";
  87 
  88 /* Define a constant that represents the number of directories to pop off the
  89  * current location to find the java binary */
  90 static const int RELATIVE_DEPTH = 3;
  91 
  92 /* path to java after popping */
  93 static const char * BIN_PATH = "/bin/java";
  94 
  95 /* flag used when running JAR files */
  96 static const char * JAR_FLAG = "-jar";
  97 
  98 int main(int argc, const char * argv[]);
  99 void errorExit(int error, const char * message);
 100 int getJavaPath(const char * path, char * buf, int depth);
 101 
 102 /*
 103  * This is the main entry point.  This program (jexec) will attempt to execute
 104  * a JAR file by finding the Java program (java), relative to its own location.
 105  * The exact location of the Java program depends on the platform, i.e.
 106  *
 107  *      <INSTALLATIONDIR>/jre/lib/<ISA>/jexec  (for Solaris)
 108  *      <INSTALLATIONDIR>/lib/jexec            (for Linux JDK)
 109  *
 110  * Once the Java program is found, this program copies any remaining arguments
 111  * into another array, which is then used to exec the Java program.
 112  *
 113  * On Linux this program does some additional steps.  When copying the array of
 114  * args, it is necessary to insert the "-jar" flag between arg[0], the program
 115  * name, and the original arg[1], which is presumed to be a path to a JAR file.
 116  * It is also necessary to verify that the original arg[1] really is a JAR file.
 117  * (These steps are unnecessary on Solaris because they are taken care of by
 118  * the kernel.)
 119  */
 120 int main(int argc, const char * argv[]) {
 121     /* We need to exec the original arguments using java, instead of jexec.
 122      * Also, for Linux, it is necessary to add the "-jar" argument between
 123      * the new arg[0], and the old arg[1].  To do this we will create a new
 124      * args array. */
 125     char          java[PATH_MAX + 1];    /* path to java binary  */
 126     const char ** nargv = NULL;          /* new args array       */
 127     int           nargc = 0;             /* new args array count */
 128     int           argi  = 0;             /* index into old array */
 129 
 130     /* Make sure we have something to work with */
 131     if ((argc < 1) || (argv == NULL)) {
 132         /* Shouldn't happen... */
 133         errorExit(CRAZY_EXEC, CRAZY_EXEC_MSG);
 134     }
 135 
 136     /* Get the path to the java binary, which is in a known position relative
 137      * to our current position, which is in argv[0]. */
 138     if (getJavaPath(argv[argi++], java, RELATIVE_DEPTH) != 0) {
 139         errorExit(errno, MISSING_JAVA_MSG);
 140     }
 141 
 142     nargv = (const char **) malloc((argc + 2) * (sizeof (const char *)));
 143     nargv[nargc++] = java;
 144 
 145     if (argc >= 2) {
 146         const char * jarfile = argv[argi++];
 147         const char * message = NULL;
 148 
 149         /* the next argument is the path to the JAR file */
 150         nargv[nargc++] = jarfile;
 151     }
 152 
 153     /* finally copy any remaining arguments */
 154     while (argi < argc) {
 155         nargv[nargc++] = argv[argi++];
 156     }
 157 
 158     /* finally add one last terminating null */
 159     nargv[nargc++] = NULL;
 160 
 161     /* It's time to exec the java binary with the new arguments.  It
 162      * is possible that we've reached this point without actually
 163      * having a JAR file argument (i.e. if argc < 2), but we still
 164      * want to exec the java binary, since that will take care of
 165      * displaying the correct usage. */
 166     execv(java, (char * const *) nargv);
 167 
 168     /* If the exec worked, this process would have been replaced
 169      * by the new process.  So any code reached beyond this point
 170      * implies an error in the exec. */
 171     free(nargv);
 172     errorExit(errno, BAD_EXEC_MSG);
 173     return 0; // keep the compiler happy
 174 }
 175 
 176 
 177 /*
 178  * Exit the application by setting errno, and writing a message.
 179  *
 180  * Parameters:
 181  *     error   - errno is set to this value, and it is used to exit.
 182  *     message - the message to write.
 183  */
 184 void errorExit(int error, const char * message) {
 185     if (error != 0) {
 186         errno = error;
 187         perror((message != NULL) ? message : UNKNOWN_ERROR);
 188     }
 189 
 190     exit((error == 0) ? 0 : 1);
 191 }
 192 
 193 
 194 /*
 195  * Get the path to the java binary that should be relative to the current path.
 196  *
 197  * Parameters:
 198  *     path  - the input path that the java binary that should be relative to.
 199  *     buf   - a buffer of size PATH_MAX or greater that the java path is
 200  *             copied to.
 201  *     depth - the number of names to trim off the current path, including the
 202  *             name of this program.
 203  *
 204  * Returns:
 205  *     This function returns 0 on success; otherwise it returns the value of
 206  *     errno.
 207  */
 208 int getJavaPath(const char * path, char * buf, int depth) {
 209     int result = 0;
 210 
 211     /* Get the full path to this program.  Depending on whether this is Solaris
 212      * or Linux, this will be something like,
 213      *
 214      *     <FOO>/jre/lib/<ISA>/jexec  (for Solaris)
 215      *     <FOO>/lib/jexec            (for Linux)
 216      */
 217     if (realpath(path, buf) != NULL) {
 218         int count = 0;
 219 
 220         /* Pop off the filename, and then subdirectories for each level of
 221          * depth */
 222         for (count = 0; count < depth; count++) {
 223             *(strrchr(buf, '/')) = '\0';
 224         }
 225 
 226         /* Append the relative location of java, creating something like,
 227          *
 228          *     <FOO>/jre/bin/java  (for Solaris)
 229          *     <FOO>/bin/java      (for Linux)
 230          */
 231         strcat(buf, BIN_PATH);
 232     }
 233     else {
 234         /* Failed to get the path */
 235         result = errno;
 236     }
 237 
 238     return (result);
 239 }