[SOLVED] Image viewer that reads stdin and will not write cache files to disk
Linux - SoftwareThis forum is for Software issues.
Having a problem installing a new program? Want to know which application is best for the job? Post your question in this forum.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
Image viewer that reads stdin and will not write cache files to disk
I guess I should explain a bit of what I'm trying to do.
I have tried truecrypt and I don't like it. I don't like the way it operates, and I don't like its limitations.
I'm trying to make my own form of encryption + plausible deniability. To do this and make it efficient, I need programs that will read from stdin (a pipe that has been unencrypted) and display content WITHOUT writing this data do disk (for obvious reasons). I've found programs that can do this for other media types: movies, text, etc. However, I have NOT yet found a suitable image viewer. I need an image viewer that won't cache to disk.
I've tried 'xv' and 'display', but my studies show that they write cache files to /tmp. I've tried to disable this, but it doesn't see possible.
Just for fun, I created a helper program that takes a command as an argument, and a stream of concatenated image files from standard input. It reads the next image, then fork()s and exec()s the desired command, supplying the image data to it via its standard input. After the command exits, the helper repeats the same for the next image.
This program is very crude. I wrote the format parsers from the specs, not by trial-and-error, so the logic should be on a sound basis, barring any typos and thinkos, of course. Consider it only a working prototype. I posted it in case somebody would like to (rewrite and) develop it further. I consider this code CC0 and/or public domain, whichever is applicable in your location.
The GIF and PNG parsers should be okay. GIF and PNG files have very straightforward structures, and are easy to parse and walk through. The JPEG parser I'm not absolutely sure of, although it does seem to work on all images I can throw at it. It does also tell you if it skips data it does not feed to the viewer.
It is my understanding that according to ITU-T.81, in the JPEG scan data, all 0xFFs should be stuffed (followed by a zero or one), or they indicate an RST marker (followed one byte between 0xD0 and 0xD7 inclusive), or they indicate the end of the scan data (being a normal JPEG marker). The parser exploits this, only looking at the 0xFF and following bytes in scan data. For all the non-scan data, it of course relies on the marker structure.
Here it is, per-image.c:
Code:
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#define EXIT_READ 99
#define EXIT_MEMORY 98
#define EXIT_PIPE 97
#define EXIT_FORK 96
#define EXIT_EXEC 95
#define EXIT_SIGNAL 94
#define EXIT_INPUT 93
/* Input buffer.
*/
static unsigned char *input_data = NULL; /* Input buffer */
static size_t input_have = 0; /* Number of bytes available */
static size_t input_size = 0; /* Number of bytes allocated */
/* Command to execute.
*/
static const char *command_file = NULL;
static const char *const *command_args = NULL;
/* Helper function to avoid using stdio: Write to standard error.
*/
static int wrerr(const void *const ptr, const size_t len)
{
const int descriptor = STDERR_FILENO;
const char *const q = (const char *)ptr + len;
const char *p = (const char *)ptr;
ssize_t n;
while (p < q) {
n = write(descriptor, p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
return EIO;
else
if (errno != EINTR)
return errno;
}
return 0;
}
static inline int wrerrs(const char *const string)
{
return (string && *string) ? wrerr(string, strlen(string)) : 0;
}
/* Output a fatal error message, and exit.
*/
static void fatal(const int exitstatus, const int num_strings, ...)
{
const char *s;
int i;
va_list a;
va_start(a, num_strings);
for (i = 0; i < num_strings; i++) {
s = va_arg(a, const char *);
if (s && *s)
if (wrerr(s, strlen(s)))
break;
}
va_end(a);
exit(exitstatus);
}
/* Helper to encode one signed integer to fatal error messages.
*/
static const char *errint(const int value)
{
static unsigned char buffer[4 + 4*sizeof(int)];
unsigned char *q = buffer + sizeof(buffer);
unsigned int u = (value < 0) ? (unsigned int)(-value) : value;
*(--q) = '\0';
*(--q) = '0' + (u % 10U); u /= 10U;
while (u) {
*(--q) = '0' + (u % 10U);
u /= 10U;
}
if (value < 0)
*(--q) = '-';
return (const char *)q;
}
/* Close a file descriptor without affecting errno.
*/
static inline int closefd(const int fd)
{
int saved_errno, result;
saved_errno = errno;
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
if (result == -1)
result = errno;
else
result = 0;
errno = saved_errno;
return result;
}
/* Move a file descriptor without affecting errno.
*/
static inline int dupfd(const int oldfd, const int newfd)
{
int saved_errno, result;
if (oldfd == newfd)
return 0;
saved_errno = errno;
do {
result = dup2(oldfd, newfd);
} while (result == -1 && errno == EINTR);
if (result == -1)
result = errno;
else
result = 0;
errno = saved_errno;
return result;
}
/* Fork and execute the command, supplying size bytes from the input buffer.
* Return values:
* 0: Success (or child detached)
* <0: Killed by signal -(return value)
* >0: Exit status from child process.
*/
static int run(const size_t size)
{
pid_t child, pid;
int pipefd[2];
int result;
do {
result = pipe(pipefd);
} while (result == -1 && errno == EINTR);
if (result == -1)
fatal(EXIT_PIPE, 3, "Cannot create a pipe: ", strerror(errno), ".\n");
child = fork();
if (child == (pid_t)-1)
fatal(EXIT_FORK, 3, "Cannot fork: ", strerror(errno), ".\n");
if (!child) {
/* Child process. */
closefd(STDIN_FILENO);
if (dupfd(pipefd[0], STDIN_FILENO))
exit(EXIT_PIPE);
if (closefd(pipefd[0]) ||
closefd(pipefd[1]))
exit(EXIT_PIPE);
execvp(command_file, (char *const *)command_args);
exit(EXIT_EXEC);
}
/* Parent process. */
closefd(pipefd[0]);
/* Write the data to the write end of the pipe. */
{ const char *p = (char *)input_data;
const char *const q = (char *)input_data + size;
ssize_t n;
while (p < q) {
n = write(pipefd[1], p, (size_t)(q - p));
if (n > (ssize_t)0)
p += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
break;
}
closefd(pipefd[1]);
}
/* Wait for the child process to exit. */
do {
pid = waitpid(child, &result, 0);
} while (pid == (pid_t)-1 && errno == EINTR);
/* If the child detached, assume success. */
if (pid == (pid_t)-1)
return 0;
if (WIFEXITED(result))
return WEXITSTATUS(result) & 127;
if (WIFSIGNALED(result))
return -(WTERMSIG(result) & 127);
return 0;
}
/* Function to increase an allocation block.
*/
static inline size_t pad(const size_t n)
{
return (n | (size_t)8191) + (size_t)8129;
}
/* Read more input, until at least limit bytes buffered.
*/
static void need(const size_t limit)
{
const int descriptor = STDIN_FILENO;
ssize_t n;
while (input_have < limit) {
if (input_have >= input_size) {
const size_t size = pad(input_have);
unsigned char *data;
if (size < input_size)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
data = realloc(input_data, size);
if (!data)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
input_data = data;
input_size = size;
}
do {
n = read(descriptor, input_data + input_have, input_size - input_have);
} while (n == (ssize_t)-1 && errno == EINTR);
if (n > (ssize_t)0)
input_have += n;
else
if (n != (ssize_t)-1)
break;
else
if (errno != EINTR)
fatal(EXIT_READ, 3, "Error reading standard input: ", strerror(errno), ".\n");
}
if (input_size < limit) {
const size_t size = pad(limit);
unsigned char *data;
if (size < input_size)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
data = realloc(input_data, size);
if (!data)
fatal(EXIT_MEMORY, 1, "Out of memory.\n");
input_data = data;
input_size = size;
}
if (input_have < limit)
memset(input_data + input_have, 0, input_size - input_have);
return;
}
/* Peek a byte in the input buffer.
* After the call, all bytes prior to offset are also available.
*/
static inline unsigned int peekb(const size_t offset)
{
if (offset >= input_have)
need(offset + 1);
return (unsigned int)(input_data[offset]);
}
/* Peek a 16-bit word in the input buffer. Offset is in bytes.
* After the call, all bytes prior to offset are also available.
*/
static inline unsigned int peekw(const size_t offset)
{
if (offset + 1 >= input_have)
need(offset + 2);
return (unsigned int)(input_data[offset + 0]) * 256U
+ (unsigned int)(input_data[offset + 1]);
}
/* Peek a 32-bit word in the input buffer. Offset is in bytes.
* After the call, all bytes prior to offset are also available.
*/
static inline unsigned long peekl(const size_t offset)
{
if (offset + 3 >= input_have)
need(offset + 4);
return (unsigned long)(input_data[offset + 0]) * 16777216UL
+ (unsigned long)(input_data[offset + 1]) * 65536UL
+ (unsigned long)(input_data[offset + 2]) * 256UL
+ (unsigned long)(input_data[offset + 3]);
}
/* Peek a number of bytes in the input buffer,
* returning zero if the data matches.
*/
static inline int peek(const size_t offset, const void *const data, const size_t bytes)
{
if (offset + bytes > input_have)
need(offset + bytes);
return memcmp(data, input_data + offset, bytes);
}
/* Discard bytes first bytes of input.
* Returns nonzero if there is further input available.
*/
static size_t discard(const size_t bytes)
{
if (bytes >= input_have)
need(bytes + 1);
if (bytes < input_have) {
memmove(input_data, input_data + bytes, input_have - bytes);
input_have = input_have - bytes;
} else
input_have = 0;
return input_have;
}
/*
* Image data parsers.
*
* Each of the is_format() functions will return 0 if the input buffer
* does not begin with an image of said format, and a nonzero length
* otherwise.
*/
/* GIF image parser.
*/
static size_t is_gif(void)
{
size_t length = 0, n;
int flags;
/* Verify GIF header */
if (peek(0, "GIF87a", 6) && peek(0, "GIF89a", 6))
return 0;
/* Skip Logical Screen Descriptor */
flags = peekb(10);
if (flags & 128)
length = 13 + (6 << (flags & 7));
else
length = 13;
/* Parse until end of data. */
while (1) {
switch (peekb(length)) {
case 0x3B: /* Trailer */
return length + 1;
case 0x21: /* Extension */
n = 1;
do {
length += n + 1;
n = peekb(length);
} while (n);
length++;
break;
case 0x2C: /* Image descriptor */
flags = peekb(length + 9);
if (flags & 128)
length += 10 + (6 << (flags & 7));
else
length += 10;
n = 0;
do {
length += n + 1;
n = peekb(length);
} while (n);
length++;
break;
default: /* Bad data. */
return 0;
}
}
}
/* PNG Image parser.
*/
static size_t is_png(void)
{
const unsigned char *p;
size_t i = 8;
size_t n;
if (peek(0, "\x89PNG\x0D\x0A\x1A\x0A", 8))
return 0;
while (1) {
n = peekl(i);
if (n > (size_t)0x7FFFFFFFUL || n < (size_t)0)
return 0;
peekb(i + n + (size_t)12);
p = (const unsigned char *)(input_data + i + 4);
if (!((p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z')))
return 0;
if (!((p[1] >= 'A' && p[1] <= 'Z') || (p[1] >= 'a' && p[1] <= 'z')))
return 0;
if (!((p[2] >= 'A' && p[2] <= 'Z') || (p[2] >= 'a' && p[2] <= 'z')))
return 0;
if (!((p[3] >= 'A' && p[3] <= 'Z') || (p[3] >= 'a' && p[3] <= 'z')))
return 0;
i += n + (size_t)12;
if (p[0] == 'I' && p[1] == 'E' && p[2] == 'N' && p[3] == 'D')
return i;
}
return 0;
}
/* JPEG image parser.
*/
static size_t is_jpeg(void)
{
size_t n, index = 0;
int b;
/* SOI (0xFF+ 0xD8) must be first in stream. */
b = peekb(index);
if (b != 0xFF)
return 0;
while (b == 0xFF)
b = peekb(++index);
if (b != 0xD8)
return 0;
index++;
while (1) {
/* Marker. */
b = peekb(index);
if (b != 0xFF)
return 0;
while (b == 0xFF)
b = peekb(++index);
/* Invalid? */
if (b < 0xC0)
return 0;
if (b >= 0xD0 && b <= 0xD7) {
/* RSTn? */
index++;
continue;
} else
if (b == 0xD8) {
/* Start of Image? */
index++;
continue;
} else
if (b == 0xD9) {
/* End of Image? */
return index + 1;
}
/* Marker has length indicator. */
n = peekw(++index);
if (n < (size_t)2)
return 0;
index += n;
/* Not Start of Scan? */
if (b != 0xDA)
continue;
/* Parse scan. */
do {
b = peekb(index);
while (b != 0xFF)
if (++index <= input_size)
b = peekb(index);
else
return 0;
while (b == 0xFF)
b = peekb(++index);
} while (b <= 0x01 || (b >= 0xD0 && b <= 0xD7));
index--;
continue;
}
}
/* Return the number of bytes that can be discarded. Conside
* discard(is_none());
* a replacement for
* while (!is_gif() && !is_png() && !is_jpg() && input_have)
* discard(1);
*/
size_t is_none(void)
{
size_t n = 0;
int b;
do {
b = peekb(n);
if (b == 'G')
if (peekb(n+1) == 'I' &&
peekb(n+2) == 'F' &&
peekb(n+3) == '8' &&
peekb(n+5) == 'a')
return n;
if (b == 0x89)
if (peekb(n+1) == 'P' &&
peekb(n+2) == 'N' &&
peekb(n+3) == 'G')
return n;
if (b == 0xFF)
if (peekb(n+1) == 0xD8)
return n;
} while (n++ <= input_have);
return input_have;
}
/*
* Main
*/
int main(int argc, char *argv[])
{
size_t len;
int retval;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
fatal(0, 5,
"\n"
"Usage: ", argv[0], " [ -h | --help ]\n"
" ", argv[0], " command [ args... ]\n"
"\n"
"The command will be run once for each image in standard input.\n"
"The command will receive the image data from standard input.\n"
"\n");
command_file = (const char *)argv[1];
command_args = (const char *const *)&(argv[1]);
/* Paranoid check: Verify the arg list ends with a NULL. */
if (argv[argc])
argv[argc] = NULL;
while (1)
if ((len = is_png()) ||
(len = is_gif()) ||
(len = is_jpeg()) ) {
/* Run the command. */
retval = run(len);
if (retval > 0)
fatal(retval, 5, "'", command_file, "': Nonzero (", errint(retval), ") exit status.\n");
if (retval < 0)
fatal(EXIT_SIGNAL, 5, "'", command_file, "': Died by signal (", errint(-retval), ").\n");
/* Discard image data. */
if (!discard(len))
return 0;
} else {
/* No data? */
if (!input_have)
return 0;
/* Skip unrecognized input. */
len = 0;
while (input_have && !is_png() && !is_gif() && !is_jpeg()) {
size_t const n = is_none();
discard(n);
len += n;
}
if (!input_have)
fatal(0, 3, "Discarded ", errint(len), " trailing garbage bytes.\n");
wrerrs("Skipped ");
wrerrs(errint(len));
wrerrs(" bytes of unrecognized input.\n");
continue;
}
}
I recall that xloadimage does this too, although I have no way to try it now. Its development has ceased, however you can find sources and binpack on debian repositories.
After a quick check, I found no tempfiles around with feh. Even if this is the case, you would end up with the risk of huge memory allocations for heavy images.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.