代码之家  ›  专栏  ›  技术社区  ›  gsamaras a Data Head

了解xdg open的可用性

  •  1
  • gsamaras a Data Head  · 技术社区  · 6 年前

    我想打开一个图像,在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中,它起作用了。但是,在魔盒中运行时( Live Demo 或者在任何其他联机编译器中,我很可能会得到一个错误:

    sh:1:xdg打开:未找到

    尽管这些在线编译器似乎生活在Linux中( checked )我不希望在线编译器为我打开浏览器,但我确实希望代码运行时不会出错。啊,忘了 Mac (个人笔记本电脑,限制我的机器)。

    因为我没有其他的Linux机器要检查,我的问题是:我能期望这个代码在大多数主要的Linux发行版上都能工作吗?

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


    附:这是给我的帖子 God of Time 所以不用担心安全问题。

    2 回复  |  直到 6 年前
        1
  •  3
  •   Nominal Animal    6 年前

    虽然安蒂哈帕拉已经完全 answered 这个问题,我认为一些关于该方法的评论,以及一个使安全使用变得微不足道的示例函数,可能是有用的。


    xdg-open 是freedesktop.org的桌面集成实用程序的一部分,它是 Portland project . 人们可以期望它们在运行桌面环境的任何计算机上都可用。 participating in freedesktop.org . 这包括gnome、kde和xfce。

    简单地说, 这是推荐的方法 当桌面环境正在使用时,在用户喜欢的任何应用程序中打开资源(无论是文件还是URL)。

    如果没有正在使用的桌面环境,那么就没有理由期望 XDG开放 可供选择。


    对于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开放 ,没有泄漏文件描述符;只传递文件名或URL。

    在非Linux posixy系统上,您可以用更合适的东西替换它们。关于BSDS的使用 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开放 得到执行,但该函数尝试确保不会向该进程泄漏敏感信息或特权。

    (环境变量可以被过滤,但首先不应该包含敏感信息,而且我们不知道桌面环境使用的是哪一个。所以最好不要和他们混在一起,把用户的惊喜降到最低。)

    作为最低限度的测试 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 . 用你想用的任何方式,用你想用的任何代码。

        2
  •  1
  •   Antti Haapala -- Слава Україні    6 年前

    这个 xdg-open 是其中的一部分 xdg-utils . 它们几乎总是与任何Linux发行版的GUI桌面一起安装。

    Linux发行版可以安装在服务器上,而不需要任何图形用户界面,很可能它们会缺少 XDG开放 .

    而不是 system 你可以也应该使用 fork + exec -如果 执行 失败了 XDG开放 无法执行。

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