【问题标题】:Towards understanding availability of xdg-open了解 xdg-open 的可用性
【发布时间】:2018-11-29 09:47:38
【问题描述】:

我想打开一张图片,在 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 上的帖子,所以不用担心安全问题。

【问题讨论】:

    标签: c linux availability xdgutils


    【解决方案1】:

    虽然 Antti Haapala 已经完全解决了answered 的问题,但我认为一些关于该方法的 cmets 以及一个使安全使用变得微不足道的示例函数可能会有用。


    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 二进制文件中使用),并传递成功/失败使用子进程退出状态。

    setresuid()setresgid() 调用仅适用于已保存用户和组 ID 的操作系统。在其他情况下,请改用 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 下获得许可。以任何你想要的方式,在你想要的任何代码中使用它。

    【讨论】:

    • Ah Nomimal,一如既往,在低级 C 方面你知道很多!什么是“POSIXy”?我在网上搜索,发现了几个意思..
    • @gsamaras:“POSIXy”是指那些实现大部分或全部 IEEE 标准 1003.1™(也称为 POSIX.1)的系统。至少包括 Linux 和 BSD;我不确定其他人,因为我不是最新的。许多 Unix 非常 POSIXy,尽管您可能需要安装额外的库和实用程序。
    • @gsamaras:我很晚才注意到我没有足够强调它绝对只针对桌面环境,在非 DE 中不可用,并在第二部分添加了一个注释。我希望我没有误导任何人......
    【解决方案2】:

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

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

    您可以并且应该使用fork + exec 而不是system - 如果exec 失败,则xdg-open 无法执行。

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

    【讨论】:

    • 知道为什么在线编译器没有它,尽管他们的操作系统是 Linux 吗?感谢您的回答和提示,这是正确的。
    • @gsamaras 因为他们没有安装任何桌面 GUI:P 为什么要安装。
    • 在没有xdg-open 的情况下可以尝试使用其他程序,但默认情况下很少安装这些程序。
    • 我对此不感兴趣,因为我只想在我的 Chronos Post 中有代码,这些代码可以很容易地准备好复制-粘贴-编译-执行!你也可以自己试试,如果你喜欢!无论如何,很高兴知道,因为未来的读者可能会感兴趣!
    • @gsamaras 您希望xdg-open 在在线服务上做什么?在您的网络浏览器中打开一个窗口?
    猜你喜欢
    • 1970-01-01
    • 2014-09-01
    • 1970-01-01
    • 2018-04-16
    • 2013-09-24
    • 2020-10-12
    • 2022-07-08
    • 2023-03-17
    • 2014-03-09
    相关资源
    最近更新 更多