1 /*
   2  * Copyright (c) 1999, 2013, 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 #ifdef __linux__
  80 #  include <sys/types.h>
  81 #  include <sys/stat.h>
  82 #  include <fcntl.h>
  83 #  include "jni.h"
  84 #  include "manifest_info.h"
  85 #endif
  86 
  87 static const int CRAZY_EXEC = ENOEXEC;
  88 static const int BAD_MAGIC  = ENOEXEC;
  89 
  90 static const char * BAD_EXEC_MSG     = "jexec failed";
  91 static const char * CRAZY_EXEC_MSG   = "missing args";
  92 static const char * MISSING_JAVA_MSG = "can't locate java";
  93 static const char * BAD_ARG_MSG      = "incorrect number of arguments";
  94 static const char * MEM_FAILED_MSG   = "memory allocation failed";
  95 #ifdef __linux__
  96 static const char * BAD_PATHNAME_MSG = "invalid path";
  97 static const char * BAD_FILE_MSG     = "invalid file";
  98 static const char * BAD_MAGIC_MSG    = "invalid file (bad magic number)";
  99 #endif
 100 static const char * UNKNOWN_ERROR    = "unknown error";
 101 
 102 /* Define a constant that represents the number of directories to pop off the
 103  * current location to find the java binary */
 104 #ifdef __linux__
 105 static const int RELATIVE_DEPTH = 2;
 106 #else /* Solaris */
 107 static const int RELATIVE_DEPTH = 3;
 108 #endif
 109 
 110 /* path to java after popping */
 111 static const char * BIN_PATH = "/bin/java";
 112 
 113 /* flag used when running JAR files */
 114 static const char * JAR_FLAG = "-jar";
 115 
 116 
 117 #ifdef __linux__
 118 /* largest possible size for a local file header */
 119 static const size_t CHUNK_SIZE = 65535;
 120 
 121 /* smallest possible size for a local file header */
 122 static const ssize_t MIN_SIZE = LOCHDR + 1 + 4;
 123 #endif
 124 
 125 
 126 int main(int argc, const char * argv[]);
 127 void errorExit(int error, const char * message);
 128 int getJavaPath(const char * path, char * buf, int depth);
 129 #ifdef __linux__
 130 const char * isJar(const char * path);
 131 #endif
 132 
 133 
 134 /*
 135  * This is the main entry point.  This program (jexec) will attempt to execute
 136  * a JAR file by finding the Java program (java), relative to its own location.
 137  * The exact location of the Java program depends on the platform, i.e.
 138  *
 139  *      <INSTALLATIONDIR>/jre/lib/<ISA>/jexec  (for Solaris)
 140  *      <INSTALLATIONDIR>/lib/jexec            (for Linux JDK)
 141  *
 142  * Once the Java program is found, this program copies any remaining arguments
 143  * into another array, which is then used to exec the Java program.
 144  *
 145  * On Linux this program does some additional steps.  When copying the array of
 146  * args, it is necessary to insert the "-jar" flag between arg[0], the program
 147  * name, and the original arg[1], which is presumed to be a path to a JAR file.
 148  * It is also necessary to verify that the original arg[1] really is a JAR file.
 149  * (These steps are unnecessary on Solaris because they are taken care of by
 150  * the kernel.)
 151  */
 152 int main(int argc, const char * argv[]) {
 153     /* We need to exec the original arguments using java, instead of jexec.
 154      * Also, for Linux, it is necessary to add the "-jar" argument between
 155      * the new arg[0], and the old arg[1].  To do this we will create a new
 156      * args array. */
 157     char          java[PATH_MAX + 1];    /* path to java binary  */
 158     const char ** nargv = NULL;          /* new args array       */
 159     int           nargc = 0;             /* new args array count */
 160     int           argi  = 0;             /* index into old array */
 161     size_t        alen  = 0;             /* length of new array */
 162 
 163     /* Make sure we have something to work with */
 164     if ((argc < 1) || (argv == NULL)) {
 165         /* Shouldn't happen... */
 166         errorExit(CRAZY_EXEC, CRAZY_EXEC_MSG);
 167     }
 168 
 169     /* Get the path to the java binary, which is in a known position relative
 170      * to our current position, which is in argv[0]. */
 171     if (getJavaPath(argv[argi++], java, RELATIVE_DEPTH) != 0) {
 172         errorExit(errno, MISSING_JAVA_MSG);
 173     }
 174     alen = (argc + 2) * (sizeof (const char *));
 175     if (alen <= 0 || alen > INT_MAX / sizeof(char *)) {
 176         errorExit(errno, BAD_ARG_MSG);
 177     }
 178     nargv = (const char **) malloc(alen);
 179     if (nargv == NULL) {
 180         errorExit(errno, MEM_FAILED_MSG);
 181     }
 182     nargv[nargc++] = java;
 183 
 184 #ifdef __linux__
 185     /* The "-jar" flag is already in the original args list on Solaris,
 186      * so it only needs to be added on Linux. */
 187     nargv[nargc++] = JAR_FLAG;
 188 #endif
 189 
 190     if (argc >= 2) {
 191         const char * jarfile = argv[argi++];
 192         const char * message = NULL;
 193 
 194 #ifdef __linux__
 195         /* On Linux we also need to make sure argv[1] is really a JAR
 196          * file (this will also resolve any symlinks, which helps). */
 197         char jarPath[PATH_MAX + 1];
 198 
 199         if (realpath(jarfile, jarPath) == NULL) {
 200             errorExit(errno, BAD_PATHNAME_MSG);
 201         }
 202 
 203         message = isJar(jarPath);
 204         if (message != NULL) {
 205             errorExit(errno, message);
 206         }
 207 
 208         jarfile = jarPath;
 209 #endif
 210         /* the next argument is the path to the JAR file */
 211         nargv[nargc++] = jarfile;
 212     }
 213 
 214     /* finally copy any remaining arguments */
 215     while (argi < argc) {
 216         nargv[nargc++] = argv[argi++];
 217     }
 218 
 219     /* finally add one last terminating null */
 220     nargv[nargc++] = NULL;
 221 
 222     /* It's time to exec the java binary with the new arguments.  It
 223      * is possible that we've reached this point without actually
 224      * having a JAR file argument (i.e. if argc < 2), but we still
 225      * want to exec the java binary, since that will take care of
 226      * displaying the correct usage. */
 227     execv(java, (char * const *) nargv);
 228 
 229     /* If the exec worked, this process would have been replaced
 230      * by the new process.  So any code reached beyond this point
 231      * implies an error in the exec. */
 232     free(nargv);
 233     errorExit(errno, BAD_EXEC_MSG);
 234     return 0; // keep the compiler happy
 235 }
 236 
 237 
 238 /*
 239  * Exit the application by setting errno, and writing a message.
 240  *
 241  * Parameters:
 242  *     error   - errno is set to this value, and it is used to exit.
 243  *     message - the message to write.
 244  */
 245 void errorExit(int error, const char * message) {
 246     if (error != 0) {
 247         errno = error;
 248         perror((message != NULL) ? message : UNKNOWN_ERROR);
 249     }
 250 
 251     exit((error == 0) ? 0 : 1);
 252 }
 253 
 254 
 255 /*
 256  * Get the path to the java binary that should be relative to the current path.
 257  *
 258  * Parameters:
 259  *     path  - the input path that the java binary that should be relative to.
 260  *     buf   - a buffer of size PATH_MAX or greater that the java path is
 261  *             copied to.
 262  *     depth - the number of names to trim off the current path, including the
 263  *             name of this program.
 264  *
 265  * Returns:
 266  *     This function returns 0 on success; otherwise it returns the value of
 267  *     errno.
 268  */
 269 int getJavaPath(const char * path, char * buf, int depth) {
 270     int result = 0;
 271 
 272     /* Get the full path to this program.  Depending on whether this is Solaris
 273      * or Linux, this will be something like,
 274      *
 275      *     <FOO>/jre/lib/<ISA>/jexec  (for Solaris)
 276      *     <FOO>/lib/jexec            (for Linux)
 277      */
 278     if (realpath(path, buf) != NULL) {
 279         int count = 0;
 280 
 281         /* Pop off the filename, and then subdirectories for each level of
 282          * depth */
 283         for (count = 0; count < depth; count++) {
 284             *(strrchr(buf, '/')) = '\0';
 285         }
 286 
 287         /* Append the relative location of java, creating something like,
 288          *
 289          *     <FOO>/jre/bin/java  (for Solaris)
 290          *     <FOO>/bin/java      (for Linux)
 291          */
 292         strcat(buf, BIN_PATH);
 293     }
 294     else {
 295         /* Failed to get the path */
 296         result = errno;
 297     }
 298 
 299     return (result);
 300 }
 301 
 302 
 303 #ifdef __linux__
 304 /*
 305  * Check if the given file is a JAR file.
 306  *
 307  * Parameters:
 308  *     path  - the path to the file to check for JAR magic.
 309  *
 310  * Returns:
 311  *     This function return NULL on success.  Otherwise, errno is set, and it
 312  *     returns a message that indicates what caused the failure.
 313  */
 314 const char * isJar(const char * path) {
 315     const char * result = BAD_FILE_MSG;
 316 
 317     int fd = open(path, O_RDONLY);
 318     if (fd != -1) {
 319         unsigned char buf[CHUNK_SIZE];
 320 
 321         ssize_t count = read(fd, buf, CHUNK_SIZE);
 322         if (count >= MIN_SIZE) {
 323             result = BAD_MAGIC_MSG;
 324 
 325             // be sure the file is at least a ZIP file
 326             if (GETSIG(buf) == LOCSIG) {
 327 
 328                 off_t flen  = LOCNAM(buf);
 329                 off_t xlen  = LOCEXT(buf);
 330                 off_t start = LOCHDR + flen;
 331                 off_t end   = start  + xlen;
 332 
 333                 if (end <= count) {
 334                     while (start < end) {
 335                         off_t xhid  = SH(buf, start);
 336                         off_t xdlen = SH(buf, start + 2);
 337 
 338                         start += 4 + xdlen;
 339                         if (xhid == 0xcafe) {
 340                             // found the JAR magic
 341                             result = NULL;
 342                             break;
 343                         }
 344                     }
 345                 }
 346             }
 347         }
 348 
 349         if (result != NULL) {
 350             errno = BAD_MAGIC;
 351         }
 352 
 353         close (fd);
 354     }
 355 
 356     return (result);
 357 }
 358 #endif