/**
* scrotty — Screenshot program for Linux's TTY
*
* Copyright © 2014, 2015 Mattias Andrée (maandree@member.fsf.org)
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#include "pattern.h"
/**
* Duplicate all '%':s in a buffer until the first occurrence of a zero byte.
*
* @param buf The buffer.
* @param n The size of the buffer.
* @return -1 if the buffer is too small, otherwise.
* the new position of the first zero byte.
*/
static ssize_t
duplicate_percents (char *restrict buf, size_t n)
{
size_t p = 0, pi, pc = 0, i;
/* Count number of '%':s. */
for (i = 0; buf[i]; i++)
if (buf[i] == '%')
pc++;
/* Check whether the string will overflow. */
if (i + pc > n)
return -1;
/* Duplicate all '%':s. */
for (pi = 0; pi < pc; pi++)
{
p = (size_t)(strchr (buf + p, '%') - buf);
memmove (buf + p + 1, buf + p, (i - (p - pi)) * sizeof (char));
p += 2;
}
return (ssize_t)(i + pi);
}
/**
* Parse and evaluate a --exec argument or filename pattern.
*
* If `path != NULL` than all non-escaped spaces in
* `pattern` will be stored as 255-bytes in `buf`.
*
* @param buf The output buffer.
* @param n The size of `buf`.
* @param pattern The pattern to evaluate.
* @param fbno The index of the framebuffer.
* @param width The width of the image/framebuffer.
* @param height The height of the image/framebuffer.
* @param path The filename of the saved image, `NULL`
* during the evaluation of the filename pattern.
* @return Zero on success, -1 on error.
*/
static int
try_evaluate (char *restrict buf, size_t n, const char *restrict pattern,
int fbno, long width, long height, const char *restrict path)
{
#define P(format, value) r = snprintf (buf + i, n - i, format "%zn", value, &j)
size_t i = 0;
ssize_t j = 0;
int percent = 0, backslash = 0, dollar = 0, r = 0;
char c;
char *fmt = NULL;
time_t t;
struct tm *tm;
int saved_errno;
/* Expand '$' and '\'. */
while ((i < n) && ((c = *pattern++)))
if (dollar)
{
dollar = 0;
if (path == NULL)
if ((c == 'f') || (c == 'n'))
continue;
if (c == 'i') P ("%i", fbno);
else if (c == 'f') P ("%s", path);
else if (c == 'n') P ("%s", strrchr (path, '/') ? (strrchr (path, '/') + 1) : path);
else if (c == 'p') P ("%ju", (uintmax_t)width * (uintmax_t)height);
else if (c == 'w') P ("%li", width);
else if (c == 'h') P ("%li", height);
else if (c == '$') r = 0, j = 1, buf[i] = '$';
else if ((r < 0) || (j <= 0))
return -1;
else if ((c == 'f') || (c == 'n'))
if (j = duplicate_percents (buf + i, n - i), j < 0)
goto enametoolong;
i += (size_t)j;
}
else if (backslash) buf[i++] = (c == 'n' ? '\n' : c), backslash = 0;
else if (percent) buf[i++] = c, percent = 0;
else if (c == '%') buf[i++] = c, percent = 1;
else if (c == '\\') backslash = 1;
else if (c == '$') dollar = 1;
else if (c == ' ') buf[i++] = path == NULL ? ' ' : (char)255; /* 255 is not valid in UTF-8. */
else buf[i++] = c;
if (i >= n)
goto enametoolong;
buf[i] = '\0';
/* Check whether there are any '%' to expand. */
if (strchr (buf, '%') == NULL)
return 0;
/* Copy the buffer so we can reuse the buffer and use its old content for the format. */
fmt = strdup (buf);
if (fmt == NULL)
goto fail;
/* Expand '%'. */
t = time (NULL);
tm = localtime (&t);
if (tm == NULL)
goto fail;
#ifdef __GNUC__
# pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
if (strftime (buf, n, fmt, tm) == 0)
goto enametoolong; /* No errors are defined for `strftime`. What else can we do? */
free (fmt);
return 0;
enametoolong:
errno = ENAMETOOLONG;
fail:
saved_errno = errno;
free (fmt);
errno = saved_errno;
return -1;
}
/**
* Parse and evaluate a --exec argument or filename pattern.
*
* If `path != NULL` than all non-escaped spaces in
* `pattern` will be stored as 255-bytes in `buf`.
*
* @param pattern The pattern to evaluate.
* @param fbno The index of the framebuffer.
* @param width The width of the image/framebuffer.
* @param height The height of the image/framebuffer.
* @param path The filename of the saved image, `NULL`
* during the evaluation of the filename pattern.
* @return The constructed string, `NULL` on error.
*/
char*
evaluate (const char *restrict pattern, int fbno, long width,
long height, const char *restrict path)
{
char *buffer = NULL;
size_t size = 32;
void *new;
int saved_errno;
retry:
new = realloc (buffer, size * sizeof(char));
if (new == NULL)
goto fail;
buffer = new;
if (try_evaluate (buffer, size, pattern, fbno, width, height, path) < 0)
{
size <<= 1;
if (errno == ENAMETOOLONG)
goto retry;
goto fail;
}
return buffer;
fail:
saved_errno = errno;
free(buffer);
errno = saved_errno;
return NULL;
}