alsa-utils/axfer/container.c
Takashi Sakamoto 6b0f1b20ae axfer: maintain lifetime of file descriptor outside of container module
This commit closes file descriptor outside of container module so
that maintenance of lifetime for the descriptor is delegated to container
user.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2021-03-11 09:28:12 +01:00

459 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0
//
// container.c - an interface of parser/builder for formatted files.
//
// Copyright (c) 2018 Takashi Sakamoto <o-takashi@sakamocchi.jp>
//
// Licensed under the terms of the GNU General Public License, version 2.
#include "container.h"
#include "misc.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <inttypes.h>
static const char *const cntr_type_labels[] = {
[CONTAINER_TYPE_PARSER] = "parser",
[CONTAINER_TYPE_BUILDER] = "builder",
};
static const char *const cntr_format_labels[] = {
[CONTAINER_FORMAT_RIFF_WAVE] = "riff/wave",
[CONTAINER_FORMAT_AU] = "au",
[CONTAINER_FORMAT_VOC] = "voc",
[CONTAINER_FORMAT_RAW] = "raw",
};
static const char *const suffixes[] = {
[CONTAINER_FORMAT_RIFF_WAVE] = ".wav",
[CONTAINER_FORMAT_AU] = ".au",
[CONTAINER_FORMAT_VOC] = ".voc",
[CONTAINER_FORMAT_RAW] = "",
};
const char *const container_suffix_from_format(enum container_format format)
{
return suffixes[format];
}
int container_recursive_read(struct container_context *cntr, void *buf,
unsigned int byte_count)
{
char *dst = buf;
ssize_t result;
size_t consumed = 0;
while (consumed < byte_count && !cntr->interrupted) {
result = read(cntr->fd, dst + consumed, byte_count - consumed);
if (result < 0) {
// This descriptor was configured with non-blocking
// mode. EINTR is not cought when get any interrupts.
if (cntr->interrupted)
return -EINTR;
if (errno == EAGAIN)
continue;
return -errno;
}
// Reach EOF.
if (result == 0) {
cntr->eof = true;
return 0;
}
consumed += result;
}
return 0;
}
int container_recursive_write(struct container_context *cntr, void *buf,
unsigned int byte_count)
{
char *src = buf;
ssize_t result;
size_t consumed = 0;
while (consumed < byte_count && !cntr->interrupted) {
result = write(cntr->fd, src + consumed, byte_count - consumed);
if (result < 0) {
// This descriptor was configured with non-blocking
// mode. EINTR is not cought when get any interrupts.
if (cntr->interrupted)
return -EINTR;
if (errno == EAGAIN)
continue;
return -errno;
}
consumed += result;
}
return 0;
}
enum container_format container_format_from_path(const char *path)
{
const char *suffix;
const char *pos;
int i;
for (i = 0; i < ARRAY_SIZE(suffixes); ++i) {
suffix = suffixes[i];
// Check last part of the string.
pos = path + strlen(path) - strlen(suffix);
if (!strcmp(pos, suffix))
return i;
}
// Unsupported.
return CONTAINER_FORMAT_RAW;
}
int container_seek_offset(struct container_context *cntr, off64_t offset)
{
off64_t pos;
pos = lseek64(cntr->fd, offset, SEEK_SET);
if (pos < 0)
return -errno;
if (pos != offset)
return -EIO;
return 0;
}
// To avoid blocking execution at system call iteration after receiving UNIX
// signals.
static int set_nonblock_flag(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
if (flags < 0)
return -errno;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
return -errno;
return 0;
}
int container_parser_init(struct container_context *cntr, int fd,
unsigned int verbose)
{
const struct container_parser *parsers[] = {
[CONTAINER_FORMAT_RIFF_WAVE] = &container_parser_riff_wave,
[CONTAINER_FORMAT_AU] = &container_parser_au,
[CONTAINER_FORMAT_VOC] = &container_parser_voc,
};
const struct container_parser *parser;
unsigned int size;
int i;
int err;
assert(cntr);
assert(fd >= 0);
// Detect forgotten to destruct.
assert(cntr->fd == 0);
assert(cntr->private_data == NULL);
memset(cntr, 0, sizeof(*cntr));
cntr->fd = fd;
cntr->stdio = (cntr->fd == fileno(stdin));
if (cntr->stdio) {
if (isatty(cntr->fd)) {
fprintf(stderr,
"A terminal is referred for standard input. "
"Output from any process or shell redirection "
"should be referred instead.\n");
return -EIO;
}
}
err = set_nonblock_flag(cntr->fd);
if (err < 0)
return err;
// 4 bytes are enough to detect supported containers.
err = container_recursive_read(cntr, cntr->magic, sizeof(cntr->magic));
if (err < 0)
return err;
for (i = 0; i < ARRAY_SIZE(parsers); ++i) {
parser = parsers[i];
size = strlen(parser->magic);
if (size > 4)
size = 4;
if (!strncmp(cntr->magic, parser->magic, size))
break;
}
// Don't forget that the first 4 bytes were already read for magic
// bytes.
cntr->magic_handled = false;
// Unless detected, use raw container.
if (i == ARRAY_SIZE(parsers))
parser = &container_parser_raw;
// Allocate private data for the parser.
if (parser->private_size > 0) {
cntr->private_data = malloc(parser->private_size);
if (cntr->private_data == NULL)
return -ENOMEM;
memset(cntr->private_data, 0, parser->private_size);
}
cntr->type = CONTAINER_TYPE_PARSER;
cntr->process_bytes = container_recursive_read;
cntr->format = parser->format;
cntr->ops = &parser->ops;
cntr->max_size = parser->max_size;
cntr->verbose = verbose;
return 0;
}
int container_builder_init(struct container_context *cntr, int fd,
enum container_format format, unsigned int verbose)
{
const struct container_builder *builders[] = {
[CONTAINER_FORMAT_RIFF_WAVE] = &container_builder_riff_wave,
[CONTAINER_FORMAT_AU] = &container_builder_au,
[CONTAINER_FORMAT_VOC] = &container_builder_voc,
[CONTAINER_FORMAT_RAW] = &container_builder_raw,
};
const struct container_builder *builder;
int err;
assert(cntr);
assert(fd >= 0);
// Detect forgotten to destruct.
assert(cntr->fd == 0);
assert(cntr->private_data == NULL);
memset(cntr, 0, sizeof(*cntr));
cntr->fd = fd;
cntr->stdio = (cntr->fd == fileno(stdout));
if (cntr->stdio) {
if (isatty(cntr->fd)) {
fprintf(stderr,
"A terminal is referred for standard output. "
"Input to any process or shell redirection "
"should be referred instead.\n");
return -EIO;
}
}
err = set_nonblock_flag(cntr->fd);
if (err < 0)
return err;
builder = builders[format];
// Allocate private data for the builder.
if (builder->private_size > 0) {
cntr->private_data = malloc(builder->private_size);
if (cntr->private_data == NULL)
return -ENOMEM;
memset(cntr->private_data, 0, builder->private_size);
}
cntr->type = CONTAINER_TYPE_BUILDER;
cntr->process_bytes = container_recursive_write;
cntr->format = builder->format;
cntr->ops = &builder->ops;
cntr->max_size = builder->max_size;
cntr->verbose = verbose;
return 0;
}
int container_context_pre_process(struct container_context *cntr,
snd_pcm_format_t *format,
unsigned int *samples_per_frame,
unsigned int *frames_per_second,
uint64_t *frame_count)
{
uint64_t byte_count = 0;
unsigned int bytes_per_frame;
int err;
assert(cntr);
assert(format);
assert(samples_per_frame);
assert(frames_per_second);
assert(frame_count);
if (cntr->type == CONTAINER_TYPE_BUILDER)
byte_count = cntr->max_size;
if (cntr->ops->pre_process) {
err = cntr->ops->pre_process(cntr, format, samples_per_frame,
frames_per_second, &byte_count);
if (err < 0)
return err;
if (cntr->eof)
return 0;
}
if (cntr->format == CONTAINER_FORMAT_RAW) {
if (*format == SND_PCM_FORMAT_UNKNOWN ||
*samples_per_frame == 0 || *frames_per_second == 0) {
fprintf(stderr,
"Any file format is not detected. Need to "
"indicate all of sample format, channels and "
"rate explicitly.\n");
return -EINVAL;
}
}
assert(*format >= SND_PCM_FORMAT_S8);
assert(*format <= SND_PCM_FORMAT_LAST);
assert(*samples_per_frame > 0);
assert(*frames_per_second > 0);
assert(byte_count > 0);
cntr->bytes_per_sample = snd_pcm_format_physical_width(*format) / 8;
cntr->samples_per_frame = *samples_per_frame;
cntr->frames_per_second = *frames_per_second;
bytes_per_frame = cntr->bytes_per_sample * *samples_per_frame;
*frame_count = byte_count / bytes_per_frame;
cntr->max_size -= cntr->max_size / bytes_per_frame;
if (cntr->verbose > 0) {
fprintf(stderr, "Container: %s\n",
cntr_type_labels[cntr->type]);
fprintf(stderr, " format: %s\n",
cntr_format_labels[cntr->format]);
fprintf(stderr, " sample format: %s\n",
snd_pcm_format_name(*format));
fprintf(stderr, " bytes/sample: %u\n",
cntr->bytes_per_sample);
fprintf(stderr, " samples/frame: %u\n",
cntr->samples_per_frame);
fprintf(stderr, " frames/second: %u\n",
cntr->frames_per_second);
if (cntr->type == CONTAINER_TYPE_PARSER) {
fprintf(stderr, " frames: %" PRIu64 "\n",
*frame_count);
} else {
fprintf(stderr, " max frames: %" PRIu64 "\n",
*frame_count);
}
}
return 0;
}
int container_context_process_frames(struct container_context *cntr,
void *frame_buffer,
unsigned int *frame_count)
{
char *buf = frame_buffer;
unsigned int bytes_per_frame;
unsigned int byte_count;
unsigned int target_byte_count;
int err;
assert(cntr);
assert(!cntr->eof);
assert(frame_buffer);
assert(frame_count);
bytes_per_frame = cntr->bytes_per_sample * cntr->samples_per_frame;
target_byte_count = *frame_count * bytes_per_frame;
// A parser of cotainers already read first 4 bytes to detect format
// of container, however they includes PCM frames when any format was
// undetected. Surely to write out them.
byte_count = target_byte_count;
if (cntr->format == CONTAINER_FORMAT_RAW &&
cntr->type == CONTAINER_TYPE_PARSER && !cntr->magic_handled) {
memcpy(buf, cntr->magic, sizeof(cntr->magic));
buf += sizeof(cntr->magic);
byte_count -= sizeof(cntr->magic);
cntr->magic_handled = true;
}
// Each container has limitation for its volume for sample data.
if (cntr->handled_byte_count > cntr->max_size - byte_count)
byte_count = cntr->max_size - cntr->handled_byte_count;
// All of supported containers include interleaved PCM frames.
// TODO: process frames for truncate case.
err = cntr->process_bytes(cntr, buf, byte_count);
if (err < 0) {
*frame_count = 0;
return err;
}
cntr->handled_byte_count += target_byte_count;
if (cntr->handled_byte_count == cntr->max_size)
cntr->eof = true;
*frame_count = target_byte_count / bytes_per_frame;
return 0;
}
int container_context_post_process(struct container_context *cntr,
uint64_t *frame_count)
{
int err = 0;
assert(cntr);
assert(frame_count);
if (cntr->verbose && cntr->handled_byte_count > 0) {
fprintf(stderr, " Handled bytes: %" PRIu64 "\n",
cntr->handled_byte_count);
}
// NOTE* we cannot seek when using standard input/output.
if (!cntr->stdio && cntr->ops && cntr->ops->post_process) {
// Usually, need to write out processed bytes in container
// header even it this program is interrupted.
cntr->interrupted = false;
err = cntr->ops->post_process(cntr, cntr->handled_byte_count);
}
// Ensure to perform write-back from disk cache.
if (cntr->type == CONTAINER_TYPE_BUILDER)
fsync(cntr->fd);
if (err < 0)
return err;
if (cntr->bytes_per_sample == 0 || cntr->samples_per_frame == 0) {
*frame_count = 0;
} else {
*frame_count = cntr->handled_byte_count /
cntr->bytes_per_sample /
cntr->samples_per_frame;
}
return 0;
}
void container_context_destroy(struct container_context *cntr)
{
assert(cntr);
if (cntr->private_data)
free(cntr->private_data);
cntr->fd = 0;
cntr->private_data = NULL;
}