zeniba-nsxiv/util.c
NRK 931912dcf6 make pipes non-blocking and read output on POLLHUP (#490)
this makes it so that script outputs are read when the script
actually finishes. one objection to reading on POLLHUP was that
the script might fill up the pipe and get stuck. we already mark
the read-end as non-blocking on our end so might as well make
the write-end non-blocking as well which avoids the script
getting blocked after (and if) it fills up the pipe.

ref: https://codeberg.org/nsxiv/nsxiv/pulls/334
Closes: https://codeberg.org/nsxiv/nsxiv/issues/377

Reviewed-on: https://codeberg.org/nsxiv/nsxiv/pulls/490
Reviewed-by: eylles <eylles@noreply.codeberg.org>
2024-06-05 19:40:58 +00:00

298 lines
6.3 KiB
C

/* Copyright 2011-2020 Bert Muennich
* Copyright 2021-2023 nsxiv contributors
*
* This file is a part of nsxiv.
*
* nsxiv is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* nsxiv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nsxiv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nsxiv.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <spawn.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
extern char **environ;
const char *progname = "nsxiv";
void *emalloc(size_t size)
{
void *ptr;
ptr = malloc(size);
if (ptr == NULL)
error(EXIT_FAILURE, errno, NULL);
return ptr;
}
void *ecalloc(size_t nmemb, size_t size)
{
void *ptr;
ptr = calloc(nmemb, size);
if (ptr == NULL)
error(EXIT_FAILURE, errno, NULL);
return ptr;
}
void *erealloc(void *ptr, size_t size)
{
ptr = realloc(ptr, size);
if (ptr == NULL)
error(EXIT_FAILURE, errno, NULL);
return ptr;
}
char *estrdup(const char *s)
{
size_t n = strlen(s) + 1;
return memcpy(emalloc(n), s, n);
}
void error(int eval, int err, const char *fmt, ...)
{
va_list ap;
if (eval == 0 && options->quiet)
return;
fflush(stdout);
fprintf(stderr, "%s: ", progname);
va_start(ap, fmt);
if (fmt != NULL)
vfprintf(stderr, fmt, ap);
va_end(ap);
if (err != 0)
fprintf(stderr, "%s%s", fmt != NULL ? ": " : "", strerror(err));
fputc('\n', stderr);
if (eval != 0)
exit(eval);
}
int r_opendir(r_dir_t *rdir, const char *dirname, bool recursive)
{
if (*dirname == '\0')
return -1;
if ((rdir->dir = opendir(dirname)) == NULL) {
rdir->name = NULL;
rdir->stack = NULL;
return -1;
}
rdir->stcap = 512;
rdir->stack = emalloc(rdir->stcap * sizeof(*rdir->stack));
rdir->stlen = 0;
rdir->name = (char *)dirname;
rdir->d = 0;
rdir->recursive = recursive;
return 0;
}
int r_closedir(r_dir_t *rdir)
{
int ret = 0;
if (rdir->stack != NULL) {
while (rdir->stlen > 0)
free(rdir->stack[--rdir->stlen]);
free(rdir->stack);
rdir->stack = NULL;
}
if (rdir->dir != NULL) {
if ((ret = closedir(rdir->dir)) == 0)
rdir->dir = NULL;
}
if (rdir->d != 0) {
free(rdir->name);
rdir->name = NULL;
}
return ret;
}
char *r_readdir(r_dir_t *rdir, bool skip_dotfiles)
{
size_t len;
char *filename;
struct dirent *dentry;
struct stat fstats;
while (true) {
if (rdir->dir != NULL && (dentry = readdir(rdir->dir)) != NULL) {
if (dentry->d_name[0] == '.') {
if (skip_dotfiles)
continue;
if (dentry->d_name[1] == '\0')
continue;
if (dentry->d_name[1] == '.' && dentry->d_name[2] == '\0')
continue;
}
len = strlen(rdir->name) + strlen(dentry->d_name) + 2;
filename = emalloc(len);
snprintf(filename, len, "%s%s%s", rdir->name,
rdir->name[strlen(rdir->name) - 1] == '/' ? "" : "/",
dentry->d_name);
if (stat(filename, &fstats) < 0) {
free(filename);
continue;
}
if (S_ISDIR(fstats.st_mode)) {
/* put subdirectory on the stack */
if (rdir->stlen == rdir->stcap) {
rdir->stcap *= 2;
rdir->stack = erealloc(rdir->stack,
rdir->stcap * sizeof(*rdir->stack));
}
rdir->stack[rdir->stlen++] = filename;
continue;
}
return filename;
}
if (rdir->recursive && rdir->stlen > 0) {
/* open next subdirectory */
assert(rdir->dir != NULL);
closedir(rdir->dir);
if (rdir->d != 0)
free(rdir->name);
rdir->name = rdir->stack[--rdir->stlen];
rdir->d = 1;
if ((rdir->dir = opendir(rdir->name)) == NULL)
error(0, errno, "%s", rdir->name);
continue;
}
/* no more entries */
break;
}
return NULL;
}
int r_mkdir(char *path)
{
int rc = 0;
char c, *s = path;
struct stat st;
while (*s != '\0' && rc == 0) {
if (*s == '/') {
s++;
continue;
}
for (; *s != '\0' && *s != '/'; s++)
;
c = *s;
*s = '\0';
if (mkdir(path, 0755) == -1) {
if (errno != EEXIST || stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
error(0, errno, "%s", path);
rc = -1;
}
}
*s = c;
}
return rc;
}
void construct_argv(char **argv, unsigned int len, ...)
{
unsigned int i;
va_list args;
va_start(args, len);
for (i = 0; i < len; ++i)
argv[i] = va_arg(args, char *);
va_end(args);
assert(argv[len - 1] == NULL && "argv should be NULL terminated");
}
static int mkspawn_pipe(posix_spawn_file_actions_t *fa, const char *cmd, int *pfd, int dupidx, int pipeflags)
{
int err = 0;
if (pipe(pfd) < 0) {
error(0, errno, "pipe: %s", cmd);
return -1;
}
if (pipeflags && (fcntl(pfd[0], F_SETFL, pipeflags) < 0 || fcntl(pfd[1], F_SETFL, pipeflags) < 0))
err = errno;
err = err ? err : posix_spawn_file_actions_adddup2(fa, pfd[dupidx], dupidx);
err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[0]);
err = err ? err : posix_spawn_file_actions_addclose(fa, pfd[1]);
if (err) {
error(0, err, "mkspawn_pipe: %s", cmd);
close(pfd[0]);
close(pfd[1]);
}
return err ? -1 : 0;
}
pid_t spawn(int *readfd, int *writefd, int pipeflags, char *const argv[])
{
pid_t pid = -1;
const char *cmd;
int err, pfd_read[2], pfd_write[2];
posix_spawn_file_actions_t fa;
assert(argv != NULL && argv[0] != NULL);
cmd = argv[0];
if ((err = posix_spawn_file_actions_init(&fa)) != 0) {
error(0, err, "spawn: %s", cmd);
return pid;
}
if (readfd != NULL && mkspawn_pipe(&fa, cmd, pfd_read, 1, pipeflags) < 0)
goto err_destroy_fa;
if (writefd != NULL && mkspawn_pipe(&fa, cmd, pfd_write, 0, pipeflags) < 0)
goto err_close_readfd;
if ((err = posix_spawnp(&pid, cmd, &fa, NULL, argv, environ)) != 0) {
error(0, err, "spawn: %s", cmd);
} else {
if (readfd != NULL)
*readfd = pfd_read[0];
if (writefd != NULL)
*writefd = pfd_write[1];
}
if (writefd != NULL) {
close(pfd_write[0]);
if (pid < 0)
close(pfd_write[1]);
}
err_close_readfd:
if (readfd != NULL) {
if (pid < 0)
close(pfd_read[0]);
close(pfd_read[1]);
}
err_destroy_fa:
posix_spawn_file_actions_destroy(&fa);
return pid;
}