2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdint.h>
|
2023-11-12 17:29:29 +00:00
|
|
|
#include
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
uint16_t Q;
|
|
|
|
|
|
|
|
static int8_t next(FILE * input) {
|
|
|
|
char c;
|
|
|
|
while((c = getc(input)) != EOF) {
|
|
|
|
Q++;
|
|
|
|
if(c == ' ' || c == '\n' || c == '\t')
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int32_t getnum(FILE * input) {
|
|
|
|
uint8_t sign = next(input), c;
|
|
|
|
int32_t n = 0, q;
|
|
|
|
if(sign != '\t' && sign != ' ')
|
|
|
|
return 0;
|
|
|
|
q = sign == '\t' ? -1 : 1;
|
|
|
|
while((c = next(input)) != '\n') {
|
|
|
|
n <<= 1;
|
|
|
|
if(c == '\t') n++;
|
|
|
|
if(!c) break;
|
|
|
|
}
|
|
|
|
return n * q;
|
|
|
|
}
|
|
|
|
|
|
|
|
static vector(char) getlab(FILE * input) {
|
|
|
|
vector(char) label = NULL;
|
|
|
|
uint8_t c;
|
|
|
|
while((c = next(input)) != '\n')
|
|
|
|
vector_push_back(label, c == '\t' ? 'T' : 'S');
|
|
|
|
vector_push_back(label, 0);
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct _label_t {
|
|
|
|
int32_t id;
|
|
|
|
char * name;
|
|
|
|
struct instruction_t parent;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct _label_t * getlabel(vector(struct _label_t) vec, char * label_text) {
|
|
|
|
vector_foreach(struct _label_t, it, vec)
|
|
|
|
if(!strcmp(label_text, it->name))
|
|
|
|
return it;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static vector(struct label_t) fixup_labels(vector(struct instruction_t) program, void(*warn)(char * s)) {
|
|
|
|
vector(struct _label_t) labels = NULL;
|
|
|
|
vector(struct label_t) labs = NULL;
|
|
|
|
int32_t labid = 1;
|
|
|
|
|
|
|
|
vector_foreach(struct instruction_t, it, program)
|
|
|
|
if(it->type == LBL) {
|
|
|
|
struct _label_t * l;
|
|
|
|
if((l = getlabel(labels, it->label)) != NULL) {
|
2023-11-12 17:29:29 +00:00
|
|
|
warn();
|
2023-11-12 13:49:57 +00:00
|
|
|
vector_free(it->label);
|
|
|
|
it->data = l->id;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
struct _label_t lab = {.id = labid, .parent = *it, .name = it->label};
|
|
|
|
struct label_t public_label = {.id = labid, .parent = it};
|
|
|
|
it->data = labid++;
|
|
|
|
vector_push_back(labels, lab);
|
|
|
|
vector_push_back(labs, public_label);
|
|
|
|
}
|
|
|
|
|
|
|
|
vector_foreach(struct instruction_t, it, program)
|
|
|
|
switch(it->type) {
|
|
|
|
case CALL: case JMP: case BZ: case BLTZ: {
|
|
|
|
struct _label_t * l;
|
|
|
|
if((l = getlabel(labels, it->label)) != NULL) {
|
|
|
|
vector_free(it->label);
|
|
|
|
it->data = l->id;
|
|
|
|
} else {
|
|
|
|
vector_free(it->label);
|
2023-11-12 17:29:29 +00:00
|
|
|
warn();
|
2023-11-12 13:49:57 +00:00
|
|
|
it->data = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vector_foreach(struct _label_t, it, labels)
|
|
|
|
vector_free(it->name);
|
|
|
|
|
|
|
|
vector_free(labels);
|
|
|
|
|
|
|
|
return labs;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct parse_result_t parse(FILE * input, void (*fatal)(char * s), void (*warn)(char * s)) {
|
|
|
|
vector(struct instruction_t) q = NULL;
|
|
|
|
struct instruction_t cur;
|
|
|
|
|
|
|
|
while(!feof(input)) {
|
|
|
|
switch(next(input)) {
|
|
|
|
case '\t':
|
|
|
|
switch(next(input)) {
|
|
|
|
case '\t':
|
|
|
|
vector_push_back(q, cur = parse_heap(input, fatal));
|
|
|
|
break;
|
|
|
|
case ' ':
|
|
|
|
vector_push_back(q, cur = parse_arith(input, fatal));
|
|
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
vector_push_back(q, cur = parse_io(input, fatal));
|
|
|
|
break;
|
|
|
|
default:
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(); return (struct parse_result_t) { NULL, NULL };
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ' ':
|
|
|
|
vector_push_back(q, cur = parse_stack(input, fatal));
|
|
|
|
break;
|
|
|
|
case '\n':
|
|
|
|
vector_push_back(q, cur = parse_flow(input, fatal));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(cur.type == ERR)
|
|
|
|
return (struct parse_result_t) { NULL, NULL };
|
|
|
|
}
|
|
|
|
|
|
|
|
return (struct parse_result_t) { q, fixup_labels(q, warn) };
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
#include
|
|
|
|
#include
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
static void append_code(char ** buf, char * format, ...) {
|
|
|
|
va_list args, args2;
|
|
|
|
va_start(args, format);
|
|
|
|
va_copy(args2, args);
|
|
|
|
uint32_t buflen = *buf ? strlen(*buf) : 0;
|
|
|
|
uint32_t length = 1 + buflen + vsnprintf(NULL, 0, format, args);
|
|
|
|
va_end(args);
|
|
|
|
if(*buf)
|
|
|
|
*buf = realloc(*buf, length);
|
|
|
|
else
|
|
|
|
*buf = malloc(length);
|
|
|
|
assert(*buf);
|
|
|
|
vsnprintf(*buf + buflen, length - buflen, format, args2);
|
|
|
|
va_end(args2);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define emit(x) append_code(&code, x)
|
|
|
|
#define emitf(x,...) append_code(&code, x, __VA_ARGS__)
|
|
|
|
|
|
|
|
char * compile(struct parse_result_t program) {
|
|
|
|
unsigned callid = vector_size(program.labels);
|
|
|
|
char * code = NULL;
|
|
|
|
|
|
|
|
return code;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef _MAP_H_
|
|
|
|
#define _MAP_H_
|
|
|
|
|
|
|
|
#define hashmap_str_lit(str) (str), sizeof(str) - 1
|
|
|
|
#define hashmap_static_arr(arr) (arr), sizeof(arr)
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
|
|
|
|
#define HASHMAP_HASH_INIT 2166136261u
|
|
|
|
|
|
|
|
#ifdef DIRAC_64
|
|
|
|
static uint32_t hash_data(const unsigned char* data, size_t size)
|
|
|
|
{
|
|
|
|
size_t nblocks = size / 8;
|
|
|
|
uint64_t hash = HASHMAP_HASH_INIT, last;
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < nblocks; ++i)
|
|
|
|
{
|
|
|
|
hash ^= (uint64_t)data[0] << 0 | (uint64_t)data[1] << 8 |
|
|
|
|
(uint64_t)data[2] << 16 | (uint64_t)data[3] << 24 |
|
|
|
|
(uint64_t)data[4] << 32 | (uint64_t)data[5] << 40 |
|
|
|
|
(uint64_t)data[6] << 48 | (uint64_t)data[7] << 56;
|
|
|
|
hash *= 0xbf58476d1ce4e5b9;
|
|
|
|
data += 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
last = size & 0xff;
|
|
|
|
switch (size % 8)
|
|
|
|
{
|
|
|
|
case 7:
|
|
|
|
last |= (uint64_t)data[6] << 56; /* fallthrough */
|
|
|
|
case 6:
|
|
|
|
last |= (uint64_t)data[5] << 48; /* fallthrough */
|
|
|
|
case 5:
|
|
|
|
last |= (uint64_t)data[4] << 40; /* fallthrough */
|
|
|
|
case 4:
|
|
|
|
last |= (uint64_t)data[3] << 32; /* fallthrough */
|
|
|
|
case 3:
|
|
|
|
last |= (uint64_t)data[2] << 24; /* fallthrough */
|
|
|
|
case 2:
|
|
|
|
last |= (uint64_t)data[1] << 16; /* fallthrough */
|
|
|
|
case 1:
|
|
|
|
last |= (uint64_t)data[0] << 8;
|
|
|
|
hash ^= last;
|
|
|
|
hash *= 0xd6e8feb86659fd93;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* compress to a 32-bit result. also serves as a finalizer. */
|
|
|
|
return hash ^ hash >> 32;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#ifdef DIRAC_32
|
|
|
|
static uint32_t hash_data(const unsigned char* data, size_t size) {
|
|
|
|
int i, j;
|
|
|
|
unsigned int byte, crc, mask;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
crc = 0xFFFFFFFF;
|
|
|
|
while (i < size) {
|
|
|
|
byte = data[i];
|
|
|
|
crc = crc ^ byte;
|
|
|
|
for (j = 7; j >= 0; j--) {
|
|
|
|
mask = -(crc & 1);
|
|
|
|
crc = (crc >> 1) ^ (0xEDB88320 & mask);
|
|
|
|
}
|
|
|
|
i = i + 1;
|
|
|
|
}
|
|
|
|
return ~crc;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static uint16_t hash_data(const unsigned char* data, size_t size) {
|
|
|
|
unsigned char x;
|
|
|
|
unsigned short crc = 0xFFFF;
|
|
|
|
|
|
|
|
while (size--){
|
|
|
|
x = crc >> 8 ^ *data++;
|
|
|
|
x ^= x>>4;
|
|
|
|
crc = (crc << 8) ^ ((unsigned short)(x << 12)) ^ ((unsigned short)(x <<5)) ^ ((unsigned short)x);
|
|
|
|
}
|
|
|
|
return crc;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* hashmaps can associate keys with pointer values or integral types. */
|
|
|
|
typedef struct hashmap hashmap;
|
|
|
|
|
|
|
|
/* a callback type used for iterating over a map/freeing entries:
|
|
|
|
* `void <function name>(void* key, size_t size, uintptr_t value, void* usr)`
|
|
|
|
* `usr` is a user pointer which can be passed through `hashmap_iterate`.
|
|
|
|
*/
|
|
|
|
typedef void (*hashmap_callback)(void *key, size_t ksize, uintptr_t value, void *usr);
|
|
|
|
|
|
|
|
static hashmap* hashmap_create(void);
|
|
|
|
|
|
|
|
/* only frees the hashmap object and buckets.
|
|
|
|
* does not call free on each element's `key` or `value`.
|
|
|
|
* to free data associated with an element, call `hashmap_iterate`.
|
|
|
|
*/
|
|
|
|
static void hashmap_free(hashmap* map);
|
|
|
|
|
|
|
|
/* does not make a copy of `key`.
|
|
|
|
* you must copy it yourself if you want to guarantee its lifetime,
|
|
|
|
* or if you intend to call `hashmap_key_free`.
|
|
|
|
*/
|
|
|
|
static void hashmap_set(hashmap* map, void* key, size_t ksize, uintptr_t value);
|
|
|
|
|
|
|
|
/* adds an entry if it doesn't exist, using the value of `*out_in`.
|
|
|
|
* if it does exist, it sets value in `*out_in`, meaning the value
|
|
|
|
* of the entry will be in `*out_in` regardless of whether or not
|
|
|
|
* it existed in the first place.
|
|
|
|
* returns true if the entry already existed, returns false otherwise.
|
|
|
|
*/
|
|
|
|
static int hashmap_get_set(hashmap* map, void* key, size_t ksize, uintptr_t* out_in);
|
|
|
|
|
|
|
|
/* similar to `hashmap_set()`, but when overwriting an entry,
|
|
|
|
* you'll be able properly free the old entry's data via a callback.
|
|
|
|
* unlike `hashmap_set()`, this function will overwrite the original key pointer,
|
|
|
|
* which means you can free the old key in the callback if applicable.
|
|
|
|
*/
|
|
|
|
static void hashmap_set_free(hashmap* map, void* key, size_t ksize, uintptr_t value, hashmap_callback c, void* usr);
|
|
|
|
|
|
|
|
static int hashmap_get(hashmap* map, void* key, size_t ksize, uintptr_t* out_val);
|
|
|
|
|
|
|
|
static int hashmap_size(hashmap* map);
|
|
|
|
|
|
|
|
/* iterate over the map, calling `c` on every element.
|
|
|
|
* goes through elements in the order they were added.
|
|
|
|
* the element's key, key size, value, and `usr` will be passed to `c`.
|
|
|
|
*/
|
|
|
|
static void hashmap_iterate(hashmap* map, hashmap_callback c, void* usr);
|
|
|
|
|
|
|
|
#define HASHMAP_DEFAULT_CAPACITY 5
|
|
|
|
#define HASHMAP_MAX_LOAD 0.75f
|
|
|
|
#define HASHMAP_RESIZE_FACTOR 2
|
|
|
|
|
|
|
|
struct bucket
|
|
|
|
{
|
|
|
|
/* `next` must be the first struct element.
|
|
|
|
* changing the order will break multiple functions */
|
|
|
|
struct bucket* next;
|
|
|
|
|
|
|
|
/* key, key size, key hash, and associated value */
|
|
|
|
void* key;
|
|
|
|
size_t ksize;
|
|
|
|
uint32_t hash;
|
|
|
|
uintptr_t value;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct hashmap
|
|
|
|
{
|
|
|
|
struct bucket* buckets;
|
|
|
|
int capacity;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
/* a linked list of all valid entries, in order */
|
|
|
|
struct bucket* first;
|
|
|
|
/* lets us know where to add the next element */
|
|
|
|
struct bucket* last;
|
|
|
|
};
|
|
|
|
|
|
|
|
static hashmap* hashmap_create(void)
|
|
|
|
{
|
|
|
|
hashmap* m = malloc(sizeof(hashmap));
|
|
|
|
m->capacity = HASHMAP_DEFAULT_CAPACITY;
|
|
|
|
m->count = 0;
|
|
|
|
|
|
|
|
m->buckets = calloc(HASHMAP_DEFAULT_CAPACITY, sizeof(struct bucket));
|
|
|
|
m->first = NULL;
|
|
|
|
|
|
|
|
/* this prevents branching in hashmap_set.
|
2023-11-12 17:29:29 +00:00
|
|
|
* m->first will be treated as the pointer in an imaginary bucket.
|
2023-11-12 13:49:57 +00:00
|
|
|
* when the first item is added, m->first will be set to the correct address.
|
|
|
|
*/
|
|
|
|
m->last = (struct bucket*)&m->first;
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hashmap_free(hashmap* m)
|
|
|
|
{
|
|
|
|
free(m->buckets);
|
|
|
|
free(m);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* puts an old bucket into a resized hashmap */
|
|
|
|
static struct bucket* resize_entry(hashmap* m, struct bucket* old_entry)
|
|
|
|
{
|
|
|
|
uint32_t index = old_entry->hash % m->capacity;
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
struct bucket* entry = &m->buckets[index];
|
|
|
|
|
|
|
|
if (entry->key == NULL)
|
|
|
|
{
|
|
|
|
*entry = *old_entry;
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
index = (index + 1) % m->capacity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hashmap_resize(hashmap* m)
|
|
|
|
{
|
|
|
|
struct bucket* old_buckets = m->buckets;
|
|
|
|
|
|
|
|
m->capacity *= HASHMAP_RESIZE_FACTOR;
|
|
|
|
m->buckets = calloc(m->capacity, sizeof(struct bucket));
|
|
|
|
m->last = (struct bucket*)&m->first;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
m->last->next = resize_entry(m, m->last->next);
|
|
|
|
m->last = m->last->next;
|
|
|
|
} while (m->last->next != NULL);
|
|
|
|
|
|
|
|
free(old_buckets);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct bucket* find_entry(hashmap* m, void* key, size_t ksize, uint32_t hash)
|
|
|
|
{
|
|
|
|
uint32_t index = hash % m->capacity;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
struct bucket* entry = &m->buckets[index];
|
|
|
|
|
|
|
|
/* kind of a thicc condition; */
|
|
|
|
/* I didn't want this to span multiple if statements or functions. */
|
|
|
|
if (entry->key == NULL ||
|
|
|
|
/* compare sizes, then hashes, then key data as a last resort. */
|
|
|
|
(entry->ksize == ksize &&
|
|
|
|
entry->hash == hash &&
|
|
|
|
memcmp(entry->key, key, ksize) == 0))
|
|
|
|
{
|
|
|
|
/* return the entry if a match or an empty bucket is found */
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
index = (index + 1) % m->capacity;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hashmap_set(hashmap* m, void* key, size_t ksize, uintptr_t val)
|
|
|
|
{
|
|
|
|
uint32_t hash;
|
|
|
|
struct bucket * entry;
|
|
|
|
|
|
|
|
if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity)
|
|
|
|
hashmap_resize(m);
|
|
|
|
|
|
|
|
hash = hash_data(key, ksize);
|
|
|
|
entry = find_entry(m, key, ksize, hash);
|
|
|
|
if (entry->key == NULL)
|
|
|
|
{
|
|
|
|
m->last->next = entry;
|
|
|
|
m->last = entry;
|
|
|
|
entry->next = NULL;
|
|
|
|
|
|
|
|
++m->count;
|
|
|
|
|
|
|
|
entry->key = key;
|
|
|
|
entry->ksize = ksize;
|
|
|
|
entry->hash = hash;
|
|
|
|
}
|
|
|
|
entry->value = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hashmap_get_set(hashmap* m, void* key, size_t ksize, uintptr_t* out_in)
|
|
|
|
{
|
|
|
|
uint32_t hash;
|
|
|
|
struct bucket * entry;
|
|
|
|
|
|
|
|
if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity)
|
|
|
|
hashmap_resize(m);
|
|
|
|
|
|
|
|
hash = hash_data(key, ksize);
|
|
|
|
entry = find_entry(m, key, ksize, hash);
|
|
|
|
if (entry->key == NULL)
|
|
|
|
{
|
|
|
|
m->last->next = entry;
|
|
|
|
m->last = entry;
|
|
|
|
entry->next = NULL;
|
|
|
|
|
|
|
|
++m->count;
|
|
|
|
|
|
|
|
entry->value = *out_in;
|
|
|
|
entry->key = key;
|
|
|
|
entry->ksize = ksize;
|
|
|
|
entry->hash = hash;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
*out_in = entry->value;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hashmap_set_free(hashmap* m, void* key, size_t ksize, uintptr_t val, hashmap_callback c, void* usr)
|
|
|
|
{
|
|
|
|
uint32_t hash;
|
|
|
|
struct bucket * entry;
|
|
|
|
|
|
|
|
if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity)
|
|
|
|
hashmap_resize(m);
|
|
|
|
|
|
|
|
hash = hash_data(key, ksize);
|
|
|
|
entry = find_entry(m, key, ksize, hash);
|
|
|
|
if (entry->key == NULL)
|
|
|
|
{
|
|
|
|
m->last->next = entry;
|
|
|
|
m->last = entry;
|
|
|
|
entry->next = NULL;
|
|
|
|
|
|
|
|
++m->count;
|
|
|
|
|
|
|
|
entry->key = key;
|
|
|
|
entry->ksize = ksize;
|
|
|
|
entry->hash = hash;
|
|
|
|
entry->value = val;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* allow the callback to free entry data.
|
|
|
|
* use old key and value so the callback can free them.
|
|
|
|
* the old key and value will be overwritten after this call. */
|
|
|
|
c(entry->key, ksize, entry->value, usr);
|
|
|
|
|
|
|
|
/* overwrite the old key pointer in case the callback frees it. */
|
|
|
|
entry->key = key;
|
|
|
|
entry->value = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hashmap_get(hashmap* m, void* key, size_t ksize, uintptr_t* out_val)
|
|
|
|
{
|
|
|
|
uint32_t hash = hash_data(key, ksize);
|
|
|
|
struct bucket* entry = find_entry(m, key, ksize, hash);
|
|
|
|
|
|
|
|
/* if there is no match, output val will just be NULL */
|
|
|
|
*out_val = entry->value;
|
|
|
|
|
|
|
|
return entry->key != NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hashmap_size(hashmap* m)
|
|
|
|
{
|
|
|
|
return m->count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hashmap_iterate(hashmap* m, hashmap_callback c, void* user_ptr)
|
|
|
|
{
|
|
|
|
/* loop through the linked list of valid entries
|
|
|
|
* this way we can skip over empty buckets */
|
|
|
|
struct bucket* current = m->first;
|
|
|
|
|
|
|
|
int co = 0;
|
|
|
|
|
|
|
|
while (current != NULL)
|
|
|
|
{
|
|
|
|
c(current->key, current->ksize, current->value, user_ptr);
|
|
|
|
|
|
|
|
current = current->next;
|
|
|
|
|
|
|
|
if (co > 1000)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
co++;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
Copyright (C) 2006-2016,2018 Con Kolivas
|
|
|
|
Copyright (C) 2011, 2022 Peter Hyman
|
|
|
|
Copyright (C) 1998-2003 Andrew Tridgell
|
|
|
|
Copyright (C) 2022 Kamila Szewczyk
|
|
|
|
|
|
|
|
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 2 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 <arpa/inet.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/statvfs.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <utime.h>
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
#include
|
|
|
|
#include
|
|
|
|
#include
|
|
|
|
#include
|
|
|
|
#include
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
#define MAGIC_LEN (20) // new v 0.9 magic header
|
|
|
|
#define MAGIC_V8_LEN (18) // new v 0.8 magic header
|
|
|
|
#define OLD_MAGIC_LEN (24) // Just to read older versions
|
|
|
|
#define MAGIC_HEADER (6) // to validate file initially
|
|
|
|
|
|
|
|
static void release_hashes(rzip_control * control);
|
|
|
|
|
|
|
|
static i64 fdout_seekto(rzip_control * control, i64 pos) {
|
|
|
|
if (TMP_OUTBUF) {
|
|
|
|
pos -= control->out_relofs;
|
|
|
|
control->out_ofs = pos;
|
|
|
|
if (unlikely(pos > control->out_len || pos < 0)) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_err(, pos);
|
2023-11-12 13:49:57 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return lseek(control->fd_out, pos, SEEK_SET);
|
|
|
|
}
|
|
|
|
|
|
|
|
i64 get_ram(rzip_control * control) {
|
|
|
|
#ifdef __APPLE__
|
|
|
|
#include <sys/sysctl.h>
|
|
|
|
int mib[2];
|
|
|
|
size_t len;
|
|
|
|
i64 ramsize;
|
|
|
|
|
|
|
|
mib[0] = CTL_HW;
|
|
|
|
mib[1] = HW_MEMSIZE;
|
|
|
|
sysctl(mib, 2, &ramsize, &len, NULL, 0);
|
|
|
|
#elif defined(__OpenBSD__)
|
|
|
|
#include <sys/resource.h>
|
|
|
|
struct rlimit rl;
|
|
|
|
i64 ramsize = (i64)sysconf(_SC_PHYS_PAGES) * PAGE_SIZE;
|
|
|
|
|
|
|
|
/* Raise limits all the way to the max */
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (getrlimit(RLIMIT_DATA, &rl) == -1) fatal((), -1);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
rl.rlim_cur = rl.rlim_max;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (setrlimit(RLIMIT_DATA, &rl) == -1) fatal((), -1);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
/* Declare detected RAM to be either the max RAM available from
|
|
|
|
physical memory or the max RAM allowed by RLIMIT_DATA, whatever
|
|
|
|
is smaller, to prevent the heuristics from selecting
|
|
|
|
compression windows which cause mrzip to go into deep swap */
|
|
|
|
|
|
|
|
if (rl.rlim_max < ramsize) return rl.rlim_max;
|
|
|
|
|
|
|
|
return ramsize;
|
|
|
|
#else /* __APPLE__ or __Open_BSD__ */
|
|
|
|
i64 ramsize;
|
|
|
|
FILE * meminfo;
|
|
|
|
char aux[256];
|
|
|
|
|
|
|
|
ramsize = (i64)sysconf(_SC_PHYS_PAGES) * PAGE_SIZE;
|
|
|
|
if (ramsize <= 0) {
|
|
|
|
/* Workaround for uclibc which doesn't properly support sysconf */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (!(meminfo = fopen();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
while (!feof(meminfo) && !fscanf(meminfo, , &ramsize)) {
|
2023-11-12 13:49:57 +00:00
|
|
|
if (unlikely(fgets(aux, sizeof(aux), meminfo) == NULL)) {
|
|
|
|
fclose(meminfo);
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-12 17:29:29 +00:00
|
|
|
if (fclose(meminfo) == -1) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
ramsize *= 1024;
|
|
|
|
}
|
|
|
|
#endif
|
2023-11-12 17:29:29 +00:00
|
|
|
if (ramsize <= 0) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
return ramsize;
|
|
|
|
}
|
|
|
|
|
|
|
|
i64 nloops(i64 seconds, uchar * b1, uchar * b2) {
|
|
|
|
i64 nloops;
|
|
|
|
int nbits;
|
|
|
|
|
|
|
|
nloops = ARBITRARY_AT_EPOCH * pow(MOORE_TIMES_PER_SECOND, seconds);
|
|
|
|
if (nloops < ARBITRARY) nloops = ARBITRARY;
|
|
|
|
for (nbits = 0; nloops > 255; nbits++) nloops = nloops >> 1;
|
|
|
|
*b1 = nbits;
|
|
|
|
*b2 = nloops;
|
|
|
|
return nloops << nbits;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool write_magic(rzip_control * control) {
|
|
|
|
unsigned char magic[MAGIC_LEN] = { 'M', 'R', 'Z', 'I', MRZIP_MAJOR, MRZIP_MINOR };
|
|
|
|
|
|
|
|
/* In encrypted files, the size is left unknown
|
|
|
|
* and instead the salt is stored here to preserve space. */
|
|
|
|
// FIXME. I think we can do better. 8 bytes is no reason to save space
|
|
|
|
if (ENCRYPT) {
|
|
|
|
memcpy(&magic[6], &control->salt, 8);
|
|
|
|
magic[15] = control->enc_code; /* write whatever encryption code */
|
|
|
|
} else if (control->eof) {
|
|
|
|
i64 esize = htole64(control->st_size); // we know file size even when piped
|
|
|
|
memcpy(&magic[6], &esize, 8);
|
|
|
|
}
|
|
|
|
/* This is a flag that the archive contains an hash sum at the end
|
|
|
|
* which can be used as an integrity check instead of crc check.
|
|
|
|
* crc is still stored for compatibility with 0.5 versions.
|
|
|
|
*/
|
|
|
|
if (HAS_HASH) magic[14] = control->hash_code; /* write whatever hash */
|
|
|
|
|
|
|
|
magic[16] = 0;
|
|
|
|
|
|
|
|
/* save LZMA dictionary size */
|
|
|
|
if (ZPAQ_COMPRESS) {
|
|
|
|
/* Save zpaq compression level and block size as one byte */
|
|
|
|
/* High order bits = 128 + (16 * Compression Level 3-5)
|
|
|
|
* Low order bits = Block Size 1-11
|
|
|
|
* 128 necessary to distinguish in decoding LZMA which is 1-40
|
|
|
|
* 1CCC BBBB in binary */
|
|
|
|
magic[17] = 0b10000000 + (control->zpaq_level << 4) + control->zpaq_bs;
|
|
|
|
/* Decoding would be
|
|
|
|
* magic byte & 127 (clear high bit)
|
|
|
|
* zpaq_bs = magic byte & 0X0F
|
|
|
|
* zpaq_level = magic_byte >> 4
|
|
|
|
*/
|
|
|
|
} else if (BZIP3_COMPRESS) {
|
|
|
|
/* Save block size. ZPAQ compression level is from 3 to 5, so this is sound.
|
|
|
|
bzip3 blocksize is from 1 to 8 (or 0 to 7). */
|
|
|
|
magic[17] = 0b11110000 + bzip3_prop_from_block_size(control->bzip3_block_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* save compression levels
|
|
|
|
* high order bits, rzip compression level
|
|
|
|
* low order bits mrzip compression level
|
|
|
|
*/
|
|
|
|
magic[18] = (control->rzip_compression_level << 4) + control->compression_level;
|
|
|
|
|
|
|
|
/* store comment length */
|
|
|
|
magic[19] = (char)control->comment_length;
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fdout_seekto(control, 0))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(put_fdout(control, magic, MAGIC_LEN) != MAGIC_LEN)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
/* now write comment if any */
|
|
|
|
if (magic[19]) {
|
|
|
|
if (unlikely(put_fdout(control, control->comment, control->comment_length) != control->comment_length))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
control->magic_written = 1;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline i64 enc_loops(uchar b1, uchar b2) { return (i64)b2 << (i64)b1; }
|
|
|
|
|
|
|
|
// check for comments
|
|
|
|
// Called only if comment length > 0
|
|
|
|
|
|
|
|
static void get_comment(rzip_control * control, int fd_in, unsigned char * magic) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!(control->comment = malloc(magic[19] + 1)))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
/* read comment */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(read(fd_in, control->comment, magic[19]) != magic[19])) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
control->comment_length = magic[19];
|
|
|
|
control->comment[control->comment_length] = '\0';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// retriev lzma properties
|
|
|
|
|
|
|
|
static void get_hash_from_magic(rzip_control * control, unsigned char * magic) {
|
|
|
|
/* Whether this archive contains hash data at the end or not */
|
|
|
|
if (*magic > 0 && *magic <= MAXHASH) {
|
|
|
|
control->flags |= FLAG_HASHED;
|
|
|
|
control->hash_code = *magic; /* set hash code */
|
|
|
|
control->hash_label = &hashes[control->hash_code].label[0];
|
|
|
|
control->hash_gcode = &hashes[control->hash_code].gcode;
|
|
|
|
control->hash_len = &hashes[control->hash_code].length;
|
|
|
|
} else
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get encrypted salt
|
|
|
|
|
|
|
|
static void get_encryption(rzip_control * control, unsigned char * magic, unsigned char * salt) {
|
|
|
|
if (*magic > 0 && *magic <= MAXENC) {
|
|
|
|
control->flags |= FLAG_ENCRYPT;
|
|
|
|
control->enc_code = *magic;
|
|
|
|
/* In encrypted files, the size field is used to store the salt
|
|
|
|
* instead and the size is unknown, just like a STDOUT chunked
|
|
|
|
* file */
|
|
|
|
memcpy(&control->salt, salt, 8);
|
|
|
|
control->st_size = 0;
|
|
|
|
control->encloops = enc_loops(control->salt[0], control->salt[1]);
|
|
|
|
} else if (ENCRYPT) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_err();
|
2023-11-12 13:49:57 +00:00
|
|
|
control->flags &= ~FLAG_ENCRYPT;
|
|
|
|
control->enc_code = 0;
|
|
|
|
}
|
|
|
|
control->enc_label = &encryptions[control->enc_code].label[0];
|
|
|
|
control->enc_gcode = &encryptions[control->enc_code].gcode;
|
|
|
|
control->enc_keylen = &encryptions[control->enc_code].keylen;
|
|
|
|
control->enc_ivlen = &encryptions[control->enc_code].ivlen;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// expected size
|
|
|
|
|
|
|
|
static void get_expected_size(rzip_control * control, unsigned char * magic) {
|
|
|
|
i64 expected_size;
|
|
|
|
|
|
|
|
memcpy(&expected_size, &magic[6], 8);
|
|
|
|
control->st_size = le64toh(expected_size);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// new mrzip v8 magic header format.
|
|
|
|
|
|
|
|
static void get_magic_v8(rzip_control * control, unsigned char * magic) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!magic[15]) // not encrypted
|
|
|
|
get_expected_size(control, magic);
|
|
|
|
get_encryption(control, &magic[15], &magic[6]);
|
|
|
|
|
|
|
|
if ((magic[17] & 0b10000000)) // bzip3 or zpaq block sizes/levels stored
|
|
|
|
{
|
|
|
|
if ((magic[17] & 0b11110000) == 0b11110000) { // bzip3 block size
|
|
|
|
control->bzip3_bs = magic[17] & 0b00001111; // bzip3 block size code 0 to 8
|
|
|
|
control->bzip3_block_size = BZIP3_BLOCK_SIZE_FROM_PROP(control->bzip3_bs); // Real Block Size
|
|
|
|
} else // zpaq block and compression level stored
|
|
|
|
{
|
|
|
|
control->zpaq_bs = magic[17] & 0b00001111; // low order bits are block size
|
|
|
|
magic[17] &= 0b01110000; // strip high bit
|
|
|
|
control->zpaq_level = magic[17] >> 4; // divide by 16
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
get_hash_from_magic(control, &magic[14]);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// new mrzip v9 magic header format.
|
|
|
|
|
|
|
|
static void get_magic_v9(rzip_control * control, int fd_in, unsigned char * magic) {
|
|
|
|
/* get compression levels
|
|
|
|
* rzip level is high order bits
|
|
|
|
* mrzip level is low order bits
|
|
|
|
*/
|
|
|
|
control->compression_level = magic[18] & 0b00001111;
|
|
|
|
control->rzip_compression_level = magic[18] >> 4;
|
|
|
|
|
|
|
|
if (magic[19]) /* get comment if there is one */
|
|
|
|
get_comment(control, fd_in, magic);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
static bool get_magic(rzip_control * control, int fd_in, unsigned char * magic) {
|
|
|
|
memcpy(&control->major_version, &magic[4], 1);
|
|
|
|
memcpy(&control->minor_version, &magic[5], 1);
|
|
|
|
|
|
|
|
/* zero out compression levels so info does not show for earlier versions */
|
|
|
|
control->rzip_compression_level = control->compression_level = 0;
|
|
|
|
/* remove checks for mrzip < 0.6 */
|
|
|
|
if (control->major_version == 0) {
|
|
|
|
switch (control->minor_version) {
|
|
|
|
case 9: /* version 0.9 adds two bytes */
|
|
|
|
get_magic_v8(control, magic);
|
|
|
|
get_magic_v9(control, fd_in, magic);
|
|
|
|
break;
|
|
|
|
default:
|
2023-11-12 17:29:29 +00:00
|
|
|
print_err(, control->major_version,
|
2023-11-12 13:49:57 +00:00
|
|
|
control->minor_version);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool read_magic(rzip_control * control, int fd_in, i64 * expected_size) {
|
|
|
|
unsigned char magic[OLD_MAGIC_LEN]; // Make at least big enough for old magic
|
|
|
|
int bytes_to_read; // simplify reading of magic
|
|
|
|
|
|
|
|
memset(magic, 0, sizeof(magic));
|
|
|
|
/* Initially read only file type and version */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(read(fd_in, magic, MAGIC_HEADER) != MAGIC_HEADER)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(strncmp(magic, );
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (magic[4] == 0) {
|
|
|
|
if (magic[5] < 8) /* old magic */
|
|
|
|
bytes_to_read = OLD_MAGIC_LEN;
|
|
|
|
else if (magic[5] == 8) /* 0.8 file */
|
|
|
|
bytes_to_read = MAGIC_V8_LEN;
|
|
|
|
else /* ASSUME current version */
|
|
|
|
bytes_to_read = MAGIC_LEN;
|
|
|
|
|
|
|
|
if (unlikely(read(fd_in, &magic[6], bytes_to_read - MAGIC_HEADER) != bytes_to_read - MAGIC_HEADER))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (unlikely(!get_magic(control, fd_in, magic))) return false;
|
|
|
|
*expected_size = control->st_size;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* show mrzip version
|
|
|
|
* helps preserve output format when validating
|
|
|
|
*/
|
|
|
|
static void show_version(rzip_control * control) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, control->major_version, control->minor_version);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* preserve ownership and permissions where possible */
|
|
|
|
static bool preserve_perms(rzip_control * control, int fd_in, int fd_out) {
|
|
|
|
struct stat st;
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fstat(fd_in, &st))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (unlikely(fchmod(fd_out, (st.st_mode & 0666))))
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
/* chown fail is not fatal_return(( */
|
|
|
|
if (unlikely(fchown(fd_out, st.st_uid, st.st_gid)))
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool preserve_times(rzip_control * control, int fd_in) {
|
|
|
|
struct utimbuf times;
|
|
|
|
struct stat st;
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fstat(fd_in, &st))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
times.actime = 0;
|
|
|
|
times.modtime = st.st_mtime;
|
|
|
|
if (unlikely(utime(control->outfile, ×)))
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open a temporary outputfile to emulate stdout */
|
|
|
|
int open_tmpoutfile(rzip_control * control) {
|
|
|
|
int fd_out;
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (STDOUT && !TEST_ONLY) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
control->outfile = realloc(NULL, strlen(control->tmpdir) + 16);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!control->outfile)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
strcpy(control->outfile, control->tmpdir);
|
2023-11-12 17:29:29 +00:00
|
|
|
strcat(control->outfile, );
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
fd_out = mkstemp(control->outfile);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (fd_out == -1) fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
register_outfile(control, control->outfile, TEST_ONLY || STDOUT || !KEEP_BROKEN);
|
|
|
|
return fd_out;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool fwrite_stdout(rzip_control * control, void * buf, i64 len) {
|
|
|
|
uchar * offset_buf = buf;
|
|
|
|
ssize_t ret, nmemb;
|
|
|
|
i64 total;
|
|
|
|
|
|
|
|
total = 0;
|
|
|
|
while (len > 0) {
|
|
|
|
nmemb = len;
|
|
|
|
ret = fwrite(offset_buf, 1, nmemb, control->outFILE);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(ret == -1)) fatal(, nmemb);
|
2023-11-12 13:49:57 +00:00
|
|
|
len -= ret;
|
|
|
|
offset_buf += ret;
|
|
|
|
total += ret;
|
|
|
|
}
|
|
|
|
fflush(control->outFILE);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool write_fdout(rzip_control * control, void * buf, i64 len) {
|
|
|
|
uchar * offset_buf = buf;
|
|
|
|
ssize_t ret, nmemb;
|
|
|
|
|
|
|
|
while (len > 0) {
|
|
|
|
nmemb = len;
|
|
|
|
ret = write(control->fd_out, offset_buf, (size_t)nmemb);
|
|
|
|
/* error if ret == -1 only. Otherwise, buffer not wholly written */
|
|
|
|
if (unlikely(ret == -1)) /* error, not underflow */
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, nmemb);
|
2023-11-12 13:49:57 +00:00
|
|
|
len -= ret;
|
|
|
|
offset_buf += ret;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool flush_tmpoutbuf(rzip_control * control) {
|
|
|
|
if (!TEST_ONLY) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_maxverbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (STDOUT) {
|
|
|
|
if (unlikely(!fwrite_stdout(control, control->tmp_outbuf, control->out_len))) return false;
|
|
|
|
} else {
|
|
|
|
if (unlikely(!write_fdout(control, control->tmp_outbuf, control->out_len))) return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
control->out_relofs += control->out_len;
|
|
|
|
control->out_ofs = control->out_len = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Dump temporary outputfile to perform stdout */
|
|
|
|
bool dump_tmpoutfile(rzip_control * control, int fd_out) {
|
|
|
|
FILE * tmpoutfp;
|
|
|
|
int tmpchar;
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fd_out == -1)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
/* flush anything not yet in the temporary file */
|
|
|
|
fsync(fd_out);
|
2023-11-12 17:29:29 +00:00
|
|
|
tmpoutfp = fdopen(fd_out, );
|
|
|
|
if (unlikely(tmpoutfp == NULL)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
rewind(tmpoutfp);
|
|
|
|
|
|
|
|
if (!TEST_ONLY) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
while ((tmpchar = fgetc(tmpoutfp)) != EOF) putchar(tmpchar);
|
|
|
|
fflush(control->outFILE);
|
|
|
|
rewind(tmpoutfp);
|
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(ftruncate(fd_out, 0))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Used if we're unable to read STDIN into the temporary buffer, shunts data
|
|
|
|
* to temporary file */
|
|
|
|
bool write_fdin(rzip_control * control) {
|
|
|
|
uchar * offset_buf = control->tmp_inbuf;
|
|
|
|
i64 len = control->in_len;
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
while (len > 0) {
|
|
|
|
ret = len;
|
|
|
|
ret = write(control->fd_in, offset_buf, (size_t)ret);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(ret == -1)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
len -= ret;
|
|
|
|
offset_buf += ret;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open a temporary inputfile to perform stdin decompression */
|
|
|
|
int open_tmpinfile(rzip_control * control) {
|
|
|
|
int fd_in = -1;
|
|
|
|
|
|
|
|
/* Use temporary directory if there is one. /tmp is default */
|
|
|
|
control->infile = malloc(strlen(control->tmpdir) + 15);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!control->infile)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
strcpy(control->infile, control->tmpdir);
|
2023-11-12 17:29:29 +00:00
|
|
|
strcat(control->infile, );
|
2023-11-12 13:49:57 +00:00
|
|
|
fd_in = mkstemp(control->infile);
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (fd_in == -1) fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
register_infile(control, control->infile, (DECOMPRESS || TEST_ONLY) && STDIN);
|
|
|
|
/* Unlink temporary file immediately to minimise chance of files left
|
|
|
|
* lying around */
|
|
|
|
if (unlikely(unlink(control->infile))) {
|
|
|
|
close(fd_in);
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
return fd_in;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool read_tmpinmagic(rzip_control * control, int fd_in) {
|
|
|
|
/* just in case < 0.8 file */
|
|
|
|
char magic[OLD_MAGIC_LEN];
|
|
|
|
int bytes_to_read, i, tmpchar;
|
|
|
|
|
|
|
|
memset(magic, 0, sizeof(magic));
|
|
|
|
/* Initially read only file type and version */
|
|
|
|
for (i = 0; i < MAGIC_HEADER; i++) {
|
|
|
|
tmpchar = getchar();
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(tmpchar == EOF)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
magic[i] = (char)tmpchar;
|
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(strncmp(magic, );
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (magic[4] == 0) {
|
|
|
|
if (magic[5] < 8) /* old magic */
|
|
|
|
bytes_to_read = OLD_MAGIC_LEN;
|
|
|
|
else if (magic[5] == 8) /* 0.8 file */
|
|
|
|
bytes_to_read = MAGIC_V8_LEN;
|
|
|
|
else /* ASSUME current version */
|
|
|
|
bytes_to_read = MAGIC_LEN;
|
|
|
|
|
|
|
|
for (; i < bytes_to_read; i++) {
|
|
|
|
tmpchar = getchar();
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(tmpchar == EOF)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
magic[i] = (char)tmpchar;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return get_magic(control, fd_in, magic);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read data from stdin into temporary inputfile */
|
|
|
|
bool read_tmpinfile(rzip_control * control, int fd_in) {
|
|
|
|
FILE * tmpinfp;
|
|
|
|
int tmpchar;
|
|
|
|
|
|
|
|
if (fd_in == -1) return false;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (control->flags & FLAG_SHOW_PROGRESS) fprintf(control->msgout, );
|
|
|
|
tmpinfp = fdopen(fd_in, );
|
|
|
|
if (unlikely(tmpinfp == NULL)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
while ((tmpchar = getchar()) != EOF) fputc(tmpchar, tmpinfp);
|
|
|
|
|
|
|
|
fflush(tmpinfp);
|
|
|
|
rewind(tmpinfp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* To perform STDOUT, we allocate a proportion of ram that is then used as
|
|
|
|
* a pseudo-temporary file */
|
|
|
|
static bool open_tmpoutbuf(rzip_control * control) {
|
|
|
|
i64 maxlen = control->maxram;
|
|
|
|
void * buf;
|
|
|
|
|
|
|
|
while (42) {
|
|
|
|
round_to_page(&maxlen);
|
|
|
|
buf = malloc(maxlen);
|
|
|
|
if (buf) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_maxverbose(, maxlen);
|
2023-11-12 13:49:57 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
maxlen = maxlen / 3 * 2;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (maxlen < 100000000) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
control->flags |= FLAG_TMP_OUTBUF;
|
|
|
|
/* Allocate slightly more so we can cope when the buffer overflows and
|
|
|
|
* fall back to a real temporary file */
|
|
|
|
control->out_maxlen = maxlen + control->page_size;
|
|
|
|
control->tmp_outbuf = buf;
|
|
|
|
if (!DECOMPRESS && !TEST_ONLY) control->out_ofs = control->out_len = MAGIC_LEN + control->comment_length;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We've decided to use a temporary output file instead of trying to store
|
|
|
|
* all the output buffer in ram so we can free up the ram and increase the
|
|
|
|
* maximum sizes of ram we can allocate */
|
|
|
|
void close_tmpoutbuf(rzip_control * control) {
|
|
|
|
control->flags &= ~FLAG_TMP_OUTBUF;
|
|
|
|
dealloc(control->tmp_outbuf);
|
|
|
|
control->usable_ram = control->maxram += control->ramsize / 18;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool open_tmpinbuf(rzip_control * control) {
|
|
|
|
control->flags |= FLAG_TMP_INBUF;
|
|
|
|
control->in_maxlen = control->maxram;
|
|
|
|
control->tmp_inbuf = malloc(control->maxram + control->page_size);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!control->tmp_inbuf)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear_tmpinbuf(rzip_control * control) { control->in_len = control->in_ofs = 0; }
|
|
|
|
|
|
|
|
bool clear_tmpinfile(rzip_control * control) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(lseek(control->fd_in, 0, SEEK_SET))) fatal();
|
|
|
|
if (unlikely(ftruncate(control->fd_in, 0))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* As per temporary output file but for input file */
|
|
|
|
void close_tmpinbuf(rzip_control * control) {
|
|
|
|
control->flags &= ~FLAG_TMP_INBUF;
|
|
|
|
dealloc(control->tmp_inbuf);
|
|
|
|
control->usable_ram = control->maxram += control->ramsize / 18;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_pass(rzip_control * control, char * s) {
|
|
|
|
int len;
|
|
|
|
|
|
|
|
memset(s, 0, PASS_LEN - SALT_LEN);
|
|
|
|
if (control->passphrase)
|
|
|
|
strncpy(s, control->passphrase, PASS_LEN - SALT_LEN - 1);
|
|
|
|
else if (unlikely(fgets(s, PASS_LEN - SALT_LEN, stdin) == NULL))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
len = strlen(s);
|
|
|
|
if (len > 0 && ('\r' == s[len - 1] || '\n' == s[len - 1])) s[len - 1] = '\0';
|
|
|
|
if (len > 1 && ('\r' == s[len - 2] || '\n' == s[len - 2])) s[len - 2] = '\0';
|
|
|
|
len = strlen(s);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(0 == len)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool get_hash(rzip_control * control, int make_hash) {
|
|
|
|
char *passphrase, *testphrase;
|
|
|
|
struct termios termios_p;
|
|
|
|
int prompt = control->passphrase == NULL;
|
|
|
|
|
|
|
|
passphrase = calloc(PASS_LEN, 1);
|
|
|
|
testphrase = calloc(PASS_LEN, 1);
|
|
|
|
control->salt_pass = calloc(PASS_LEN, 1);
|
|
|
|
control->hash = calloc(HASH_LEN, 1);
|
|
|
|
if (unlikely(!passphrase || !testphrase || !control->salt_pass || !control->hash)) {
|
|
|
|
dealloc(testphrase);
|
|
|
|
dealloc(passphrase);
|
|
|
|
dealloc(control->salt_pass);
|
|
|
|
dealloc(control->hash);
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
mlock(passphrase, PASS_LEN);
|
|
|
|
mlock(testphrase, PASS_LEN);
|
|
|
|
mlock(control->salt_pass, PASS_LEN);
|
|
|
|
mlock(control->hash, HASH_LEN);
|
|
|
|
|
|
|
|
/* mrzip library callback code removed */
|
|
|
|
/* Disable stdin echo to screen */
|
|
|
|
tcgetattr(fileno(stdin), &termios_p);
|
|
|
|
termios_p.c_lflag &= ~ECHO;
|
|
|
|
tcsetattr(fileno(stdin), 0, &termios_p);
|
|
|
|
retry_pass:
|
2023-11-12 17:29:29 +00:00
|
|
|
if (prompt) print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
control->salt_pass_len = get_pass(control, passphrase) + SALT_LEN;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (prompt) print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (make_hash) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (prompt) print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
get_pass(control, testphrase);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (prompt) print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (strcmp(passphrase, testphrase)) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
goto retry_pass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
termios_p.c_lflag |= ECHO;
|
|
|
|
tcsetattr(fileno(stdin), 0, &termios_p);
|
|
|
|
memset(testphrase, 0, PASS_LEN);
|
|
|
|
memcpy(control->salt_pass, control->salt, SALT_LEN);
|
|
|
|
memcpy(control->salt_pass + SALT_LEN, passphrase, PASS_LEN - SALT_LEN);
|
|
|
|
lrz_stretch(control);
|
|
|
|
memset(passphrase, 0, PASS_LEN);
|
|
|
|
munlock(passphrase, PASS_LEN);
|
|
|
|
munlock(testphrase, PASS_LEN);
|
|
|
|
dealloc(testphrase);
|
|
|
|
dealloc(passphrase);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void release_hashes(rzip_control * control) {
|
|
|
|
memset(control->salt_pass, 0, PASS_LEN);
|
|
|
|
memset(control->hash, 0, HASH_LEN);
|
|
|
|
munlock(control->salt_pass, PASS_LEN);
|
|
|
|
munlock(control->hash, HASH_LEN);
|
|
|
|
dealloc(control->salt_pass);
|
|
|
|
dealloc(control->hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get_header_info(rzip_control * control, int fd_in, uchar * ctype, i64 * c_len, i64 * u_len, i64 * last_head,
|
|
|
|
int chunk_bytes) {
|
|
|
|
uchar enc_head[25 + SALT_LEN];
|
|
|
|
if (ENCRYPT) {
|
|
|
|
// read in salt
|
|
|
|
// first 8 bytes, instead of chunk bytes and size
|
|
|
|
if (unlikely(read(fd_in, enc_head, SALT_LEN) != SALT_LEN))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(read(fd_in, ctype, 1) != 1)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
*c_len = *u_len = *last_head = 0;
|
|
|
|
/* remove checks for mrzip < 0.6 */
|
|
|
|
if (control->major_version == 0) {
|
|
|
|
// header the same after v 0.4 except for chunk bytes
|
|
|
|
int read_len;
|
|
|
|
|
|
|
|
read_len = chunk_bytes;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(read(fd_in, c_len, read_len) != read_len)) fatal();
|
|
|
|
if (unlikely(read(fd_in, u_len, read_len) != read_len)) fatal();
|
|
|
|
if (unlikely(read(fd_in, last_head, read_len) != read_len)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
*c_len = le64toh(*c_len);
|
|
|
|
*u_len = le64toh(*u_len);
|
|
|
|
*last_head = le64toh(*last_head);
|
|
|
|
if (ENCRYPT) {
|
|
|
|
// decrypt header suppressing printing max verbose message
|
|
|
|
if (unlikely(!decrypt_header(control, enc_head, ctype, c_len, u_len, last_head, LRZ_VALIDATE)))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
} // control->major_version
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static double percentage(i64 num, i64 den) {
|
|
|
|
double d_num, d_den;
|
|
|
|
|
|
|
|
if (den < 100) {
|
|
|
|
d_num = num * 100;
|
|
|
|
d_den = den;
|
|
|
|
if (!d_den) d_den = 1;
|
|
|
|
} else {
|
|
|
|
d_num = num;
|
|
|
|
d_den = den / 100;
|
|
|
|
}
|
|
|
|
return d_num / d_den;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If Decompressing or Testing, omit printing, just read file and see if valid
|
|
|
|
// using construct if (INFO)
|
|
|
|
// Encrypted files cannot be checked now
|
|
|
|
bool get_fileinfo(rzip_control * control) {
|
|
|
|
i64 u_len, c_len, second_last, last_head, utotal = 0, ctotal = 0, ofs, stream_head[2];
|
|
|
|
i64 expected_size, infile_size, chunk_size = 0, chunk_total = 0;
|
|
|
|
int header_length = 0, stream = 0, chunk = 0;
|
|
|
|
char *tmp, *infilecopy = NULL;
|
|
|
|
char chunk_byte = 0;
|
|
|
|
long double cratio, bpb;
|
|
|
|
uchar ctype = 0;
|
|
|
|
uchar save_ctype = 255;
|
|
|
|
struct stat st;
|
|
|
|
int fd_in;
|
|
|
|
int lzma_ret;
|
|
|
|
|
|
|
|
// Take out all STDIN checks
|
|
|
|
struct stat fdin_stat;
|
|
|
|
|
|
|
|
if (unlikely(stat(control->infile, &fdin_stat)))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
else if (unlikely(!S_ISREG(fdin_stat.st_mode)))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
else
|
|
|
|
infilecopy = strdupa(control->infile);
|
|
|
|
|
|
|
|
fd_in = open(infilecopy, O_RDONLY);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fd_in == -1)) fatal(, infilecopy);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
/* Get file size */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fstat(fd_in, &st))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
infile_size = st.st_size;
|
|
|
|
|
|
|
|
/* Get decompressed size */
|
|
|
|
if (unlikely(!read_magic(control, fd_in, &expected_size))) goto error;
|
|
|
|
|
|
|
|
if (INFO) show_version(control); // show version if not validating
|
|
|
|
|
|
|
|
if (ENCRYPT) {
|
|
|
|
/* can only show info for current mrzip files */
|
|
|
|
if (control->major_version == 0) {
|
|
|
|
if (!control->salt_pass_len) // Only get password if needed
|
|
|
|
if (unlikely(!get_hash(control, 0))) return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove checks for mrzip < 0.6 */
|
|
|
|
if (control->major_version == 0) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(read(fd_in, &chunk_byte, 1) != 1)) fatal();
|
|
|
|
if (unlikely(chunk_byte < 1 || chunk_byte > 8)) fatal(, chunk_byte);
|
|
|
|
if (unlikely(read(fd_in, &control->eof, 1) != 1)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (!ENCRYPT) {
|
|
|
|
if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
chunk_size = le64toh(chunk_size);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(chunk_size < 0)) fatal(, chunk_size);
|
2023-11-12 13:49:57 +00:00
|
|
|
/* set header offsets for earlier versions */
|
|
|
|
switch (control->minor_version) {
|
|
|
|
case 9:
|
|
|
|
ofs = 22 + control->comment_length; /* comment? Add length */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ofs += chunk_byte;
|
|
|
|
/* header length is the same for non-encrypted files */
|
|
|
|
header_length = 1 + (chunk_byte * 3);
|
|
|
|
} else { /* ENCRYPTED */
|
|
|
|
chunk_byte = 8; // chunk byte size is always 8 for encrypted files
|
|
|
|
chunk_size = 0; // chunk size is unknown with encrypted files
|
|
|
|
header_length = 33; // 25 + 8
|
|
|
|
// salt is first 8 bytes
|
|
|
|
if (control->major_version == 0) {
|
|
|
|
switch (control->minor_version) {
|
|
|
|
case 9:
|
|
|
|
ofs = 22 + control->comment_length;
|
|
|
|
break;
|
|
|
|
default:
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
next_chunk:
|
|
|
|
stream = 0;
|
|
|
|
stream_head[0] = 0;
|
|
|
|
stream_head[1] = stream_head[0] + header_length;
|
|
|
|
|
|
|
|
if (!ENCRYPT) {
|
|
|
|
chunk_total += chunk_size;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(chunk_byte && (chunk_byte > 8 || chunk_size <= 0))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (INFO) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, ++chunk);
|
|
|
|
print_verbose(, chunk_byte);
|
|
|
|
print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (!ENCRYPT)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, chunk_size);
|
2023-11-12 13:49:57 +00:00
|
|
|
else
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, control->enc_label);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
while (stream < NUM_STREAMS) {
|
|
|
|
int block = 1;
|
|
|
|
|
|
|
|
second_last = 0;
|
|
|
|
if (unlikely(lseek(fd_in, stream_head[stream] + ofs, SEEK_SET) == -1))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte))) return false;
|
|
|
|
|
|
|
|
if (ENCRYPT && ctype != CTYPE_NONE)
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, ctype);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (INFO) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, stream);
|
|
|
|
print_maxverbose(, stream_head[stream] + ofs);
|
|
|
|
print_verbose();
|
|
|
|
print_maxverbose();
|
|
|
|
print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
do {
|
|
|
|
i64 head_off;
|
|
|
|
|
|
|
|
if (unlikely(last_head && last_head <= second_last))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
second_last = last_head;
|
|
|
|
if (!ENCRYPT) {
|
|
|
|
if (unlikely(last_head + ofs > infile_size))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else {
|
|
|
|
if (unlikely(last_head + ofs + header_length > infile_size))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (unlikely((head_off = lseek(fd_in, last_head + ofs, SEEK_SET)) == -1))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte)))
|
|
|
|
return false;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(last_head < 0 || c_len < 0 || u_len < 0)) fatal();
|
|
|
|
if (INFO) print_verbose(, block);
|
2023-11-12 13:49:57 +00:00
|
|
|
if (ctype == CTYPE_NONE) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (INFO) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else if (ctype == CTYPE_LZ4) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (INFO) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else if (ctype == CTYPE_LZMA) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (INFO) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else if (ctype == CTYPE_ZSTD) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (INFO) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else if (ctype == CTYPE_ZPAQ) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (INFO) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else if (ctype == CTYPE_BZIP3) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (INFO) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, ctype);
|
2023-11-12 13:49:57 +00:00
|
|
|
if (save_ctype == 255)
|
|
|
|
save_ctype = ctype; /* need this for lzma when some chunks could have no compression
|
|
|
|
* and info will show rzip + none on info display if last chunk
|
|
|
|
* is not compressed. Adjust for all types in case it's used in
|
|
|
|
* the future */
|
|
|
|
utotal += u_len;
|
|
|
|
ctotal += c_len;
|
|
|
|
if (INFO) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, percentage(c_len, u_len), c_len, u_len);
|
|
|
|
print_maxverbose(, head_off, last_head);
|
|
|
|
print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
block++;
|
|
|
|
} while (last_head);
|
|
|
|
++stream;
|
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely((ofs = lseek(fd_in, c_len, SEEK_CUR)) == -1)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (ofs >= infile_size - *control->hash_len)
|
|
|
|
goto done;
|
|
|
|
else if (ENCRYPT)
|
|
|
|
if (ofs + header_length + *control->hash_len > infile_size) goto done;
|
|
|
|
|
|
|
|
/* Chunk byte entry */
|
|
|
|
/* remove checks for mrzip < 0.6 */
|
|
|
|
if (control->major_version == 0) {
|
|
|
|
if (!ENCRYPT) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(read(fd_in, &chunk_byte, 1) != 1)) fatal();
|
|
|
|
if (unlikely(chunk_byte < 1 || chunk_byte > 8)) fatal(, chunk_byte);
|
2023-11-12 13:49:57 +00:00
|
|
|
ofs++;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(read(fd_in, &control->eof, 1) != 1)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
chunk_size = le64toh(chunk_size);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(chunk_size < 0)) fatal(, chunk_size);
|
2023-11-12 13:49:57 +00:00
|
|
|
ofs += 1 + chunk_byte;
|
|
|
|
header_length = 1 + (chunk_byte * 3);
|
|
|
|
} else {
|
|
|
|
// ENCRYPTED
|
|
|
|
// no change to chunk_byte
|
|
|
|
ofs += 10;
|
|
|
|
// no change to header_length
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
goto next_chunk;
|
|
|
|
done:
|
|
|
|
/* compression ratio and bits per byte ratio */
|
|
|
|
cratio = (long double)expected_size / (long double)infile_size;
|
|
|
|
bpb = ((long double)infile_size / (long double)expected_size) * 8;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(ofs > infile_size)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (INFO) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
|
|
|
print_output(, infilecopy, control->major_version, control->minor_version,
|
|
|
|
ENCRYPT ? );
|
|
|
|
if (ENCRYPT) print_output(, control->enc_label);
|
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (control->comment_length) /* print comment */
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output(, control->comment);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (save_ctype == CTYPE_NONE)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
else if (save_ctype == CTYPE_LZ4)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
else if (save_ctype == CTYPE_LZMA) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else if (save_ctype == CTYPE_ZSTD)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
else if (save_ctype == CTYPE_ZPAQ) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (control->zpaq_level) // update magic with zpaq coding.
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output(, control->zpaq_level,
|
2023-11-12 13:49:57 +00:00
|
|
|
control->zpaq_bs, (1 << control->zpaq_bs));
|
|
|
|
else // early 0.8 or <0.8 file without zpaq coding in magic header
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
} else if (save_ctype == BZIP3_COMPRESS) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output(, control->bzip3_bs,
|
2023-11-12 13:49:57 +00:00
|
|
|
control->bzip3_block_size);
|
|
|
|
} else
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
/* only print stored compression level for versions that have it! */
|
|
|
|
if (control->compression_level)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output(,
|
2023-11-12 13:49:57 +00:00
|
|
|
control->rzip_compression_level, control->compression_level);
|
|
|
|
|
|
|
|
if (!expected_size)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output(,
|
|
|
|
ENCRYPT ? );
|
2023-11-12 13:49:57 +00:00
|
|
|
print_verbose(
|
2023-11-12 17:29:29 +00:00
|
|
|
|
|
|
|
);
|
2023-11-12 13:49:57 +00:00
|
|
|
/* If we can't show expected size, tailor output for it */
|
|
|
|
if (expected_size) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(,
|
2023-11-12 13:49:57 +00:00
|
|
|
percentage(utotal, expected_size), utotal, expected_size);
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, percentage(ctotal, utotal),
|
2023-11-12 13:49:57 +00:00
|
|
|
ctotal, utotal);
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(,
|
2023-11-12 13:49:57 +00:00
|
|
|
percentage(ctotal, expected_size), ctotal, expected_size);
|
|
|
|
} else {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose();
|
|
|
|
print_verbose(, percentage(ctotal, utotal),
|
2023-11-12 13:49:57 +00:00
|
|
|
ctotal, utotal);
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (expected_size) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output(, expected_size);
|
|
|
|
print_output(, infile_size);
|
|
|
|
print_output(, cratio, bpb);
|
2023-11-12 13:49:57 +00:00
|
|
|
} else {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
|
|
|
print_output(, infile_size);
|
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
} /* end if (INFO) */
|
|
|
|
|
|
|
|
if (HAS_HASH) {
|
|
|
|
uchar * hash_stored;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (INFO) {
|
|
|
|
hash_stored = calloc(*control->hash_len, 1);
|
|
|
|
if (unlikely(lseek(fd_in, -*control->hash_len, SEEK_END) == -1))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->hash_label);
|
2023-11-12 13:49:57 +00:00
|
|
|
if (unlikely(read(fd_in, hash_stored, *control->hash_len) != *control->hash_len))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->hash_label);
|
2023-11-12 13:49:57 +00:00
|
|
|
if (ENCRYPT)
|
|
|
|
if (unlikely(!lrz_decrypt(control, hash_stored, *control->hash_len, control->salt_pass, LRZ_VALIDATE)))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->hash_label);
|
|
|
|
print_output(, control->hash_label);
|
|
|
|
for (i = 0; i < *control->hash_len; i++) print_output(, hash_stored[i]);
|
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
dealloc(hash_stored);
|
|
|
|
}
|
|
|
|
} else {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (INFO) print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(close(fd_in))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
return true;
|
|
|
|
error:
|
|
|
|
dealloc(control->outfile);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
compress one file from the command line
|
|
|
|
*/
|
|
|
|
bool compress_file(rzip_control * control) {
|
|
|
|
const char *tmp, *tmpinfile; /* we're just using this as a proxy for control->infile.
|
|
|
|
* Spares a compiler warning
|
|
|
|
*/
|
|
|
|
int fd_in = -1, fd_out = -1, len = MAGIC_LEN + control->comment_length;
|
|
|
|
char * header;
|
|
|
|
|
|
|
|
header = calloc(len, 1);
|
|
|
|
|
|
|
|
control->flags |= FLAG_HASHED;
|
|
|
|
/* allocate result block for selected hash */
|
|
|
|
control->hash_resblock = calloc(*control->hash_len, 1);
|
|
|
|
|
|
|
|
if (ENCRYPT) { /* AES 128 now default */
|
|
|
|
if (unlikely(!get_hash(control, 1))) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!STDIN) {
|
|
|
|
fd_in = open(control->infile, O_RDONLY);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fd_in == -1)) fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
} else
|
|
|
|
fd_in = fileno(control->inFILE);
|
|
|
|
|
|
|
|
if (!STDOUT) {
|
|
|
|
if (control->outname) {
|
|
|
|
control->outfile = strdup(control->outname);
|
|
|
|
} else {
|
|
|
|
/* default output name from control->infile
|
|
|
|
* test if outdir specified. If so, strip path from filename of
|
|
|
|
* control->infile
|
|
|
|
*/
|
|
|
|
if (control->outdir && (tmp = strrchr(control->infile, '/')))
|
|
|
|
tmpinfile = tmp + 1;
|
|
|
|
else
|
|
|
|
tmpinfile = control->infile;
|
|
|
|
|
|
|
|
control->outfile = malloc((control->outdir == NULL ? 0 : strlen(control->outdir)) + strlen(tmpinfile) +
|
|
|
|
strlen(control->suffix) + 1);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!control->outfile)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (control->outdir) { /* prepend control->outdir */
|
|
|
|
strcpy(control->outfile, control->outdir);
|
|
|
|
strcat(control->outfile, tmpinfile);
|
|
|
|
} else
|
|
|
|
strcpy(control->outfile, tmpinfile);
|
|
|
|
strcat(control->outfile, control->suffix);
|
2023-11-12 17:29:29 +00:00
|
|
|
// print_progress(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
// Not needed since printed at end of decompression
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(control->infile, control->outfile))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
fd_out = open(control->outfile, O_RDWR | O_CREAT | O_EXCL, 0666);
|
|
|
|
if (FORCE_REPLACE && (-1 == fd_out) && (EEXIST == errno)) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(unlink(control->outfile))) fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
fd_out = open(control->outfile, O_RDWR | O_CREAT | O_EXCL, 0666);
|
|
|
|
}
|
|
|
|
if (unlikely(fd_out == -1)) {
|
|
|
|
/* We must ensure we don't delete a file that already
|
|
|
|
* exists just because we tried to create a new one */
|
|
|
|
control->flags |= FLAG_KEEP_BROKEN;
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
control->fd_out = fd_out;
|
|
|
|
if (!STDIN) {
|
|
|
|
if (unlikely(!preserve_perms(control, fd_in, fd_out))) goto error;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (unlikely(!open_tmpoutbuf(control))) goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write zeroes to header at beginning of file */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!STDOUT && write(fd_out, header, len) != len)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
rzip_fd(control, fd_in, fd_out);
|
|
|
|
|
|
|
|
/* need to write magic after compression for expected size */
|
|
|
|
if (!STDOUT) {
|
|
|
|
if (unlikely(!write_magic(control))) goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ENCRYPT) release_hashes(control);
|
|
|
|
|
|
|
|
if (unlikely(!STDIN && !STDOUT && !preserve_times(control, fd_in))) {
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (unlikely(close(fd_in))) {
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
fd_in = -1;
|
|
|
|
goto error;
|
|
|
|
}
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!STDOUT && close(fd_out))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (TMP_OUTBUF) close_tmpoutbuf(control);
|
|
|
|
|
|
|
|
if (!KEEP_FILES && !STDIN) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(unlink(control->infile))) fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dealloc(control->outfile);
|
|
|
|
dealloc(control->hash_resblock);
|
|
|
|
dealloc(header);
|
|
|
|
return true;
|
|
|
|
error:
|
|
|
|
dealloc(header);
|
|
|
|
if (!STDIN && (fd_in > 0)) close(fd_in);
|
|
|
|
if ((!STDOUT) && (fd_out > 0)) close(fd_out);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
decompress one file from the command line
|
|
|
|
*/
|
|
|
|
bool decompress_file(rzip_control * control) {
|
|
|
|
char *tmp, *tmpoutfile, *infilecopy = NULL;
|
|
|
|
int fd_in, fd_out = -1, fd_hist = -1;
|
|
|
|
i64 expected_size = 0, free_space;
|
|
|
|
struct statvfs fbuf;
|
|
|
|
|
|
|
|
if (!STDIN) {
|
|
|
|
struct stat fdin_stat;
|
|
|
|
infilecopy = strdupa(control->infile);
|
|
|
|
if (unlikely(stat(infilecopy, &fdin_stat)))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->infile);
|
2023-11-12 13:49:57 +00:00
|
|
|
else if (unlikely(!S_ISREG(fdin_stat.st_mode)))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
/* regardless, infilecopy has the input filename */
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!STDOUT && !TEST_ONLY) {
|
|
|
|
/* if output name already set, use it */
|
|
|
|
if (control->outname)
|
|
|
|
control->outfile = strdup(control->outname);
|
|
|
|
else {
|
|
|
|
/* default output name from infilecopy
|
|
|
|
* test if outdir specified. If so, strip path from filename of
|
|
|
|
* infilecopy, then remove suffix.
|
|
|
|
*/
|
|
|
|
if (control->outdir && (tmp = strrchr(infilecopy, '/')))
|
|
|
|
tmpoutfile = strdupa(tmp + 1);
|
|
|
|
else
|
|
|
|
tmpoutfile = strdupa(infilecopy);
|
|
|
|
|
|
|
|
/* remove suffix to make outfile name */
|
|
|
|
if ((tmp = strrchr(tmpoutfile, '.')) && !strcmp(tmp, control->suffix)) *tmp = '\0';
|
|
|
|
|
|
|
|
control->outfile = malloc((control->outdir == NULL ? 0 : strlen(control->outdir)) + strlen(tmpoutfile) + 1);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(!control->outfile)) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (control->outdir) { /* prepend control->outdir */
|
|
|
|
strcpy(control->outfile, control->outdir);
|
|
|
|
strcat(control->outfile, tmpoutfile);
|
|
|
|
} else
|
|
|
|
strcpy(control->outfile, tmpoutfile);
|
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (!STDOUT) print_progress(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (unlikely(!strcmp(control->outfile, infilecopy))) {
|
|
|
|
control->flags |= FLAG_TEST_ONLY; // stop and no more decompres or deleting files.
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (STDIN) {
|
|
|
|
fd_in = open_tmpinfile(control);
|
|
|
|
read_tmpinmagic(control, fd_in);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (ENCRYPT) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
expected_size = control->st_size;
|
|
|
|
if (unlikely(!open_tmpinbuf(control))) return false;
|
|
|
|
} else {
|
|
|
|
fd_in = open(infilecopy, O_RDONLY);
|
|
|
|
if (unlikely(fd_in == -1)) {
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, infilecopy);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
control->fd_in = fd_in;
|
|
|
|
|
|
|
|
if (!(TEST_ONLY || STDOUT)) {
|
|
|
|
fd_out = open(control->outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
|
|
|
|
if (FORCE_REPLACE && (-1 == fd_out) && (EEXIST == errno)) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(unlink(control->outfile))) fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
fd_out = open(control->outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
|
|
|
|
}
|
|
|
|
if (unlikely(fd_out == -1)) {
|
|
|
|
/* We must ensure we don't delete a file that already
|
|
|
|
* exists just because we tried to create a new one */
|
|
|
|
control->flags |= FLAG_KEEP_BROKEN;
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
fd_hist = open(control->outfile, O_RDONLY);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fd_hist == -1)) fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
/* Can't copy permissions from STDIN */
|
|
|
|
if (!STDIN)
|
|
|
|
if (unlikely(!preserve_perms(control, fd_in, fd_out))) return false;
|
|
|
|
} else {
|
|
|
|
fd_out = open_tmpoutfile(control);
|
|
|
|
if (fd_out == -1) {
|
|
|
|
fd_hist = -1;
|
|
|
|
} else {
|
|
|
|
fd_hist = open(control->outfile, O_RDONLY);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fd_hist == -1)) fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
/* Unlink temporary file as soon as possible */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(unlink(control->outfile))) fatal(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for STDOUT removed. In memory compression speedup. No memory leak.
|
|
|
|
if (unlikely(!open_tmpoutbuf(control))) return false;
|
|
|
|
|
|
|
|
if (!STDIN) {
|
|
|
|
if (unlikely(!read_magic(control, fd_in, &expected_size))) return false;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(expected_size < 0)) fatal(, expected_size);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!STDOUT) {
|
|
|
|
/* Check if there's enough free space on the device chosen to fit the
|
|
|
|
* decompressed or test file. */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(fstatvfs(fd_out, &fbuf))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
free_space = (i64)fbuf.f_bsize * (i64)fbuf.f_bavail;
|
|
|
|
if (free_space < expected_size) {
|
|
|
|
if (FORCE_REPLACE && !TEST_ONLY)
|
|
|
|
print_err(
|
2023-11-12 17:29:29 +00:00
|
|
|
|
|
|
|
);
|
2023-11-12 13:49:57 +00:00
|
|
|
else
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal( PRId64
|
|
|
|
,
|
|
|
|
TEST_ONLY ? , expected_size, free_space,
|
|
|
|
TEST_ONLY ? );
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
control->fd_out = fd_out;
|
|
|
|
control->fd_hist = fd_hist;
|
|
|
|
|
|
|
|
show_version(control);
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (NO_HASH) print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (HAS_HASH)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose(, control->hash_label);
|
2023-11-12 13:49:57 +00:00
|
|
|
else
|
2023-11-12 17:29:29 +00:00
|
|
|
print_verbose();
|
|
|
|
print_verbose();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
control->hash_resblock = calloc(*control->hash_len, 1);
|
|
|
|
|
|
|
|
if (ENCRYPT && !control->salt_pass_len) { // Only get password if needed
|
|
|
|
if (unlikely(!get_hash(control, 0))) return false;
|
2023-11-12 17:29:29 +00:00
|
|
|
print_maxverbose(, control->encloops);
|
|
|
|
if (!INFO) print_verbose(, control->enc_label);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// vailidate file on decompression or test
|
|
|
|
if (STDIN)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_err();
|
2023-11-12 13:49:57 +00:00
|
|
|
else {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_progress();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (unlikely((get_fileinfo(control)) == false))
|
2023-11-12 17:29:29 +00:00
|
|
|
fatal();
|
|
|
|
print_progress();
|
|
|
|
if (!VERBOSE) print_progress(); // output LF to prevent overwriing decompression output
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
show_version(control); // show version here to preserve output formatting
|
2023-11-12 17:29:29 +00:00
|
|
|
print_progress();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (unlikely(runzip_fd(control, fd_in, fd_out, fd_hist, expected_size) < 0)) {
|
|
|
|
clear_rulist(control);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We can now safely delete sinfo and pthread data of all threads
|
|
|
|
* created. */
|
|
|
|
clear_rulist(control);
|
|
|
|
|
|
|
|
if (STDOUT && !TMP_OUTBUF) {
|
|
|
|
if (unlikely(!dump_tmpoutfile(control, fd_out))) return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if we get here, no fatal_return(( errors during decompression */
|
2023-11-12 17:29:29 +00:00
|
|
|
print_progress();
|
|
|
|
if (!(STDOUT || TEST_ONLY)) print_progress(, control->outfile);
|
2023-11-12 13:49:57 +00:00
|
|
|
if (!expected_size) expected_size = control->st_size;
|
|
|
|
if (!ENCRYPT)
|
2023-11-12 17:29:29 +00:00
|
|
|
print_progress(, expected_size);
|
2023-11-12 13:49:57 +00:00
|
|
|
else
|
2023-11-12 17:29:29 +00:00
|
|
|
print_progress();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (TMP_OUTBUF) close_tmpoutbuf(control);
|
|
|
|
|
|
|
|
if (fd_out > 0)
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(close(fd_hist) || close(fd_out))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (unlikely(!STDIN && !STDOUT && !TEST_ONLY && !preserve_times(control, fd_in))) return false;
|
|
|
|
|
|
|
|
if (!STDIN) close(fd_in);
|
|
|
|
|
|
|
|
if (!KEEP_FILES && !STDIN)
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(unlink(control->infile))) fatal(, infilecopy);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
if (ENCRYPT) release_hashes(control);
|
|
|
|
|
|
|
|
dealloc(control->outfile);
|
|
|
|
dealloc(control->hash_resblock);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool initialise_control(rzip_control * control) {
|
|
|
|
time_t now_t, tdiff;
|
2023-11-12 17:29:29 +00:00
|
|
|
char localeptr[] = , *eptr; /* for environment. OR Default to /tmp if none set */
|
2023-11-12 13:49:57 +00:00
|
|
|
size_t len;
|
|
|
|
|
|
|
|
memset(control, 0, sizeof(rzip_control));
|
2023-11-12 17:29:29 +00:00
|
|
|
control->locale = ; /* empty string for default locale */
|
2023-11-12 13:49:57 +00:00
|
|
|
control->msgout = stderr;
|
|
|
|
control->msgerr = stderr;
|
|
|
|
register_outputfile(control, control->msgout);
|
|
|
|
control->flags = FLAG_SHOW_PROGRESS | FLAG_KEEP_FILES | FLAG_THRESHOLD;
|
|
|
|
control->filter_flag = 0; /* filter flag. Default to none */
|
|
|
|
control->compression_level = 7; /* compression level default */
|
|
|
|
control->rzip_compression_level =
|
|
|
|
0; /* rzip compression level default will equal compression level unless explicitly set */
|
|
|
|
control->ramsize = get_ram(control); /* if something goes wrong, exit from get_ram */
|
|
|
|
control->threshold = 100; /* default for no threshold limiting */
|
|
|
|
/* for testing single CPU */
|
|
|
|
control->threads = PROCESSORS; /* get CPUs for LZMA */
|
|
|
|
control->page_size = PAGE_SIZE;
|
|
|
|
control->nice_val = 19;
|
|
|
|
|
|
|
|
/* The first 5 bytes of the salt is the time in seconds.
|
|
|
|
* The next 2 bytes encode how many times to hash the password.
|
|
|
|
* The last 9 bytes are random data, making 16 bytes of salt */
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely((now_t = time(NULL)) == ((time_t)-1))) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (unlikely(now_t < T_ZERO)) {
|
2023-11-12 17:29:29 +00:00
|
|
|
print_output();
|
2023-11-12 13:49:57 +00:00
|
|
|
now_t = T_ZERO;
|
|
|
|
}
|
|
|
|
/* Workaround for CPUs no longer keeping up with Moore's law!
|
|
|
|
* This way we keep the magic header format unchanged. */
|
|
|
|
tdiff = (now_t - T_ZERO) / 4;
|
|
|
|
now_t = T_ZERO + tdiff;
|
|
|
|
control->secs = now_t;
|
|
|
|
control->encloops = nloops(control->secs, control->salt, control->salt + 1);
|
|
|
|
gcry_create_nonce(control->salt + 2, 6);
|
|
|
|
|
|
|
|
/* Get Temp Dir. Try variations on canonical unix environment variable */
|
2023-11-12 17:29:29 +00:00
|
|
|
eptr = getenv();
|
|
|
|
if (!eptr) eptr = getenv();
|
|
|
|
if (!eptr) eptr = getenv();
|
|
|
|
if (!eptr) eptr = getenv();
|
2023-11-12 13:49:57 +00:00
|
|
|
if (!eptr) eptr = localeptr;
|
|
|
|
len = strlen(eptr);
|
|
|
|
|
|
|
|
control->tmpdir = malloc(len + 2);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (control->tmpdir == NULL) fatal();
|
2023-11-12 13:49:57 +00:00
|
|
|
strcpy(control->tmpdir, eptr);
|
|
|
|
if (control->tmpdir[len - 1] != '/') {
|
|
|
|
control->tmpdir[len] = '/'; /* need a trailing slash */
|
|
|
|
control->tmpdir[len + 1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* just in case, set pointers for hash and encryptions */
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef MRZIP_UTIL_H
|
|
|
|
#define MRZIP_UTIL_H
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <semaphore.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdnoreturn.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
#include
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
void register_infile(rzip_control * control, const char * name, char delete);
|
|
|
|
void register_outfile(rzip_control * control, const char * name, char delete);
|
|
|
|
void unlink_files(rzip_control * control);
|
|
|
|
void register_outputfile(rzip_control * control, FILE * f);
|
|
|
|
noreturn void fatal_exit(rzip_control * control);
|
|
|
|
void setup_overhead(rzip_control * control);
|
|
|
|
void setup_ram(rzip_control * control);
|
|
|
|
void round_to_page(i64 * size);
|
|
|
|
size_t round_up_page(rzip_control * control, size_t len);
|
|
|
|
bool read_config(rzip_control * control);
|
|
|
|
void lrz_stretch(rzip_control * control);
|
|
|
|
bool lrz_crypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt, int encrypt);
|
|
|
|
bool decrypt_header(rzip_control * control, uchar * head, uchar * c_type, i64 * c_len, i64 * u_len, i64 * last_head,
|
|
|
|
int decompress_type);
|
|
|
|
|
|
|
|
static inline noreturn void fatal(const rzip_control * control, unsigned int line, const char * file, const char * func,
|
|
|
|
const char * format, ...) {
|
|
|
|
va_list ap;
|
|
|
|
/* mrzip library callback code removed */
|
|
|
|
va_start(ap, format);
|
|
|
|
vfprintf(stderr, format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
fatal_exit((rzip_control *)control);
|
|
|
|
}
|
|
|
|
#ifdef fatal
|
|
|
|
#undef fatal
|
|
|
|
#endif
|
|
|
|
#define fatal(...) fatal(control, __LINE__, __FILE__, __func__, __VA_ARGS__)
|
|
|
|
|
|
|
|
static inline bool lrz_encrypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt) {
|
|
|
|
return lrz_crypt(control, buf, len, salt, LRZ_ENCRYPT);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool lrz_decrypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt,
|
|
|
|
int dec_or_validate) {
|
|
|
|
return lrz_crypt(control, buf, len, salt, dec_or_validate);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ck specific wrappers for true unnamed semaphore usage on platforms
|
|
|
|
* that support them and for apple which does not. We use a single byte across
|
|
|
|
* a pipe to emulate semaphore behaviour there. */
|
|
|
|
#ifdef __APPLE__
|
|
|
|
static inline void cksem_init(const rzip_control * control, cksem_t * cksem) {
|
|
|
|
int flags, fd, i;
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (pipe(cksem->pipefd) == -1) fatal(, errno);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
|
|
|
/* Make the pipes FD_CLOEXEC to allow them to close should we call
|
|
|
|
* execv on restart. */
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
fd = cksem->pipefd[i];
|
|
|
|
flags = fcntl(fd, F_GETFD, 0);
|
|
|
|
flags |= FD_CLOEXEC;
|
2023-11-12 17:29:29 +00:00
|
|
|
if (fcntl(fd, F_SETFD, flags) == -1) fatal(, errno);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void cksem_post(const rzip_control * control, cksem_t * cksem) {
|
|
|
|
const char buf = 1;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = write(cksem->pipefd[1], &buf, 1);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(ret == 0)) fatal(, errno);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) {
|
|
|
|
char buf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = read(cksem->pipefd[0], &buf, 1);
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(ret == 0)) fatal(, errno);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void cksem_init(const rzip_control * control, cksem_t * cksem) {
|
|
|
|
int ret;
|
2023-11-12 17:29:29 +00:00
|
|
|
if ((ret = sem_init(cksem, 0, 0))) fatal(, ret, errno);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void cksem_post(const rzip_control * control, cksem_t * cksem) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(sem_post(cksem))) fatal(, errno, cksem);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) {
|
2023-11-12 17:29:29 +00:00
|
|
|
if (unlikely(sem_wait(cksem))) fatal(, errno, cksem);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Module specific Functions Declaration
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage);
|
|
|
|
static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount);
|
|
|
|
static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer);
|
|
|
|
|
|
|
|
#if defined(RAUDIO_STANDALONE)
|
|
|
|
static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension
|
|
|
|
static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png)
|
|
|
|
|
|
|
|
static unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read)
|
|
|
|
static bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write)
|
|
|
|
static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// AudioBuffer management functions declaration
|
|
|
|
// NOTE: Those functions are not exposed by raylib... for the moment
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage);
|
|
|
|
void UnloadAudioBuffer(AudioBuffer *buffer);
|
|
|
|
|
|
|
|
bool IsAudioBufferPlaying(AudioBuffer *buffer);
|
|
|
|
void PlayAudioBuffer(AudioBuffer *buffer);
|
|
|
|
void StopAudioBuffer(AudioBuffer *buffer);
|
|
|
|
void PauseAudioBuffer(AudioBuffer *buffer);
|
|
|
|
void ResumeAudioBuffer(AudioBuffer *buffer);
|
|
|
|
void SetAudioBufferVolume(AudioBuffer *buffer, float volume);
|
|
|
|
void SetAudioBufferPitch(AudioBuffer *buffer, float pitch);
|
|
|
|
void SetAudioBufferPan(AudioBuffer *buffer, float pan);
|
|
|
|
void TrackAudioBuffer(AudioBuffer *buffer);
|
|
|
|
void UntrackAudioBuffer(AudioBuffer *buffer);
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Module Functions Definition - Audio Device initialization and Closing
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Initialize audio device
|
|
|
|
void InitAudioDevice(void)
|
|
|
|
{
|
|
|
|
// Init audio context
|
|
|
|
ma_context_config ctxConfig = ma_context_config_init();
|
|
|
|
ma_log_callback_init(OnLog, NULL);
|
|
|
|
|
|
|
|
ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context);
|
|
|
|
if (result != MA_SUCCESS)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init audio device
|
|
|
|
// NOTE: Using the default device. Format is floating point because it simplifies mixing.
|
|
|
|
ma_device_config config = ma_device_config_init(ma_device_type_playback);
|
|
|
|
config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device.
|
|
|
|
config.playback.format = AUDIO_DEVICE_FORMAT;
|
|
|
|
config.playback.channels = AUDIO_DEVICE_CHANNELS;
|
|
|
|
config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.System.device.
|
|
|
|
config.capture.format = ma_format_s16;
|
|
|
|
config.capture.channels = 1;
|
|
|
|
config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE;
|
|
|
|
config.dataCallback = OnSendAudioDataToDevice;
|
|
|
|
config.pUserData = NULL;
|
|
|
|
|
|
|
|
result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device);
|
|
|
|
if (result != MA_SUCCESS)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
ma_context_uninit(&AUDIO.System.context);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mixing happens on a separate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may
|
|
|
|
// want to look at something a bit smarter later on to keep everything real-time, if that's necessary.
|
|
|
|
if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
ma_device_uninit(&AUDIO.System.device);
|
|
|
|
ma_context_uninit(&AUDIO.System.context);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running
|
|
|
|
// while there's at least one sound being played.
|
|
|
|
result = ma_device_start(&AUDIO.System.device);
|
|
|
|
if (result != MA_SUCCESS)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
ma_device_uninit(&AUDIO.System.device);
|
|
|
|
ma_context_uninit(&AUDIO.System.context);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AUDIO.System.isReady = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the audio device for all contexts
|
|
|
|
void CloseAudioDevice(void)
|
|
|
|
{
|
|
|
|
if (AUDIO.System.isReady)
|
|
|
|
{
|
|
|
|
ma_mutex_uninit(&AUDIO.System.lock);
|
|
|
|
ma_device_uninit(&AUDIO.System.device);
|
|
|
|
ma_context_uninit(&AUDIO.System.context);
|
|
|
|
|
|
|
|
AUDIO.System.isReady = false;
|
|
|
|
RL_FREE(AUDIO.System.pcmBuffer);
|
|
|
|
AUDIO.System.pcmBuffer = NULL;
|
|
|
|
AUDIO.System.pcmBufferSize = 0;
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_INFO, );
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
2023-11-12 17:29:29 +00:00
|
|
|
else TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if device has been initialized successfully
|
|
|
|
bool IsAudioDeviceReady(void)
|
|
|
|
{
|
|
|
|
return AUDIO.System.isReady;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set master volume (listener)
|
|
|
|
void SetMasterVolume(float volume)
|
|
|
|
{
|
|
|
|
ma_device_set_master_volume(&AUDIO.System.device, volume);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get master volume (listener)
|
|
|
|
float GetMasterVolume(void)
|
|
|
|
{
|
|
|
|
float volume = 0.0f;
|
|
|
|
ma_device_get_master_volume(&AUDIO.System.device, &volume);
|
|
|
|
return volume;
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Module Functions Definition - Audio Buffer management
|
|
|
|
//----------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
// Initialize a new audio buffer (filled with silence)
|
|
|
|
AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage)
|
|
|
|
{
|
|
|
|
AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer));
|
|
|
|
|
|
|
|
if (audioBuffer == NULL)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1);
|
|
|
|
|
|
|
|
// Audio data runs through a format converter
|
|
|
|
ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate);
|
|
|
|
converterConfig.allowDynamicSampleRate = true;
|
|
|
|
|
|
|
|
ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter);
|
|
|
|
|
|
|
|
if (result != MA_SUCCESS)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
RL_FREE(audioBuffer);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init audio buffer values
|
|
|
|
audioBuffer->volume = 1.0f;
|
|
|
|
audioBuffer->pitch = 1.0f;
|
|
|
|
audioBuffer->pan = 0.5f;
|
|
|
|
|
|
|
|
audioBuffer->callback = NULL;
|
|
|
|
audioBuffer->processor = NULL;
|
|
|
|
|
|
|
|
audioBuffer->playing = false;
|
|
|
|
audioBuffer->paused = false;
|
|
|
|
audioBuffer->looping = false;
|
|
|
|
|
|
|
|
audioBuffer->usage = usage;
|
|
|
|
audioBuffer->frameCursorPos = 0;
|
|
|
|
audioBuffer->sizeInFrames = sizeInFrames;
|
|
|
|
|
|
|
|
// Buffers should be marked as processed by default so that a call to
|
|
|
|
// UpdateAudioStream() immediately after initialization works correctly
|
|
|
|
audioBuffer->isSubBufferProcessed[0] = true;
|
|
|
|
audioBuffer->isSubBufferProcessed[1] = true;
|
|
|
|
|
|
|
|
// Track audio buffer to linked list next position
|
|
|
|
TrackAudioBuffer(audioBuffer);
|
|
|
|
|
|
|
|
return audioBuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete an audio buffer
|
|
|
|
void UnloadAudioBuffer(AudioBuffer *buffer)
|
|
|
|
{
|
|
|
|
if (buffer != NULL)
|
|
|
|
{
|
|
|
|
ma_data_converter_uninit(&buffer->converter, NULL);
|
|
|
|
UntrackAudioBuffer(buffer);
|
|
|
|
RL_FREE(buffer->data);
|
|
|
|
RL_FREE(buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if an audio buffer is playing
|
|
|
|
bool IsAudioBufferPlaying(AudioBuffer *buffer)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
if (buffer != NULL) result = (buffer->playing && !buffer->paused);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Clone sound from existing sound data, clone does not own wave data
|
|
|
|
// NOTE: Wave data must be unallocated manually and will be shared across all clones
|
|
|
|
Sound LoadSoundAlias(Sound source)
|
|
|
|
{
|
|
|
|
Sound sound = { 0 };
|
|
|
|
|
|
|
|
if (source.stream.buffer->data != NULL)
|
|
|
|
{
|
|
|
|
AudioBuffer* audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC);
|
|
|
|
if (audioBuffer == NULL)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
return sound; // early return to avoid dereferencing the audioBuffer null pointer
|
|
|
|
}
|
|
|
|
audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames;
|
|
|
|
audioBuffer->volume = source.stream.buffer->volume;
|
|
|
|
audioBuffer->data = source.stream.buffer->data;
|
|
|
|
|
|
|
|
sound.frameCount = source.frameCount;
|
|
|
|
sound.stream.sampleRate = AUDIO.System.device.sampleRate;
|
|
|
|
sound.stream.sampleSize = 32;
|
|
|
|
sound.stream.channels = AUDIO_DEVICE_CHANNELS;
|
|
|
|
sound.stream.buffer = audioBuffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return sound;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Checks if a sound is ready
|
|
|
|
bool IsSoundReady(Sound sound)
|
|
|
|
{
|
|
|
|
return ((sound.frameCount > 0) && // Validate frame count
|
|
|
|
(sound.stream.buffer != NULL) && // Validate stream buffer
|
|
|
|
(sound.stream.sampleRate > 0) && // Validate sample rate is supported
|
|
|
|
(sound.stream.sampleSize > 0) && // Validate sample size is supported
|
|
|
|
(sound.stream.channels > 0)); // Validate number of channels supported
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unload wave data
|
|
|
|
void UnloadWave(Wave wave)
|
|
|
|
{
|
|
|
|
RL_FREE(wave.data);
|
2023-11-12 17:29:29 +00:00
|
|
|
//TRACELOG(LOG_INFO, );
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unload sound
|
|
|
|
void UnloadSound(Sound sound)
|
|
|
|
{
|
|
|
|
UnloadAudioBuffer(sound.stream.buffer);
|
2023-11-12 17:29:29 +00:00
|
|
|
//TRACELOG(LOG_INFO, );
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void UnloadSoundAlias(Sound alias)
|
|
|
|
{
|
|
|
|
// untrack and unload just the sound buffer, not the sample data, it is shared with the source for the alias
|
|
|
|
if (alias.stream.buffer != NULL)
|
|
|
|
{
|
|
|
|
ma_data_converter_uninit(&alias.stream.buffer->converter, NULL);
|
|
|
|
UntrackAudioBuffer(alias.stream.buffer);
|
|
|
|
RL_FREE(alias.stream.buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Play a sound
|
|
|
|
void PlaySound(Sound sound)
|
|
|
|
{
|
|
|
|
PlayAudioBuffer(sound.stream.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pause a sound
|
|
|
|
void PauseSound(Sound sound)
|
|
|
|
{
|
|
|
|
PauseAudioBuffer(sound.stream.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resume a paused sound
|
|
|
|
void ResumeSound(Sound sound)
|
|
|
|
{
|
|
|
|
ResumeAudioBuffer(sound.stream.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop reproducing a sound
|
|
|
|
void StopSound(Sound sound)
|
|
|
|
{
|
|
|
|
StopAudioBuffer(sound.stream.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if a sound is playing
|
|
|
|
bool IsSoundPlaying(Sound sound)
|
|
|
|
{
|
|
|
|
return IsAudioBufferPlaying(sound.stream.buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set volume for a sound
|
|
|
|
void SetSoundVolume(Sound sound, float volume)
|
|
|
|
{
|
|
|
|
SetAudioBufferVolume(sound.stream.buffer, volume);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set pitch for a sound
|
|
|
|
void SetSoundPitch(Sound sound, float pitch)
|
|
|
|
{
|
|
|
|
SetAudioBufferPitch(sound.stream.buffer, pitch);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set pan for a sound
|
|
|
|
void SetSoundPan(Sound sound, float pan)
|
|
|
|
{
|
|
|
|
SetAudioBufferPan(sound.stream.buffer, pan);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert wave data to desired format
|
|
|
|
void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels)
|
|
|
|
{
|
|
|
|
ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32));
|
|
|
|
ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32));
|
|
|
|
|
|
|
|
ma_uint32 frameCountIn = wave->frameCount;
|
|
|
|
ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate);
|
|
|
|
|
|
|
|
if (frameCount == 0)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *data = RL_MALLOC(frameCount*channels*(sampleSize/8));
|
|
|
|
|
|
|
|
frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate);
|
|
|
|
if (frameCount == 0)
|
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_WARNING, );
|
2023-11-12 13:49:57 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wave->frameCount = frameCount;
|
|
|
|
wave->sampleSize = sampleSize;
|
|
|
|
wave->sampleRate = sampleRate;
|
|
|
|
wave->channels = channels;
|
|
|
|
|
|
|
|
RL_FREE(wave->data);
|
|
|
|
wave->data = data;
|
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Seek music to a certain position (in seconds)
|
|
|
|
void SeekMusicStream(Music music, float position)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
// Seeking is not supported in module formats
|
|
|
|
if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) return;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
unsigned int positionInFrames = (unsigned int)(position*music.stream.sampleRate);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
switch (music.ctxType)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
#if defined(SUPPORT_FILEFORMAT_WAV)
|
|
|
|
case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, positionInFrames); break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_OGG)
|
|
|
|
case MUSIC_AUDIO_OGG: stb_vorbis_seek_frame((stb_vorbis *)music.ctxData, positionInFrames); break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_MP3)
|
|
|
|
case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_QOA)
|
|
|
|
case MUSIC_AUDIO_QOA:
|
|
|
|
{
|
|
|
|
int qoaFrame = positionInFrames/QOA_FRAME_LEN;
|
|
|
|
qoaplay_seek_frame((qoaplay_desc *)music.ctxData, qoaFrame); // Seeks to QOA frame, not PCM frame
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// We need to compute QOA frame number and update positionInFrames
|
|
|
|
positionInFrames = ((qoaplay_desc *)music.ctxData)->sample_position;
|
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_FLAC)
|
|
|
|
case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break;
|
|
|
|
#endif
|
|
|
|
default: break;
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
music.stream.buffer->framesProcessed = positionInFrames;
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Update (re-fill) music buffers if data already processed
|
|
|
|
void UpdateMusicStream(Music music)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
if (music.stream.buffer == NULL) return;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// On first call of this function we lazily pre-allocated a temp buffer to read audio files/memory data in
|
|
|
|
int frameSize = music.stream.channels*music.stream.sampleSize/8;
|
|
|
|
unsigned int pcmSize = subBufferSizeInFrames*frameSize;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (AUDIO.System.pcmBufferSize < pcmSize)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
RL_FREE(AUDIO.System.pcmBuffer);
|
|
|
|
AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize);
|
|
|
|
AUDIO.System.pcmBufferSize = pcmSize;
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Check both sub-buffers to check if they require refilling
|
|
|
|
for (int i = 0; i < 2; i++)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
if ((music.stream.buffer != NULL) && !music.stream.buffer->isSubBufferProcessed[i]) continue; // No refilling required, move to next sub-buffer
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed
|
|
|
|
unsigned int framesToStream = 0; // Total frames to be streamed
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames;
|
|
|
|
else framesToStream = framesLeft;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
int frameCountStillNeeded = framesToStream;
|
|
|
|
int frameCountReadTotal = 0;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
switch (music.ctxType)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
#if defined(SUPPORT_FILEFORMAT_WAV)
|
|
|
|
case MUSIC_AUDIO_WAV:
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
if (music.stream.sampleSize == 16)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
while (true)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
int frameCountRead = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
|
|
|
|
frameCountReadTotal += frameCountRead;
|
|
|
|
frameCountStillNeeded -= frameCountRead;
|
|
|
|
if (frameCountStillNeeded == 0) break;
|
|
|
|
else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
2023-11-12 17:29:29 +00:00
|
|
|
}
|
|
|
|
else if (music.stream.sampleSize == 32)
|
|
|
|
{
|
|
|
|
while (true)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
int frameCountRead = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
|
|
|
|
frameCountReadTotal += frameCountRead;
|
|
|
|
frameCountStillNeeded -= frameCountRead;
|
|
|
|
if (frameCountStillNeeded == 0) break;
|
|
|
|
else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-12 17:29:29 +00:00
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_OGG)
|
|
|
|
case MUSIC_AUDIO_OGG:
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
int frameCountRead = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded*music.stream.channels);
|
|
|
|
frameCountReadTotal += frameCountRead;
|
|
|
|
frameCountStillNeeded -= frameCountRead;
|
|
|
|
if (frameCountStillNeeded == 0) break;
|
|
|
|
else stb_vorbis_seek_start((stb_vorbis *)music.ctxData);
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_MP3)
|
|
|
|
case MUSIC_AUDIO_MP3:
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
|
|
|
|
frameCountReadTotal += frameCountRead;
|
|
|
|
frameCountStillNeeded -= frameCountRead;
|
|
|
|
if (frameCountStillNeeded == 0) break;
|
|
|
|
else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData);
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_QOA)
|
|
|
|
case MUSIC_AUDIO_QOA:
|
|
|
|
{
|
|
|
|
unsigned int frameCountRead = qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream);
|
|
|
|
frameCountReadTotal += frameCountRead;
|
|
|
|
/*
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
int frameCountRead = (int)qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded);
|
|
|
|
frameCountReadTotal += frameCountRead;
|
|
|
|
frameCountStillNeeded -= frameCountRead;
|
|
|
|
if (frameCountStillNeeded == 0) break;
|
|
|
|
else qoaplay_rewind((qoaplay_desc *)music.ctxData);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_FLAC)
|
|
|
|
case MUSIC_AUDIO_FLAC:
|
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
int frameCountRead = (int)drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
|
|
|
|
frameCountReadTotal += frameCountRead;
|
|
|
|
frameCountStillNeeded -= frameCountRead;
|
|
|
|
if (frameCountStillNeeded == 0) break;
|
|
|
|
else drflac__seek_to_first_frame((drflac *)music.ctxData);
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_XM)
|
|
|
|
case MUSIC_MODULE_XM:
|
|
|
|
{
|
|
|
|
// NOTE: Internally we consider 2 channels generation, so sampleCount/2
|
|
|
|
if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream);
|
|
|
|
else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream);
|
|
|
|
else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream);
|
|
|
|
//jar_xm_reset((jar_xm_context_t *)music.ctxData);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
#if defined(SUPPORT_FILEFORMAT_MOD)
|
|
|
|
case MUSIC_MODULE_MOD:
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
// NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2
|
|
|
|
jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0);
|
|
|
|
//jar_mod_seek_start((jar_mod_context_t *)music.ctxData);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
} break;
|
|
|
|
#endif
|
|
|
|
default: break;
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
UpdateAudioStream(music.stream, AUDIO.System.pcmBuffer, framesToStream);
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
music.stream.buffer->framesProcessed = music.stream.buffer->framesProcessed%music.frameCount;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (framesLeft <= subBufferSizeInFrames)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
if (!music.looping)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
// Streaming is ending, we filled latest frames from input
|
|
|
|
StopMusicStream(music);
|
|
|
|
return;
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// NOTE: In case window is minimized, music stream is stopped,
|
|
|
|
// just make sure to play again on window restore
|
|
|
|
if (IsMusicStreamPlaying(music)) PlayMusicStream(music);
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Get current music time played (in seconds)
|
|
|
|
float GetMusicTimePlayed(Music music)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
float secondsPlayed = 0.0f;
|
|
|
|
if (music.stream.buffer != NULL)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
#if defined(SUPPORT_FILEFORMAT_XM)
|
|
|
|
if (music.ctxType == MUSIC_MODULE_XM)
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
uint64_t framesPlayed = 0;
|
2023-11-12 13:49:57 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &framesPlayed);
|
|
|
|
secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
2023-11-12 17:29:29 +00:00
|
|
|
else
|
|
|
|
#endif
|
2023-11-12 13:49:57 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
//ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels;
|
|
|
|
int framesProcessed = (int)music.stream.buffer->framesProcessed;
|
|
|
|
int subBufferSize = (int)music.stream.buffer->sizeInFrames/2;
|
|
|
|
int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize;
|
|
|
|
int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize;
|
|
|
|
int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize;
|
|
|
|
int framesPlayed = (framesProcessed - framesInFirstBuffer - framesInSecondBuffer + framesSentToMix)%(int)music.frameCount;
|
|
|
|
if (framesPlayed < 0) framesPlayed += music.frameCount;
|
|
|
|
secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
|
2023-11-12 13:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
return secondsPlayed;
|
2023-11-12 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Load audio stream (to stream audio pcm data)
|
|
|
|
AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels)
|
2023-11-12 17:02:04 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
AudioStream stream = { 0 };
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
stream.sampleRate = sampleRate;
|
|
|
|
stream.sampleSize = sampleSize;
|
|
|
|
stream.channels = channels;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32));
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// The size of a streaming buffer must be at least double the size of a period
|
|
|
|
unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// If the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate
|
|
|
|
unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0)? AUDIO.System.device.sampleRate/30 : AUDIO.Buffer.defaultSize;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (subBufferSize < periodSize) subBufferSize = periodSize;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Create a double audio buffer of defined size
|
|
|
|
stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM);
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (stream.buffer != NULL)
|
|
|
|
{
|
|
|
|
stream.buffer->looping = true; // Always loop for streaming buffers
|
|
|
|
TRACELOG(LOG_INFO, );
|
|
|
|
}
|
|
|
|
else TRACELOG(LOG_WARNING, );
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
return stream;
|
2023-11-12 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Checks if an audio stream is ready
|
|
|
|
bool IsAudioStreamReady(AudioStream stream)
|
2023-11-12 17:02:04 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
return ((stream.buffer != NULL) && // Validate stream buffer
|
|
|
|
(stream.sampleRate > 0) && // Validate sample rate is supported
|
|
|
|
(stream.sampleSize > 0) && // Validate sample size is supported
|
|
|
|
(stream.channels > 0)); // Validate number of channels supported
|
2023-11-12 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Unload audio stream and free memory
|
|
|
|
void UnloadAudioStream(AudioStream stream)
|
2023-11-12 17:02:04 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
UnloadAudioBuffer(stream.buffer);
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
TRACELOG(LOG_INFO, );
|
2023-11-12 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Update audio stream buffers with data
|
|
|
|
// NOTE 1: Only updates one buffer of the stream source: dequeue -> update -> queue
|
|
|
|
// NOTE 2: To dequeue a buffer it needs to be processed: IsAudioStreamProcessed()
|
|
|
|
void UpdateAudioStream(AudioStream stream, const void *data, int frameCount)
|
|
|
|
{
|
|
|
|
if (stream.buffer != NULL)
|
|
|
|
{
|
|
|
|
if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1])
|
|
|
|
{
|
|
|
|
ma_uint32 subBufferToUpdate = 0;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1])
|
|
|
|
{
|
|
|
|
// Both buffers are available for updating.
|
|
|
|
// Update the first one and make sure the cursor is moved back to the front.
|
|
|
|
subBufferToUpdate = 0;
|
|
|
|
stream.buffer->frameCursorPos = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Just update whichever sub-buffer is processed.
|
|
|
|
subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1;
|
|
|
|
}
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2;
|
|
|
|
unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate);
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Total frames processed in buffer is always the complete size, filled with 0 if required
|
|
|
|
stream.buffer->framesProcessed += subBufferSizeInFrames;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Does this API expect a whole buffer to be updated in one go?
|
|
|
|
// Assuming so, but if not will need to change this logic.
|
|
|
|
if (subBufferSizeInFrames >= (ma_uint32)frameCount)
|
|
|
|
{
|
|
|
|
ma_uint32 framesToWrite = (ma_uint32)frameCount;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8);
|
|
|
|
memcpy(subBuffer, data, bytesToWrite);
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Any leftover frames should be filled with zeros.
|
|
|
|
ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8));
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false;
|
|
|
|
}
|
|
|
|
else TRACELOG(LOG_WARNING, );
|
|
|
|
}
|
|
|
|
else TRACELOG(LOG_WARNING, );
|
|
|
|
}
|
|
|
|
}
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Main mixing function, pretty simple in this project, just an accumulation
|
|
|
|
// NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function
|
|
|
|
static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer)
|
|
|
|
{
|
|
|
|
const float localVolume = buffer->volume;
|
|
|
|
const ma_uint32 channels = AUDIO.System.device.playback.channels;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if (channels == 2) // We consider panning
|
|
|
|
{
|
|
|
|
const float left = buffer->pan;
|
|
|
|
const float right = 1.0f - left;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Fast sine approximation in [0..1] for pan law: y = 0.5f*x*(3 - x*x);
|
|
|
|
const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) };
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
float *frameOut = framesOut;
|
|
|
|
const float *frameIn = framesIn;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
for (ma_uint32 frame = 0; frame < frameCount; frame++)
|
|
|
|
{
|
|
|
|
frameOut[0] += (frameIn[0]*levels[0]);
|
|
|
|
frameOut[1] += (frameIn[1]*levels[1]);
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
frameOut += 2;
|
|
|
|
frameIn += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else // We do not consider panning
|
|
|
|
{
|
|
|
|
for (ma_uint32 frame = 0; frame < frameCount; frame++)
|
|
|
|
{
|
|
|
|
for (ma_uint32 c = 0; c < channels; c++)
|
|
|
|
{
|
|
|
|
float *frameOut = framesOut + (frame*channels);
|
|
|
|
const float *frameIn = framesIn + (frame*channels);
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Output accumulates input multiplied by volume to provided output (usually 0)
|
|
|
|
frameOut[c] += (frameIn[c]*localVolume);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-11-12 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
// Some required functions for audio standalone module version
|
|
|
|
#if defined(RAUDIO_STANDALONE)
|
|
|
|
// Check file extension
|
|
|
|
static bool IsFileExtension(const char *fileName, const char *ext)
|
2023-11-12 17:02:04 +00:00
|
|
|
{
|
2023-11-12 17:29:29 +00:00
|
|
|
bool result = false;
|
|
|
|
const char *fileExt;
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
if ((fileExt = strrchr(fileName, '.')) != NULL)
|
|
|
|
{
|
|
|
|
if (strcmp(fileExt, ext) == 0) result = true;
|
|
|
|
}
|
2023-11-12 17:02:04 +00:00
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
return result;
|
2023-11-12 17:02:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* futex_top_waiter() - Return the highest priority waiter on a futex
|
|
|
|
* @hb: the hash bucket the futex_q's reside in
|
|
|
|
* @key: the futex key (to distinguish it from other futex futex_q's)
|
|
|
|
*
|
|
|
|
* Must be called with the hb lock held.
|
|
|
|
*/
|
|
|
|
struct futex_q *futex_top_waiter(struct futex_hash_bucket *hb, union futex_key *key)
|
|
|
|
{
|
|
|
|
struct futex_q *this;
|
|
|
|
|
|
|
|
plist_for_each_entry(this, &hb->chain, list) {
|
|
|
|
if (futex_match(&this->key, key))
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int futex_cmpxchg_value_locked(u32 *curval, u32 __user *uaddr, u32 uval, u32 newval)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pagefault_disable();
|
|
|
|
ret = futex_atomic_cmpxchg_inatomic(curval, uaddr, uval, newval);
|
|
|
|
pagefault_enable();
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int futex_get_value_locked(u32 *dest, u32 __user *from)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
pagefault_disable();
|
|
|
|
ret = __get_user(*dest, from);
|
|
|
|
pagefault_enable();
|
|
|
|
|
|
|
|
return ret ? -EFAULT : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* wait_for_owner_exiting - Block until the owner has exited
|
|
|
|
* @ret: owner's current futex lock status
|
|
|
|
* @exiting: Pointer to the exiting task
|
|
|
|
*
|
|
|
|
* Caller must hold a refcount on @exiting.
|
|
|
|
*/
|
|
|
|
void wait_for_owner_exiting(int ret, struct task_struct *exiting)
|
|
|
|
{
|
|
|
|
if (ret != -EBUSY) {
|
|
|
|
WARN_ON_ONCE(exiting);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WARN_ON_ONCE(ret == -EBUSY && !exiting))
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&exiting->futex_exit_mutex);
|
|
|
|
/*
|
|
|
|
* No point in doing state checking here. If the waiter got here
|
|
|
|
* while the task was in exec()->exec_futex_release() then it can
|
|
|
|
* have any FUTEX_STATE_* value when the waiter has acquired the
|
|
|
|
* mutex. OK, if running, EXITING or DEAD if it reached exit()
|
|
|
|
* already. Highly unlikely and not a problem. Just one more round
|
|
|
|
* through the futex maze.
|
|
|
|
*/
|
|
|
|
mutex_unlock(&exiting->futex_exit_mutex);
|
|
|
|
|
|
|
|
put_task_struct(exiting);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* __futex_unqueue() - Remove the futex_q from its futex_hash_bucket
|
|
|
|
* @q: The futex_q to unqueue
|
|
|
|
*
|
|
|
|
* The q->lock_ptr must not be NULL and must be held by the caller.
|
|
|
|
*/
|
|
|
|
void __futex_unqueue(struct futex_q *q)
|
|
|
|
{
|
|
|
|
struct futex_hash_bucket *hb;
|
|
|
|
|
|
|
|
if (WARN_ON_SMP(!q->lock_ptr) || WARN_ON(plist_node_empty(&q->list)))
|
|
|
|
return;
|
|
|
|
lockdep_assert_held(q->lock_ptr);
|
|
|
|
|
|
|
|
hb = container_of(q->lock_ptr, struct futex_hash_bucket, lock);
|
|
|
|
plist_del(&q->list, &hb->chain);
|
|
|
|
futex_hb_waiters_dec(hb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The key must be already stored in q->key. */
|
|
|
|
struct futex_hash_bucket *futex_q_lock(struct futex_q *q)
|
|
|
|
__acquires(&hb->lock)
|
|
|
|
{
|
|
|
|
struct futex_hash_bucket *hb;
|
|
|
|
|
|
|
|
hb = futex_hash(&q->key);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Increment the counter before taking the lock so that
|
|
|
|
* a potential waker won't miss a to-be-slept task that is
|
|
|
|
* waiting for the spinlock. This is safe as all futex_q_lock()
|
|
|
|
* users end up calling futex_queue(). Similarly, for housekeeping,
|
|
|
|
* decrement the counter at futex_q_unlock() when some error has
|
|
|
|
* occurred and we don't end up adding the task to the list.
|
|
|
|
*/
|
|
|
|
futex_hb_waiters_inc(hb); /* implies smp_mb(); (A) */
|
|
|
|
|
|
|
|
q->lock_ptr = &hb->lock;
|
|
|
|
|
|
|
|
spin_lock(&hb->lock);
|
|
|
|
return hb;
|
|
|
|
}
|
|
|
|
|
|
|
|
void futex_q_unlock(struct futex_hash_bucket *hb)
|
|
|
|
__releases(&hb->lock)
|
|
|
|
{
|
|
|
|
spin_unlock(&hb->lock);
|
|
|
|
futex_hb_waiters_dec(hb);
|
|
|
|
}
|
|
|
|
|
|
|
|
int futex_unqueue(struct futex_q *q)
|
|
|
|
{
|
|
|
|
spinlock_t *lock_ptr;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
/* In the common case we don't take the spinlock, which is nice. */
|
|
|
|
retry:
|
|
|
|
/*
|
|
|
|
* q->lock_ptr can change between this read and the following spin_lock.
|
|
|
|
* Use READ_ONCE to forbid the compiler from reloading q->lock_ptr and
|
|
|
|
* optimizing lock_ptr out of the logic below.
|
|
|
|
*/
|
|
|
|
lock_ptr = READ_ONCE(q->lock_ptr);
|
|
|
|
if (lock_ptr != NULL) {
|
|
|
|
spin_lock(lock_ptr);
|
|
|
|
/*
|
|
|
|
* q->lock_ptr can change between reading it and
|
|
|
|
* spin_lock(), causing us to take the wrong lock. This
|
|
|
|
* corrects the race condition.
|
|
|
|
*
|
|
|
|
* Reasoning goes like this: if we have the wrong lock,
|
|
|
|
* q->lock_ptr must have changed (maybe several times)
|
|
|
|
* between reading it and the spin_lock(). It can
|
|
|
|
* change again after the spin_lock() but only if it was
|
|
|
|
* already changed before the spin_lock(). It cannot,
|
|
|
|
* however, change back to the original value. Therefore
|
|
|
|
* we can detect whether we acquired the correct lock.
|
|
|
|
*/
|
|
|
|
if (unlikely(lock_ptr != q->lock_ptr)) {
|
|
|
|
spin_unlock(lock_ptr);
|
|
|
|
goto retry;
|
|
|
|
}
|
|
|
|
__futex_unqueue(q);
|
|
|
|
|
|
|
|
BUG_ON(q->pi_state);
|
|
|
|
|
|
|
|
spin_unlock(lock_ptr);
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* PI futexes can not be requeued and must remove themselves from the
|
|
|
|
* hash bucket. The hash bucket lock (i.e. lock_ptr) is held.
|
|
|
|
*/
|
|
|
|
void futex_unqueue_pi(struct futex_q *q)
|
|
|
|
{
|
|
|
|
__futex_unqueue(q);
|
|
|
|
|
|
|
|
BUG_ON(!q->pi_state);
|
|
|
|
put_pi_state(q->pi_state);
|
|
|
|
q->pi_state = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Constants for the pending_op argument of handle_futex_death */
|
|
|
|
#define HANDLE_DEATH_PENDING true
|
|
|
|
#define HANDLE_DEATH_LIST false
|
|
|
|
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
static void __user *futex_uaddr(struct robust_list __user *entry,
|
|
|
|
compat_long_t futex_offset)
|
|
|
|
{
|
|
|
|
compat_uptr_t base = ptr_to_compat(entry);
|
|
|
|
void __user *uaddr = compat_ptr(base + futex_offset);
|
|
|
|
|
|
|
|
return uaddr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fetch a robust-list pointer. Bit 0 signals PI futexes:
|
|
|
|
*/
|
|
|
|
static inline int
|
|
|
|
compat_fetch_robust_entry(compat_uptr_t *uentry, struct robust_list __user **entry,
|
|
|
|
compat_uptr_t __user *head, unsigned int *pi)
|
|
|
|
{
|
|
|
|
if (get_user(*uentry, head))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
*entry = compat_ptr((*uentry) & ~1);
|
|
|
|
*pi = (unsigned int)(*uentry) & 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static void compat_exit_robust_list(struct task_struct *curr)
|
|
|
|
{
|
|
|
|
struct compat_robust_list_head __user *head = curr->compat_robust_list;
|
|
|
|
struct robust_list __user *entry, *next_entry, *pending;
|
|
|
|
unsigned int limit = ROBUST_LIST_LIMIT, pi, pip;
|
|
|
|
unsigned int next_pi;
|
|
|
|
compat_uptr_t uentry, next_uentry, upending;
|
|
|
|
compat_long_t futex_offset;
|
|
|
|
int rc;
|
|
|
|
next_entry = NULL; /* avoid warning with gcc */
|
|
|
|
while (entry != (struct robust_list __user *) &head->list) {
|
|
|
|
/*
|
|
|
|
* Fetch the next entry in the list before calling
|
|
|
|
* handle_futex_death:
|
|
|
|
*/
|
|
|
|
rc = compat_fetch_robust_entry(&next_uentry, &next_entry,
|
|
|
|
(compat_uptr_t __user *)&entry->next, &next_pi);
|
|
|
|
/*
|
|
|
|
* A pending lock might already be on the list, so
|
|
|
|
* dont process it twice:
|
|
|
|
*/
|
|
|
|
if (entry != pending) {
|
|
|
|
void __user *uaddr = futex_uaddr(entry, futex_offset);
|
|
|
|
|
|
|
|
if (handle_futex_death(uaddr, curr, pi,
|
|
|
|
HANDLE_DEATH_LIST))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (rc)
|
|
|
|
return;
|
|
|
|
uentry = next_uentry;
|
|
|
|
entry = next_entry;
|
|
|
|
pi = next_pi;
|
|
|
|
/*
|
|
|
|
* Avoid excessively long or circular lists:
|
|
|
|
*/
|
|
|
|
if (!--limit)
|
|
|
|
break;
|
|
|
|
|
|
|
|
cond_resched();
|
|
|
|
}
|
|
|
|
if (pending) {
|
|
|
|
void __user *uaddr = futex_uaddr(pending, futex_offset);
|
|
|
|
|
|
|
|
handle_futex_death(uaddr, curr, pip, HANDLE_DEATH_PENDING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_FUTEX_PI
|
|
|
|
|
|
|
|
static void exit_pi_state_list(struct task_struct *curr)
|
|
|
|
{
|
|
|
|
struct list_head *next, *head = &curr->pi_state_list;
|
|
|
|
struct futex_pi_state *pi_state;
|
|
|
|
struct futex_hash_bucket *hb;
|
|
|
|
union futex_key key = FUTEX_KEY_INIT;
|
|
|
|
while (!list_empty(head)) {
|
|
|
|
next = head->next;
|
|
|
|
pi_state = list_entry(next, struct futex_pi_state, list);
|
|
|
|
key = pi_state->key;
|
|
|
|
hb = futex_hash(&key);
|
|
|
|
if (!refcount_inc_not_zero(&pi_state->refcount)) {
|
|
|
|
raw_spin_unlock_irq(&curr->pi_lock);
|
|
|
|
cpu_relax();
|
|
|
|
raw_spin_lock_irq(&curr->pi_lock);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
raw_spin_unlock_irq(&curr->pi_lock);
|
|
|
|
|
|
|
|
spin_lock(&hb->lock);
|
|
|
|
raw_spin_lock_irq(&pi_state->pi_mutex.wait_lock);
|
|
|
|
raw_spin_lock(&curr->pi_lock);
|
|
|
|
/*
|
|
|
|
* We dropped the pi-lock, so re-check whether this
|
|
|
|
* task still owns the PI-state:
|
|
|
|
*/
|
|
|
|
if (head->next != next) {
|
|
|
|
/* retain curr->pi_lock for the loop invariant */
|
|
|
|
raw_spin_unlock(&pi_state->pi_mutex.wait_lock);
|
|
|
|
spin_unlock(&hb->lock);
|
|
|
|
put_pi_state(pi_state);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
WARN_ON(pi_state->owner != curr);
|
|
|
|
WARN_ON(list_empty(&pi_state->list));
|
|
|
|
list_del_init(&pi_state->list);
|
|
|
|
pi_state->owner = NULL;
|
|
|
|
|
|
|
|
raw_spin_unlock(&curr->pi_lock);
|
|
|
|
raw_spin_unlock_irq(&pi_state->pi_mutex.wait_lock);
|
|
|
|
spin_unlock(&hb->lock);
|
|
|
|
|
|
|
|
rt_mutex_futex_unlock(&pi_state->pi_mutex);
|
|
|
|
put_pi_state(pi_state);
|
|
|
|
|
|
|
|
raw_spin_lock_irq(&curr->pi_lock);
|
|
|
|
}
|
|
|
|
raw_spin_unlock_irq(&curr->pi_lock);
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void exit_pi_state_list(struct task_struct *curr) { }
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void futex_cleanup(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
if (unlikely(tsk->robust_list)) {
|
|
|
|
exit_robust_list(tsk);
|
|
|
|
tsk->robust_list = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
if (unlikely(tsk->compat_robust_list)) {
|
|
|
|
compat_exit_robust_list(tsk);
|
|
|
|
tsk->compat_robust_list = NULL;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (unlikely(!list_empty(&tsk->pi_state_list)))
|
|
|
|
exit_pi_state_list(tsk);
|
|
|
|
}
|
|
|
|
void futex_exit_recursive(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
/* If the state is FUTEX_STATE_EXITING then futex_exit_mutex is held */
|
|
|
|
if (tsk->futex_state == FUTEX_STATE_EXITING)
|
|
|
|
mutex_unlock(&tsk->futex_exit_mutex);
|
|
|
|
tsk->futex_state = FUTEX_STATE_DEAD;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void futex_cleanup_begin(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
raw_spin_lock_irq(&tsk->pi_lock);
|
|
|
|
tsk->futex_state = FUTEX_STATE_EXITING;
|
|
|
|
raw_spin_unlock_irq(&tsk->pi_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void futex_cleanup_end(struct task_struct *tsk, int state)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Lockless store. The only side effect is that an observer might
|
|
|
|
* take another loop until it becomes visible.
|
|
|
|
*/
|
|
|
|
tsk->futex_state = state;
|
|
|
|
/*
|
|
|
|
* Drop the exit protection. This unblocks waiters which observed
|
|
|
|
* FUTEX_STATE_EXITING to reevaluate the state.
|
|
|
|
*/
|
|
|
|
mutex_unlock(&tsk->futex_exit_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
void futex_exec_release(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
futex_cleanup_begin(tsk);
|
|
|
|
futex_cleanup(tsk);
|
|
|
|
/*
|
|
|
|
* Reset the state to FUTEX_STATE_OK. The task is alive and about
|
|
|
|
* exec a new binary.
|
|
|
|
*/
|
|
|
|
futex_cleanup_end(tsk, FUTEX_STATE_OK);
|
|
|
|
}
|
|
|
|
|
|
|
|
void futex_exit_release(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
futex_cleanup_begin(tsk);
|
|
|
|
futex_cleanup(tsk);
|
|
|
|
futex_cleanup_end(tsk, FUTEX_STATE_DEAD);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init futex_init(void)
|
|
|
|
{
|
|
|
|
unsigned int futex_shift;
|
|
|
|
unsigned long i;
|
|
|
|
|
|
|
|
#if CONFIG_BASE_SMALL
|
|
|
|
futex_hashsize = 16;
|
|
|
|
#else
|
|
|
|
futex_hashsize = roundup_pow_of_two(256 * num_possible_cpus());
|
|
|
|
#endif
|
|
|
|
|
2023-11-12 17:29:29 +00:00
|
|
|
futex_queues = alloc_large_system_hash(, sizeof(*futex_queues),
|
2023-11-12 17:02:04 +00:00
|
|
|
futex_hashsize, 0, 0,
|
|
|
|
&futex_shift, NULL,
|
|
|
|
futex_hashsize, futex_hashsize);
|
|
|
|
futex_hashsize = 1UL << futex_shift;
|
|
|
|
|
|
|
|
for (i = 0; i < futex_hashsize; i++) {
|
|
|
|
atomic_set(&futex_queues[i].waiters, 0);
|
|
|
|
plist_head_init(&futex_queues[i].chain);
|
|
|
|
spin_lock_init(&futex_queues[i].lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
core_initcall(futex_init);
|