Blame axfer/container.c

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