旨在了解xdg-open的可用性

时间:2018-11-29 09:47:38

标签: c linux availability xdgutils

我要打开图像,然后在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(checked)中。我不希望在线编译器为我打开浏览器,但是我确实希望代码能够正常运行。啊,忘了Mac(个人笔记本电脑,限制了我的机器)。

由于我没有其他要检查的Linux机器,我的问题是:我可以期望这段代码在大多数主要的Linux发行版中都能工作吗?

也许它在在线编译器上失败的事实具有误导性。


PS:这是我在God of Time上发布的帖子,因此不必担心安全性。

2 个答案:

答案 0 :(得分:3)

尽管Antti Haapala已经完全answered这个问题,但我认为有关此方法的一些评论以及使安全使用变得微不足道的示例函数可能会有用。


xdg-open是freedesktop.org桌面集成实用程序的一部分,是Portland project的一部分。可以期望它们在运行桌面环境participating in 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二进制文件中使用的话),并通过成功/ failure使用子进程退出状态。

setresuid()setresgid()调用仅在已保存用户ID和组ID的OS上可用。在其他情况下,请改用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获得许可。以任何所需的方式,以任何所需的代码使用它。

答案 1 :(得分:1)

xdg-openxdg-utils的一部分。它们几乎总是安装在任何Linux发行版的GUI桌面上。

可以在服务器上安装没有任何图形用户界面的Linux发行版,并且很可能会缺少xdg-open

您可以-并且应该-使用system + fork来代替exec-如果exec失败则无法执行xdg-open

在线编译器很可能没有安装任何桌面GUI,因此缺少该实用程序。