На пути к пониманию доступности xdg-open - PullRequest
0 голосов
/ 29 ноября 2018

Я хочу открыть изображение, а в Windows я делаю:

#include <windows.h>
..
ShellExecute(NULL, "open", "https://gsamaras.files.wordpress.com/2018/11/chronosgod.png", NULL, NULL, SW_SHOWNORMAL);

Я бы хотел использовать подход Linux, где намного проще запускать что-то на лету.Пример:

char s[100];
snprintf(s, sizeof s, "%s %s", "xdg-open", "https://gsamaras.files.wordpress.com/2018/11/chronosgod.png");
system(s);

В моем Ubuntu это работает.Однако при запуске этого в Wandbox ( Live Demo ) или в любом другом онлайн-компиляторе я, скорее всего, получаю сообщение об ошибке:

sh:1: xdg-open: не найдено

, несмотря на то, что эти онлайн-компиляторы, похоже, живут в Linux ( проверено ).Я не ожидаю, что онлайн-компилятор откроет для меня браузер, но я ожидал, что код будет работать без ошибок.Ах, и забудьте о Mac (личный ноутбук, ограничивающий количество моих компьютеров).

Поскольку у меня нет другого компьютера с Linux для проверки, мой вопрос: могу ли я ожидать, что этот код будет работать в большинстве случаев?из основных дистрибутивов Linux?

Может быть, тот факт, что он не работал на онлайн-компиляторах, вводит в заблуждение.


PS: Это для моего поста на God of Time так что не беспокойтесь о безопасности.

Ответы [ 2 ]

0 голосов
/ 29 ноября 2018

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


xdg-open является частью утилит интеграции с рабочим столом freedesktop.org, как часть проекта Portland .Можно ожидать, что они будут доступны на любом компьютере под управлением настольной среды , участвующей в freedesktop.org .Это включает в себя GNOME, KDE и Xfce.

Проще говоря, это рекомендуемый способ открытия ресурса (будь то файл или URL), когда используется среда рабочего стола, влюбое приложение, которое предпочитает пользователь.

Если не используется среда рабочего стола, то нет никаких оснований ожидать, что xdg-open также будет доступен.


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

#define  _POSIX_C_SOURCE  200809L
#define  _GNU_SOURCE
//
// SPDX-License-Identifier: CC0-1.0
//
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <dirent.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Number of bits in an unsigned long. */
#define  ULONG_BITS  (CHAR_BIT * sizeof (unsigned long))

/* Helper function to open /dev/null to a specific descriptor.
*/
static inline int devnullfd(const int fd)
{
    int  tempfd;

    /* Sanity check. */
    if (fd == -1)
        return errno = EINVAL;

    do {
        tempfd = open("/dev/null", O_RDWR | O_NOCTTY);
    } while (tempfd == -1 && errno == EINTR);
    if (tempfd == -1)
        return errno;

    if (tempfd != fd) {
        if (dup2(tempfd, fd) == -1) {
            const int  saved_errno = errno;
            close(tempfd);
            return errno = saved_errno;
        }
        if (close(tempfd) == -1)
            return errno;
    }

    return 0;
}

/* Helper function to close all except small descriptors
   specified in the mask. For obvious reasons, this is not
   thread safe, and is only intended to be used in recently
   forked child processes. */
static void closeall(const unsigned long  mask)
{
    DIR           *dir;
    struct dirent *ent;
    int            dfd;

    dir = opendir("/proc/self/fd/");
    if (!dir) {
        /* Cannot list open descriptors.  Just try and close all. */
        const long  fd_max = sysconf(_SC_OPEN_MAX);
        long        fd;

        for (fd = 0; fd < ULONG_BITS; fd++)
            if (!(mask & (1uL << fd)))
                close(fd);

        for (fd = ULONG_BITS; fd <= fd_max; fd++)
            close(fd);

        return;
    }

    dfd = dirfd(dir);

    while ((ent = readdir(dir)))
        if (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') {
            const char *p = &ent->d_name[1];
            int         fd = ent->d_name[0] - '0';

            while (*p >= '0' && *p <= '9')
                fd = (10 * fd) + *(p++) - '0';

            if (*p)
                continue;

            if (fd == dfd)
                continue;

            if (fd < ULONG_MAX && (mask & (1uL << fd)))
                continue;

            close(fd);
        }

    closedir(dir);
}

closeall(0) пытается закрыть все дескрипторы открытых файлов, а devnullfd(fd) пытается открыть fd для /dev/null.Они используются, чтобы гарантировать, что даже если пользователь подделывает xdg-open, дескрипторы файлов не будут пропущены;передается только имя файла или URL.

В не Linux-системах POSIXy вы можете заменить их на что-то более подходящее.На BSD используйте closefrom() и обрабатывайте первые дескрипторы ULONG_MAX в цикле.

Сама функция xdg_open(file-or-url) является чем-то вроде

/* Launch the user-preferred application to open a file or URL.
   Returns 0 if success, an errno error code otherwise.
*/ 
int xdg_open(const char *file_or_url)
{
    pid_t  child, p;
    int    status;

    /* Sanity check. */
    if (!file_or_url || !*file_or_url)
        return errno = EINVAL;

    /* Fork the child process. */
    child = fork();
    if (child == -1)
        return errno;
    else
    if (!child) {
        /* Child process. */

        uid_t  uid = getuid();  /* Real, not effective, user. */
        gid_t  gid = getgid();  /* Real, not effective, group. */

        /* Close all open file descriptors. */
        closeall(0);

        /* Redirect standard streams, if possible. */
        devnullfd(STDIN_FILENO);
        devnullfd(STDOUT_FILENO);
        devnullfd(STDERR_FILENO);

        /* Drop elevated privileges, if any. */
        if (setresgid(gid, gid, gid) == -1 ||
            setresuid(uid, uid, uid) == -1)
            _Exit(98);

        /* Have the child process execute in a new process group. */
        setsid();

        /* Execute xdg-open. */
        execlp("xdg-open", "xdg-open", file_or_url, (char *)0);

        /* Failed. xdg-open uses 0-5, we return 99. */
        _Exit(99);
    }

    /* Reap the child. */
    do {
        status = 0;
        p = waitpid(child, &status, 0);
    } while (p == -1 && errno == EINTR);
    if (p == -1)
        return errno;

    if (!WIFEXITED(status)) {
        /* Killed by a signal. Best we can do is I/O error, I think. */
        return errno = EIO;
    }

    switch (WEXITSTATUS(status)) {
    case 0: /* No error. */
        return errno = 0; /* It is unusual, but robust to explicitly clear errno. */
    case 1: /* Error in command line syntax. */
        return errno = EINVAL;      /* Invalid argument */
    case 2: /* File does not exist. */
        return errno = ENOENT;      /* No such file or directory */
    case 3: /* A required tool could not be found. */
        return errno = ENOSYS;      /* Not implemented */
    case 4: /* Action failed. */
        return errno = EPROTO;      /* Protocol error */
    case 98: /* Identity shenanigans. */
        return errno = EACCES;      /* Permission denied */
    case 99: /* xdg-open does not exist. */
        return errno = ENOPKG;      /* Package not installed */
    default:
        /* None of the other values should occur. */
        return errno = ENOSYS;      /* Not implemented */
    }
}

Как уже упоминалосьон старается закрыть все дескрипторы открытых файлов, перенаправляет стандартные потоки на /dev/null, обеспечивает эффективное и реальное совпадение идентификаторов (в случае, если это используется в двоичном файле setuid) и передает успех / неудачу, используя состояние выхода дочернего процесса.

Вызовы setresuid() и setresgid() доступны только в операционных системах, в которых сохранены идентификаторы пользователей и групп.В других случаях используйте seteuid(uid) и setegid().

Эта реализация пытается сбалансировать настраиваемость пользователя с безопасностью.Пользователи могут установить PATH так, чтобы выполнялся их любимый xdg-open, но функция пытается гарантировать, что никакая конфиденциальная информация или привилегии не будут переданы этому процессу.

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

Как минимальный тестmain(), попробуйте следующее:

int main(int argc, char *argv[])
{
    int  arg, status;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s FILE-OR-URL ...\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This example program opens each specified file or URL\n");
        fprintf(stderr, "xdg-open(1), and outputs success or failure for each.\n");
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    status = EXIT_SUCCESS;

    for (arg = 1; arg < argc; arg++)
        if (xdg_open(argv[arg])) {
            printf("%s: %s.\n", argv[arg], strerror(errno));
            status = EXIT_FAILURE;
        } else
            printf("%s: Opened.\n", argv[arg]);

    return status;
}

Как указано в идентификаторе лицензии SPDX, этот пример кода лицензирован по Creative Commons Zero 1.0 .Используйте его как хотите, в любом коде.

0 голосов
/ 29 ноября 2018

xdg-open является частью xdg-utils.Они почти всегда устанавливаются с рабочим столом GUI любого дистрибутива Linux.

Дистрибутив Linux может быть установлен без графического интерфейса пользователя, скажем, на серверах, и, скорее всего, тогда им не хватит xdg-open.

Вместо system вы могли бы - и должны - использовать fork + exec - если exec не удастся, то xdg-open не может быть выполнен.

Скорее всего, онлайн-компиляторына них не установлено ни одного графического интерфейса рабочего стола, поэтому отсутствует эта утилита.

...