На пути к пониманию доступности xdg-open
Я хочу открыть изображение, и в 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: Это для моего поста о Боге Времени, так что не беспокойтесь о безопасности.
2 ответа
Хотя Антти Хаапала уже полностью ответил на вопрос, я подумал, что некоторые комментарии о подходе и пример функции, делающей безопасное использование тривиальным, могут быть полезны.
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. Используйте его так, как вы хотите, в любом коде, который вы хотите.
xdg-open
является частью xdg-utils
, Они почти всегда устанавливаются с рабочим столом GUI любого дистрибутива Linux.
Дистрибутив Linux может быть установлен без какого-либо графического интерфейса пользователя, скажем, на серверах, и, скорее всего, тогда им не хватит xdg-open
,
Вместо system
Вы могли - и должны - использовать fork
+ exec
- если exec
терпит неудачу тогда xdg-open
не может быть выполнен
Онлайн-компиляторы, скорее всего, не имеют установленного графического интерфейса рабочего стола, поэтому этой утилиты нет.