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