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