Разъяснение по использованию Program и ProgramArguments в launchd plist.

Вопрос или проблема

Может кто-нибудь, пожалуйста, объяснить, как должны использоваться параметры конфигурации Program и ProgramArguments в launchd? Я пытался зарегистрировать службу, которую я бы запускал в командной строке следующим образом:

$ /foo/bar/baz/python /foo/bar/baz/service start

Я пробовал разделить это различными способами для launchd:

<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

или

<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/service start</string>
</array>

или

<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/python</string>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

или

<key>Program</key>
<string>/bin/bash</string>
<key>ProgramArguments</key>
<array>
    <string>-c</string>
    <string>/foo/bar/baz/python /foo/bar/baz/service start</string>
</array>

и почти любая другая вариация, которая казалась бы логичной. Однако служба всегда завершалась неудачей с различными ошибками. Единственное, что сработало — это создать .sh скрипт с точной строкой и запустить его через launchd.

Итак, чтобы раз и навсегда понять службы launchd: как launchd использует эти два параметра конфигурации, как я мог бы повторить свою команду bash с их помощью и в чем разница между ними?
Или, возможно, я просто наткнулся на проблему выполнения этой конкретной службы с некоторыми переменными окружения, которые существуют при выполнении через bash? Сама служба не предоставляла никакой полезной информации.

Я проконсультировался с execvp(3) документацией, как это рекомендовано в launchd.plist(5), но это не особо помогло в понимании.

Если указано Program, оно всегда будет выполняемым программой, даже если ProgramArguments также указаны.

<key>Program</key>
<string>/foo/bar/baz/python</string>
<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/python</string>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

В этом случае первый элемент в ProgramArguments фактически не оценивается, но передается как argv[0] программе, которая выполняется. Обычно это не нужно, но может быть полезно. Например, программа может проверить argv[0] и работать в другом режиме в зависимости от этого значения.

Для всего остального достаточно использовать только ProgramArguments. Это определение задания работает так же, как и выше:

<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/python</string>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

POSIX делает различие между фактической программой, которую вызывают, и именем программы. Например, если у вас есть бинарный /bin/abc и вы создаете символическую ссылку ln -s /bin/abc xyz, и теперь вы вызываете программу как ./xyz, то фактически вызывается программа /bin/abc, но имя программы ./xyz.

Функция main() бинарного файла получает массив аргументов, обычно называемый argv, и argv[0] содержит имя бинарного файла. Так что в приведенном выше примере это будет ./xyz. Бинарный файл не имеет возможности узнать, что он фактически находится в /bin и называется abc.

Некоторые бинарные файлы действительно ведут себя по-разному в зависимости от того, под каким именем они вызываются. Например, gmake (GNU Make) поддерживает многие специфические для GNU расширения, но когда он вызывается как make, он ведет себя как стандартный make без этих расширений (на многих системах make это просто символическая ссылка на gmake).

Так что у вас есть бинарный файл для выполнения, имя бинарного файла и дополнительные аргументы (опционально).

Если вы указываете только ProgramArguments, то первый элемент массива используется и для бинарного файла, и для имени программы. Если вы указываете и Program, и ProgramArguments, то Program — это бинарный файл для выполнения, а первый элемент ProgramArguments — это имя бинарного файла.

<key>Program</key>
<string>/usr/bin/gmake</string>
<key>ProgramArguments</key>
<array>
    <string>make</string>
    <string>...</string>
    <string>...</string>
</array>

Это гарантирует, что будет вызываться gmake, но как имя программы будет передано make.

Синтаксис execvp таков:

execvp( const char *file, char *const argv[] );

Где file — это бинарный файл для выполнения, а argv — массив, передаваемый основной функции этого бинарного файла. Однако argv[0] не является первым аргументом, а именем программы.

Таким образом, если вы просто указываете ProgramArgument, то launchd вызовет функцию как

execvp(ProgramArguments[0], ProgramArguments);

но если вы указываете Program и ProgramArgument, то launchd вызовет функцию как

execvp(Program, ProgramArguments);

позволяя вам задать программе любое имя, которое вы хотите. Однако, если вам вообще не нужны аргументы, вы можете просто указать Program, и launchd вызовет функцию как

execvp(Program, [Program]);

превращая Program в массив с единственным элементом — именем программы, который просто совпадает с путем к бинарному файлу.

Ответ или решение

Понимание использования параметров Program и ProgramArguments в launchd

В мире macOS launchd является ключевым механизмом управления службами и процессами. Однако, использование launchd для запуска приложений может быть не совсем очевидным, особенно в контексте различий между параметрами Program и ProgramArguments. Рассмотрим это подробнее.

Параметр Program

Параметр Program используется для указания полного пути к исполняемой программе. Это значит, что именно данный файл будет запускаться launchd. Задание Program необязательно, если вы используете только один параметр ProgramArguments — в таком случае первым элементом массива ProgramArguments будет считаться как исполняемый файл.

Параметр ProgramArguments

ProgramArguments — это массив, где:

  • Первый элемент представляет либо полный путь к программе, либо имя программы, если также указан Program.
  • Последующие элементы массива ProgramArguments представляют собой аргументы, которые передаются в основную функцию программы.

Как работают Program и ProgramArguments

Когда вы указываете только ProgramArguments, launchd реализует вызов:

execvp(ProgramArguments[0], ProgramArguments);

Здесь ProgramArguments[0] служит как путь/имя программы, аргументами которой являются все последующие элементы массива.

Если вы определяете оба параметра, Program получает путь к исполняемому файлу, а ProgramArguments[0] используется как имя программы:

execvp(Program, ProgramArguments);

Это позволяет запускать программу под специфичным именем, что может повлиять на поведение программы, если оно зависит от argv[0].

Пример использования

Предположим, вы хотите запустить скрипт Python с помощью launchd, как на командной строке:

/foo/bar/baz/python /foo/bar/baz/service start

Для правильного исполнения через launchd ваш .plist файл может выглядеть следующим образом, используя только ProgramArguments:

<key>ProgramArguments</key>
<array>
    <string>/foo/bar/baz/python</string>
    <string>/foo/bar/baz/service</string>
    <string>start</string>
</array>

Заключение

В правильной конфигурации launchd важно точно определять роли Program и ProgramArguments, чтобы обеспечить корректную работу вашей службы. Если программа подбирает своё поведение на основе argv[0], рассматривайте возможность использования обоих параметров. В остальных случаях настройка исключительно ProgramArguments может быть достаточной. Внимание к деталям и четкое понимание этих параметров поможет избежать ошибок и обеспечить надежную работу ваших сервисов на платформе macOS.

Оцените материал
Добавить комментарий

Капча загружается...