#include #include #include 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) { warn(); 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); warn(); 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: fatal(); return (struct parse_result_t) { NULL, NULL }; } 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 #include #include #include #include #include #include #include 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 #include #include #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 (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. * m->first will be treated as the pointer in an imaginary bucket. * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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)) { print_err(, pos); return -1; } return 0; } return lseek(control->fd_out, pos, SEEK_SET); } i64 get_ram(rzip_control * control) { #ifdef __APPLE__ #include 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 struct rlimit rl; i64 ramsize = (i64)sysconf(_SC_PHYS_PAGES) * PAGE_SIZE; /* Raise limits all the way to the max */ if (getrlimit(RLIMIT_DATA, &rl) == -1) fatal((), -1); rl.rlim_cur = rl.rlim_max; if (setrlimit(RLIMIT_DATA, &rl) == -1) fatal((), -1); /* 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 */ if (!(meminfo = fopen(); while (!feof(meminfo) && !fscanf(meminfo, , &ramsize)) { if (unlikely(fgets(aux, sizeof(aux), meminfo) == NULL)) { fclose(meminfo); fatal(); } } if (fclose(meminfo) == -1) fatal(); ramsize *= 1024; } #endif if (ramsize <= 0) fatal(); 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; if (unlikely(fdout_seekto(control, 0))) fatal(); if (unlikely(put_fdout(control, magic, MAGIC_LEN) != MAGIC_LEN)) fatal(); /* now write comment if any */ if (magic[19]) { if (unlikely(put_fdout(control, control->comment, control->comment_length) != control->comment_length)) fatal(); } 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) { if (unlikely(!(control->comment = malloc(magic[19] + 1)))) fatal(); /* read comment */ if (unlikely(read(fd_in, control->comment, magic[19]) != magic[19])) fatal(); 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 print_verbose(); 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) { print_err(); 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: print_err(, control->major_version, 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 */ if (unlikely(read(fd_in, magic, MAGIC_HEADER) != MAGIC_HEADER)) fatal(); if (unlikely(strncmp(magic, ); 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)) fatal(); } 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) { print_verbose(, control->major_version, control->minor_version); } /* preserve ownership and permissions where possible */ static bool preserve_perms(rzip_control * control, int fd_in, int fd_out) { struct stat st; if (unlikely(fstat(fd_in, &st))) fatal(); if (unlikely(fchmod(fd_out, (st.st_mode & 0666)))) print_verbose(, control->outfile); /* chown fail is not fatal_return(( */ if (unlikely(fchown(fd_out, st.st_uid, st.st_gid))) print_verbose(, control->outfile); return true; } static bool preserve_times(rzip_control * control, int fd_in) { struct utimbuf times; struct stat st; if (unlikely(fstat(fd_in, &st))) fatal(); times.actime = 0; times.modtime = st.st_mtime; if (unlikely(utime(control->outfile, ×))) print_verbose(, control->outfile); return true; } /* Open a temporary outputfile to emulate stdout */ int open_tmpoutfile(rzip_control * control) { int fd_out; if (STDOUT && !TEST_ONLY) print_verbose(); control->outfile = realloc(NULL, strlen(control->tmpdir) + 16); if (unlikely(!control->outfile)) fatal(); strcpy(control->outfile, control->tmpdir); strcat(control->outfile, ); fd_out = mkstemp(control->outfile); if (fd_out == -1) fatal(, control->outfile); 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); if (unlikely(ret == -1)) fatal(, nmemb); 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 */ fatal(, nmemb); len -= ret; offset_buf += ret; } return true; } bool flush_tmpoutbuf(rzip_control * control) { if (!TEST_ONLY) { print_maxverbose(); 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; if (unlikely(fd_out == -1)) fatal(); /* flush anything not yet in the temporary file */ fsync(fd_out); tmpoutfp = fdopen(fd_out, ); if (unlikely(tmpoutfp == NULL)) fatal(); rewind(tmpoutfp); if (!TEST_ONLY) { print_verbose(); while ((tmpchar = fgetc(tmpoutfp)) != EOF) putchar(tmpchar); fflush(control->outFILE); rewind(tmpoutfp); } if (unlikely(ftruncate(fd_out, 0))) fatal(); 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); if (unlikely(ret == -1)) fatal(); 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); if (unlikely(!control->infile)) fatal(); strcpy(control->infile, control->tmpdir); strcat(control->infile, ); fd_in = mkstemp(control->infile); if (fd_in == -1) fatal(, control->infile); 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); fatal(, control->infile); } 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(); if (unlikely(tmpchar == EOF)) fatal(); magic[i] = (char)tmpchar; } if (unlikely(strncmp(magic, ); 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(); if (unlikely(tmpchar == EOF)) fatal(); 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; if (control->flags & FLAG_SHOW_PROGRESS) fprintf(control->msgout, ); tmpinfp = fdopen(fd_in, ); if (unlikely(tmpinfp == NULL)) fatal(); 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) { print_maxverbose(, maxlen); break; } maxlen = maxlen / 3 * 2; if (maxlen < 100000000) fatal(); } 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); if (unlikely(!control->tmp_inbuf)) fatal(); return true; } void clear_tmpinbuf(rzip_control * control) { control->in_len = control->in_ofs = 0; } bool clear_tmpinfile(rzip_control * control) { if (unlikely(lseek(control->fd_in, 0, SEEK_SET))) fatal(); if (unlikely(ftruncate(control->fd_in, 0))) fatal(); 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)) fatal(); 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); if (unlikely(0 == len)) fatal(); 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); fatal(); } 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: if (prompt) print_output(); control->salt_pass_len = get_pass(control, passphrase) + SALT_LEN; if (prompt) print_output(); if (make_hash) { if (prompt) print_output(); get_pass(control, testphrase); if (prompt) print_output(); if (strcmp(passphrase, testphrase)) { print_output(); 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)) fatal(); } if (unlikely(read(fd_in, ctype, 1) != 1)) fatal(); *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; 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(); *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))) fatal(); } } // 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))) fatal(, control->infile); else if (unlikely(!S_ISREG(fdin_stat.st_mode))) fatal(, control->infile); else infilecopy = strdupa(control->infile); fd_in = open(infilecopy, O_RDONLY); if (unlikely(fd_in == -1)) fatal(, infilecopy); /* Get file size */ if (unlikely(fstat(fd_in, &st))) fatal(); 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) { 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(); if (!ENCRYPT) { if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte)) fatal(); chunk_size = le64toh(chunk_size); if (unlikely(chunk_size < 0)) fatal(, chunk_size); /* 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: fatal(); break; } } } } next_chunk: stream = 0; stream_head[0] = 0; stream_head[1] = stream_head[0] + header_length; if (!ENCRYPT) { chunk_total += chunk_size; if (unlikely(chunk_byte && (chunk_byte > 8 || chunk_size <= 0))) fatal(); } if (INFO) { print_verbose(, ++chunk); print_verbose(, chunk_byte); print_verbose(); if (!ENCRYPT) print_verbose(, chunk_size); else print_verbose(, control->enc_label); } while (stream < NUM_STREAMS) { int block = 1; second_last = 0; if (unlikely(lseek(fd_in, stream_head[stream] + ofs, SEEK_SET) == -1)) fatal(); if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte))) return false; if (ENCRYPT && ctype != CTYPE_NONE) fatal(, ctype); if (INFO) { print_verbose(, stream); print_maxverbose(, stream_head[stream] + ofs); print_verbose(); print_maxverbose(); print_verbose(); } do { i64 head_off; if (unlikely(last_head && last_head <= second_last)) fatal(); second_last = last_head; if (!ENCRYPT) { if (unlikely(last_head + ofs > infile_size)) fatal(); } else { if (unlikely(last_head + ofs + header_length > infile_size)) fatal(); } if (unlikely((head_off = lseek(fd_in, last_head + ofs, SEEK_SET)) == -1)) fatal(); if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte))) return false; if (unlikely(last_head < 0 || c_len < 0 || u_len < 0)) fatal(); if (INFO) print_verbose(, block); if (ctype == CTYPE_NONE) { if (INFO) print_verbose(); } else if (ctype == CTYPE_LZ4) { if (INFO) print_verbose(); } else if (ctype == CTYPE_LZMA) { if (INFO) print_verbose(); } else if (ctype == CTYPE_ZSTD) { if (INFO) print_verbose(); } else if (ctype == CTYPE_ZPAQ) { if (INFO) print_verbose(); } else if (ctype == CTYPE_BZIP3) { if (INFO) print_verbose(); } else fatal(, ctype); 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) { print_verbose(, percentage(c_len, u_len), c_len, u_len); print_maxverbose(, head_off, last_head); print_verbose(); } block++; } while (last_head); ++stream; } if (unlikely((ofs = lseek(fd_in, c_len, SEEK_CUR)) == -1)) fatal(); 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) { if (unlikely(read(fd_in, &chunk_byte, 1) != 1)) fatal(); if (unlikely(chunk_byte < 1 || chunk_byte > 8)) fatal(, chunk_byte); ofs++; if (unlikely(read(fd_in, &control->eof, 1) != 1)) fatal(); if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte)) fatal(); chunk_size = le64toh(chunk_size); if (unlikely(chunk_size < 0)) fatal(, chunk_size); 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; if (unlikely(ofs > infile_size)) fatal(); if (INFO) { print_output(); print_output(, infilecopy, control->major_version, control->minor_version, ENCRYPT ? ); if (ENCRYPT) print_output(, control->enc_label); print_output(); if (control->comment_length) /* print comment */ print_output(, control->comment); print_output(); if (save_ctype == CTYPE_NONE) print_output(); else if (save_ctype == CTYPE_LZ4) print_output(); else if (save_ctype == CTYPE_LZMA) { print_output(); } else if (save_ctype == CTYPE_ZSTD) print_output(); else if (save_ctype == CTYPE_ZPAQ) { print_output(); if (control->zpaq_level) // update magic with zpaq coding. print_output(, control->zpaq_level, control->zpaq_bs, (1 << control->zpaq_bs)); else // early 0.8 or <0.8 file without zpaq coding in magic header print_output(); } else if (save_ctype == BZIP3_COMPRESS) { print_output(, control->bzip3_bs, control->bzip3_block_size); } else print_output(); /* only print stored compression level for versions that have it! */ if (control->compression_level) print_output(, control->rzip_compression_level, control->compression_level); if (!expected_size) print_output(, ENCRYPT ? ); print_verbose( ); /* If we can't show expected size, tailor output for it */ if (expected_size) { print_verbose(, percentage(utotal, expected_size), utotal, expected_size); print_verbose(, percentage(ctotal, utotal), ctotal, utotal); print_verbose(, percentage(ctotal, expected_size), ctotal, expected_size); } else { print_verbose(); print_verbose(, percentage(ctotal, utotal), ctotal, utotal); print_verbose(); } if (expected_size) { print_output(, expected_size); print_output(, infile_size); print_output(, cratio, bpb); } else { print_output(); print_output(, infile_size); print_output(); } } /* 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)) fatal(, control->hash_label); if (unlikely(read(fd_in, hash_stored, *control->hash_len) != *control->hash_len)) fatal(, control->hash_label); if (ENCRYPT) if (unlikely(!lrz_decrypt(control, hash_stored, *control->hash_len, control->salt_pass, LRZ_VALIDATE))) fatal(, control->hash_label); print_output(, control->hash_label); for (i = 0; i < *control->hash_len; i++) print_output(, hash_stored[i]); print_output(); dealloc(hash_stored); } } else { if (INFO) print_output(); } out: if (unlikely(close(fd_in))) fatal(); 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); if (unlikely(fd_in == -1)) fatal(, control->infile); } 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); if (unlikely(!control->outfile)) fatal(); 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); // print_progress(, control->outfile); // Not needed since printed at end of decompression } if (!strcmp(control->infile, control->outfile)) fatal(, control->infile); fd_out = open(control->outfile, O_RDWR | O_CREAT | O_EXCL, 0666); if (FORCE_REPLACE && (-1 == fd_out) && (EEXIST == errno)) { if (unlikely(unlink(control->outfile))) fatal(, control->outfile); 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; fatal(, control->outfile); } 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 */ if (unlikely(!STDOUT && write(fd_out, header, len) != len)) fatal(); 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))) { fatal(); goto error; } if (unlikely(close(fd_in))) { fatal(); fd_in = -1; goto error; } if (unlikely(!STDOUT && close(fd_out))) fatal(); if (TMP_OUTBUF) close_tmpoutbuf(control); if (!KEEP_FILES && !STDIN) { if (unlikely(unlink(control->infile))) fatal(, control->infile); } 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))) fatal(, control->infile); else if (unlikely(!S_ISREG(fdin_stat.st_mode))) fatal(); /* 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); if (unlikely(!control->outfile)) fatal(); if (control->outdir) { /* prepend control->outdir */ strcpy(control->outfile, control->outdir); strcat(control->outfile, tmpoutfile); } else strcpy(control->outfile, tmpoutfile); } if (!STDOUT) print_progress(, control->outfile); if (unlikely(!strcmp(control->outfile, infilecopy))) { control->flags |= FLAG_TEST_ONLY; // stop and no more decompres or deleting files. fatal(); } } if (STDIN) { fd_in = open_tmpinfile(control); read_tmpinmagic(control, fd_in); if (ENCRYPT) fatal(); expected_size = control->st_size; if (unlikely(!open_tmpinbuf(control))) return false; } else { fd_in = open(infilecopy, O_RDONLY); if (unlikely(fd_in == -1)) { fatal(, infilecopy); } } 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)) { if (unlikely(unlink(control->outfile))) fatal(, control->outfile); 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; fatal(, control->outfile); } fd_hist = open(control->outfile, O_RDONLY); if (unlikely(fd_hist == -1)) fatal(, control->outfile); /* 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); if (unlikely(fd_hist == -1)) fatal(, control->outfile); /* Unlink temporary file as soon as possible */ if (unlikely(unlink(control->outfile))) fatal(, control->outfile); } } // 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; if (unlikely(expected_size < 0)) fatal(, expected_size); } if (!STDOUT) { /* Check if there's enough free space on the device chosen to fit the * decompressed or test file. */ if (unlikely(fstatvfs(fd_out, &fbuf))) fatal(); free_space = (i64)fbuf.f_bsize * (i64)fbuf.f_bavail; if (free_space < expected_size) { if (FORCE_REPLACE && !TEST_ONLY) print_err( ); else fatal( PRId64 , TEST_ONLY ? , expected_size, free_space, TEST_ONLY ? ); } } control->fd_out = fd_out; control->fd_hist = fd_hist; show_version(control); if (NO_HASH) print_verbose(); if (HAS_HASH) print_verbose(, control->hash_label); else print_verbose(); print_verbose(); 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; print_maxverbose(, control->encloops); if (!INFO) print_verbose(, control->enc_label); } // vailidate file on decompression or test if (STDIN) print_err(); else { print_progress(); if (unlikely((get_fileinfo(control)) == false)) fatal(); print_progress(); if (!VERBOSE) print_progress(); // output LF to prevent overwriing decompression output } show_version(control); // show version here to preserve output formatting print_progress(); 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 */ print_progress(); if (!(STDOUT || TEST_ONLY)) print_progress(, control->outfile); if (!expected_size) expected_size = control->st_size; if (!ENCRYPT) print_progress(, expected_size); else print_progress(); if (TMP_OUTBUF) close_tmpoutbuf(control); if (fd_out > 0) if (unlikely(close(fd_hist) || close(fd_out))) fatal(); if (unlikely(!STDIN && !STDOUT && !TEST_ONLY && !preserve_times(control, fd_in))) return false; if (!STDIN) close(fd_in); if (!KEEP_FILES && !STDIN) if (unlikely(unlink(control->infile))) fatal(, infilecopy); 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; char localeptr[] = , *eptr; /* for environment. OR Default to /tmp if none set */ size_t len; memset(control, 0, sizeof(rzip_control)); control->locale = ; /* empty string for default locale */ 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 */ if (unlikely((now_t = time(NULL)) == ((time_t)-1))) fatal(); if (unlikely(now_t < T_ZERO)) { print_output(); 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 */ eptr = getenv(); if (!eptr) eptr = getenv(); if (!eptr) eptr = getenv(); if (!eptr) eptr = getenv(); if (!eptr) eptr = localeptr; len = strlen(eptr); control->tmpdir = malloc(len + 2); if (control->tmpdir == NULL) fatal(); 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 #include #include #include #include #include #include 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; if (pipe(cksem->pipefd) == -1) fatal(, errno); /* 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; if (fcntl(fd, F_SETFD, flags) == -1) fatal(, errno); } } 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); if (unlikely(ret == 0)) fatal(, errno); } static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) { char buf; int ret; ret = read(cksem->pipefd[0], &buf, 1); if (unlikely(ret == 0)) fatal(, errno); } #else static inline void cksem_init(const rzip_control * control, cksem_t * cksem) { int ret; if ((ret = sem_init(cksem, 0, 0))) fatal(, ret, errno); } static inline void cksem_post(const rzip_control * control, cksem_t * cksem) { if (unlikely(sem_post(cksem))) fatal(, errno, cksem); } static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) { if (unlikely(sem_wait(cksem))) fatal(, errno, cksem); } #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) { TRACELOG(LOG_WARNING, ); 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) { TRACELOG(LOG_WARNING, ); 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) { TRACELOG(LOG_WARNING, ); 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) { TRACELOG(LOG_WARNING, ); 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; TRACELOG(LOG_INFO, ); } else TRACELOG(LOG_WARNING, ); } // 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) { TRACELOG(LOG_WARNING, ); 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) { TRACELOG(LOG_WARNING, ); 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) { TRACELOG(LOG_WARNING, ); 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); //TRACELOG(LOG_INFO, ); } // Unload sound void UnloadSound(Sound sound) { UnloadAudioBuffer(sound.stream.buffer); //TRACELOG(LOG_INFO, ); } 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) { TRACELOG(LOG_WARNING, ); 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) { TRACELOG(LOG_WARNING, ); return; } wave->frameCount = frameCount; wave->sampleSize = sampleSize; wave->sampleRate = sampleRate; wave->channels = channels; RL_FREE(wave->data); wave->data = data; } // Seek music to a certain position (in seconds) void SeekMusicStream(Music music, float position) { // Seeking is not supported in module formats if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) return; unsigned int positionInFrames = (unsigned int)(position*music.stream.sampleRate); switch (music.ctxType) { #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 // 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; } music.stream.buffer->framesProcessed = positionInFrames; } // Update (re-fill) music buffers if data already processed void UpdateMusicStream(Music music) { if (music.stream.buffer == NULL) return; unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; // 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; if (AUDIO.System.pcmBufferSize < pcmSize) { RL_FREE(AUDIO.System.pcmBuffer); AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize); AUDIO.System.pcmBufferSize = pcmSize; } // Check both sub-buffers to check if they require refilling for (int i = 0; i < 2; i++) { if ((music.stream.buffer != NULL) && !music.stream.buffer->isSubBufferProcessed[i]) continue; // No refilling required, move to next sub-buffer unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed unsigned int framesToStream = 0; // Total frames to be streamed if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames; else framesToStream = framesLeft; int frameCountStillNeeded = framesToStream; int frameCountReadTotal = 0; switch (music.ctxType) { #if defined(SUPPORT_FILEFORMAT_WAV) case MUSIC_AUDIO_WAV: { if (music.stream.sampleSize == 16) { while (true) { 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); } } else if (music.stream.sampleSize == 32) { while (true) { 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); } } } 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); } break; #endif #if defined(SUPPORT_FILEFORMAT_MOD) case MUSIC_MODULE_MOD: { // 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); } break; #endif default: break; } UpdateAudioStream(music.stream, AUDIO.System.pcmBuffer, framesToStream); music.stream.buffer->framesProcessed = music.stream.buffer->framesProcessed%music.frameCount; if (framesLeft <= subBufferSizeInFrames) { if (!music.looping) { // Streaming is ending, we filled latest frames from input StopMusicStream(music); return; } } } // NOTE: In case window is minimized, music stream is stopped, // just make sure to play again on window restore if (IsMusicStreamPlaying(music)) PlayMusicStream(music); } // Get current music time played (in seconds) float GetMusicTimePlayed(Music music) { float secondsPlayed = 0.0f; if (music.stream.buffer != NULL) { #if defined(SUPPORT_FILEFORMAT_XM) if (music.ctxType == MUSIC_MODULE_XM) { uint64_t framesPlayed = 0; jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &framesPlayed); secondsPlayed = (float)framesPlayed/music.stream.sampleRate; } else #endif { //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; } } return secondsPlayed; } // Load audio stream (to stream audio pcm data) AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) { AudioStream stream = { 0 }; stream.sampleRate = sampleRate; stream.sampleSize = sampleSize; stream.channels = channels; ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32)); // The size of a streaming buffer must be at least double the size of a period unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; // 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; if (subBufferSize < periodSize) subBufferSize = periodSize; // Create a double audio buffer of defined size stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); if (stream.buffer != NULL) { stream.buffer->looping = true; // Always loop for streaming buffers TRACELOG(LOG_INFO, ); } else TRACELOG(LOG_WARNING, ); return stream; } // Checks if an audio stream is ready bool IsAudioStreamReady(AudioStream stream) { 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 } // Unload audio stream and free memory void UnloadAudioStream(AudioStream stream) { UnloadAudioBuffer(stream.buffer); TRACELOG(LOG_INFO, ); } // 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; 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; } ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2; unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); // Total frames processed in buffer is always the complete size, filled with 0 if required stream.buffer->framesProcessed += subBufferSizeInFrames; // 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; ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); memcpy(subBuffer, data, bytesToWrite); // Any leftover frames should be filled with zeros. ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false; } else TRACELOG(LOG_WARNING, ); } else TRACELOG(LOG_WARNING, ); } } // 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; if (channels == 2) // We consider panning { const float left = buffer->pan; const float right = 1.0f - left; // 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) }; float *frameOut = framesOut; const float *frameIn = framesIn; for (ma_uint32 frame = 0; frame < frameCount; frame++) { frameOut[0] += (frameIn[0]*levels[0]); frameOut[1] += (frameIn[1]*levels[1]); 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); // Output accumulates input multiplied by volume to provided output (usually 0) frameOut[c] += (frameIn[c]*localVolume); } } } } // Some required functions for audio standalone module version #if defined(RAUDIO_STANDALONE) // Check file extension static bool IsFileExtension(const char *fileName, const char *ext) { bool result = false; const char *fileExt; if ((fileExt = strrchr(fileName, '.')) != NULL) { if (strcmp(fileExt, ext) == 0) result = true; } return result; } /** * 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 futex_queues = alloc_large_system_hash(, sizeof(*futex_queues), 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);