...

вторник, 11 июля 2017 г.

[Из песочницы] Запуск Java классов и JAR-ов не по учебнику

Меня давно занимала мысль как в Linux-е запускать программы на Java без вспомогательных Bash скриптов. Я не видел приемлемого решения, если не считать способ «bash script payload», когда в конец скрипта помещается бинарный файл.

Но на прошлой неделе случайно наткнулся на модуль ядра binfmt_misc, с помощью которого можно перехватить исполнение файла по его magic number. Для этого через update-binfmts добавляется собственный обработчик для получения имени исполняемого файла и аргументов пользователя.

Первое открытие


Как оказалось в моей Ubuntu 16.04 уже зарегистрирован обработчик для JAR файлов:
update-binfmts --display
...
jar (enabled):
     package = openjdk-8
        type = magic
      offset = 0
       magic = PK\x03\x04
        mask = 
 interpreter = /usr/bin/jexec
    detector = 


Отдав команду chmod +x foo.bar я радостно потирал руки, но реальность оказалось сурова — запуск ./foo.jar выдал следующее:
invalid file (bad magic number): Exec format error


Погуглив я нашел обросший мхом баг http://ift.tt/2tJDEXY Как оказывается сборка через Maven не добавляет «0xcafe» magic number в начало JAR файла. Не менее безответственно ведет себя и плагин maven-assembly-plugin. Что не нравится /usr/bin/jexec, зарегистрированному обработчику по умолчанию.

Погуглив еще я нашел решение проблемы через установку jarwrapper пакета. После установки добавляется другой обработчик /usr/bin/jarwrapper и страховка /usr/bin/jardetector (проверяет по META-INF что это действительно JAR). Но изучив код обработчика мне не понравилась куча лишней работы, которую делает скрипт запуская множество вспомогательных программ.

Поэтому решением стал собственный обработчик:

#!/bin/sh
#/usr/bin/jarinvoke

JAR=$1
shift

exec java -jar $JAR $@


Дальше открываем файл sudo gedit /var/lib/binfmts/jar и регистрируем обработчик заменив строчку с /usr/bin/jexec на /usr/bin/jarinvoke. На самом деле это плохое решение и лучше создать собственную группу (об этом ниже), но для первичного понимания сойдет.

Для вступления изменений в силу может потребоваться выполнить:

sudo update-binfmts --disable jar && sudo update-binfmts --enable jar


После чего можете запускать JAR файлы как любые другие исполняемые файлы.

Исполняемые классы


Теперь можно идти дальше и сделать из Java классов исполняемые файлы, где jarwrapper не сможет помочь. Обработчик будет работать только для классов с пакетом по умолчанию (т.е. классы с отсутствующим package заголовком). Может можно сделать и лучше, но мне хватило такой функциональности для «скриптования» на Java:
#!/bin/sh
# /usr/bin/clsinvoke

CLASS_FILE=$1
shift

ABSOLUTE_PATH=`readlink -f $CLASS_FILE`

CLASS=`basename $ABSOLUTE_PATH`
CLASS=${CLASS%.*}
CLASSPATH=`dirname $ABSOLUTE_PATH`

exec java -cp $CLASSPATH $CLASS $@


После чего создаём собственный обработчик (этим же способом можно создать новый обработчик для JAR-ов не редактируя /usr/bin/jexec):
sudo update-binfmts --package clsinvoke --install clsinvoke /usr/bin/clsinvoke --magic '\xca\xfe\xba\xbe'


Тестируем:
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World");
    }
}


javac HellWorld.java
chmod +x HelloWorld.class
./HelloWorld.class
Hello, World


Можно пойти и дальше, сделав более сложный обработчик, который по импорту классов будет определять какие библиотеки добавить в CLASSPATH из ~/.m2, но это отдельная история. Сейчас интересен взгляд со стороны, замечания, дополнения, если таковые есть. После чего думаю оформить это в deb пакет и выложить всё на гитхабе.

Комментарии (0)

    Let's block ads! (Why?)

    Комментариев нет:

    Отправить комментарий