Хотя Антти Хаапала уже полностью ответил на вопрос, я подумал, что некоторые комментарии о подходе и примерной функции, обеспечивающей безопасное использование, могут быть полезны.
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 .Используйте его как хотите, в любом коде.