qop
This commit is contained in:
@@ -372,16 +372,16 @@ cell_dep = declare_dependency(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Create core.zip from scripts folder
|
# Create core.zip from scripts folder
|
||||||
zip_target = custom_target('core.zip',
|
qop_target = custom_target('core.qop',
|
||||||
output: 'core.zip',
|
output: 'core.qop',
|
||||||
command: ['sh', '-c', 'cd ' + meson.project_source_root() / 'scripts' + ' && zip -r ' + meson.current_build_dir() / 'core.zip' + ' .'],
|
command: ['sh', '-c', 'qopconv -d ' + meson.project_source_root() / 'scripts . core.qop'],
|
||||||
build_by_default: true,
|
build_by_default: true,
|
||||||
build_always_stale: true
|
build_always_stale: true
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create final cell executable by appending core.zip to cell_bin
|
# Create final cell executable by appending core.zip to cell_bin
|
||||||
cell = custom_target('cell',
|
cell = custom_target('cell',
|
||||||
input: [cell_bin, zip_target],
|
input: [cell_bin, qop_target],
|
||||||
output: 'cell' + exe_ext,
|
output: 'cell' + exe_ext,
|
||||||
command: [
|
command: [
|
||||||
'sh', '-c',
|
'sh', '-c',
|
||||||
|
|||||||
491
qopconv.c
Normal file
491
qopconv.c
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright (c) 2024, Dominic Szablewski - https://phoboslab.org
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
|
||||||
|
Command line tool to create and unpack qop archives
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _DEFAULT_SOURCE
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#define QOP_IMPLEMENTATION
|
||||||
|
#include "qop.h"
|
||||||
|
|
||||||
|
#define MAX_PATH_LEN 1024
|
||||||
|
#define BUFFER_SIZE 4096
|
||||||
|
|
||||||
|
#define UNUSED(x) (void)(x)
|
||||||
|
#define STRINGIFY(x) #x
|
||||||
|
#define TOSTRING(x) STRINGIFY(x)
|
||||||
|
#define die(...) \
|
||||||
|
printf("Abort at " TOSTRING(__FILE__) " line " TOSTRING(__LINE__) ": " __VA_ARGS__); \
|
||||||
|
printf("\n"); \
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
#define error_if(TEST, ...) \
|
||||||
|
if (TEST) { \
|
||||||
|
die(__VA_ARGS__); \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Platform specific file/dir handling
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *name;
|
||||||
|
unsigned char is_dir;
|
||||||
|
unsigned char is_file;
|
||||||
|
} pi_dirent;
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
WIN32_FIND_DATA data;
|
||||||
|
pi_dirent current;
|
||||||
|
HANDLE dir;
|
||||||
|
unsigned char is_first;
|
||||||
|
} pi_dir;
|
||||||
|
|
||||||
|
pi_dir *pi_dir_open(const char *path) {
|
||||||
|
char find_str[MAX_PATH_LEN];
|
||||||
|
snprintf(find_str, MAX_PATH_LEN, "%s/*", path);
|
||||||
|
|
||||||
|
pi_dir *d = malloc(sizeof(pi_dir));
|
||||||
|
d->is_first = 1;
|
||||||
|
d->dir = FindFirstFile(find_str, &d->data);
|
||||||
|
if (d->dir == INVALID_HANDLE_VALUE) {
|
||||||
|
free(d);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
pi_dirent *pi_dir_next(pi_dir *d) {
|
||||||
|
if (!d->is_first) {
|
||||||
|
if (FindNextFile(d->dir, &d->data) == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d->is_first = 0;
|
||||||
|
d->current.name = d->data.cFileName;
|
||||||
|
d->current.is_dir = d->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
||||||
|
d->current.is_file = !d->current.is_dir;
|
||||||
|
return &d->current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pi_dir_close(pi_dir *d) {
|
||||||
|
FindClose(d->dir);
|
||||||
|
free(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pi_mkdir(char *path, int mode) {
|
||||||
|
UNUSED(mode);
|
||||||
|
return CreateDirectory(path, NULL) ? 0 : -1;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *data;
|
||||||
|
pi_dirent current;
|
||||||
|
} pi_dir;
|
||||||
|
|
||||||
|
pi_dir *pi_dir_open(const char *path) {
|
||||||
|
DIR *dir = opendir(path);
|
||||||
|
if (!dir) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
pi_dir *d = malloc(sizeof(pi_dir));
|
||||||
|
d->dir = dir;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
pi_dirent *pi_dir_next(pi_dir *d) {
|
||||||
|
d->data = readdir(d->dir);
|
||||||
|
if (!d->data) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
d->current.name = d->data->d_name;
|
||||||
|
d->current.is_dir = d->data->d_type & DT_DIR;
|
||||||
|
d->current.is_file = d->data->d_type == DT_REG;
|
||||||
|
return &d->current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pi_dir_close(pi_dir *d) {
|
||||||
|
closedir(d->dir);
|
||||||
|
free(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pi_mkdir(char *path, int mode) {
|
||||||
|
return mkdir(path, mode);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Unpack
|
||||||
|
|
||||||
|
int create_path(const char *path, const mode_t mode) {
|
||||||
|
char tmp[MAX_PATH_LEN];
|
||||||
|
char *p = NULL;
|
||||||
|
struct stat sb;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
// copy path
|
||||||
|
len = strnlen(path, MAX_PATH_LEN);
|
||||||
|
if (len == 0 || len == MAX_PATH_LEN) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memcpy(tmp, path, len);
|
||||||
|
tmp[len] = '\0';
|
||||||
|
|
||||||
|
// remove file part
|
||||||
|
char *last_slash = strrchr(tmp, '/');
|
||||||
|
if (last_slash == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
*last_slash = '\0';
|
||||||
|
|
||||||
|
// check if path exists and is a directory
|
||||||
|
if (stat(tmp, &sb) == 0) {
|
||||||
|
if (S_ISDIR(sb.st_mode)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive mkdir
|
||||||
|
for (p = tmp + 1; *p; p++) {
|
||||||
|
if (*p == '/') {
|
||||||
|
*p = 0;
|
||||||
|
if (stat(tmp, &sb) != 0) {
|
||||||
|
if (pi_mkdir(tmp, mode) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!S_ISDIR(sb.st_mode)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stat(tmp, &sb) != 0) {
|
||||||
|
if (pi_mkdir(tmp, mode) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!S_ISDIR(sb.st_mode)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int copy_out(FILE *src, unsigned int offset, unsigned int size, const char *dest_path) {
|
||||||
|
FILE *dest = fopen(dest_path, "wb");
|
||||||
|
error_if(!dest, "Could not open file %s for writing", dest_path);
|
||||||
|
|
||||||
|
char buffer[BUFFER_SIZE];
|
||||||
|
size_t bytes_read, bytes_written;
|
||||||
|
unsigned int bytes_total = 0;
|
||||||
|
unsigned int read_size = size < BUFFER_SIZE ? size : BUFFER_SIZE;
|
||||||
|
|
||||||
|
fseek(src, offset, SEEK_SET);
|
||||||
|
while (read_size > 0 && (bytes_read = fread(buffer, 1, read_size, src)) > 0) {
|
||||||
|
bytes_written = fwrite(buffer, 1, bytes_read, dest);
|
||||||
|
error_if(bytes_written != bytes_read, "Write error");
|
||||||
|
bytes_total += bytes_written;
|
||||||
|
if (bytes_total >= size) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (size - bytes_total < read_size) {
|
||||||
|
read_size = size - bytes_total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_if(ferror(src), "read error for file %s", dest_path);
|
||||||
|
fclose(dest);
|
||||||
|
return bytes_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unpack(const char *archive_path, int list_only) {
|
||||||
|
qop_desc qop;
|
||||||
|
int archive_size = qop_open(archive_path, &qop);
|
||||||
|
error_if(archive_size == 0, "Could not open archive %s", archive_path);
|
||||||
|
|
||||||
|
// Read the archive index
|
||||||
|
int index_len = qop_read_index(&qop, malloc(qop.hashmap_size));
|
||||||
|
error_if(index_len == 0, "Could not read index from archive %s", archive_path);
|
||||||
|
|
||||||
|
// Extract all files
|
||||||
|
for (unsigned int i = 0; i < qop.hashmap_len; i++) {
|
||||||
|
qop_file *file = &qop.hashmap[i];
|
||||||
|
if (file->size == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
error_if(file->path_len >= MAX_PATH_LEN, "Path for file %016llx exceeds %d", file->hash, MAX_PATH_LEN);
|
||||||
|
char path[MAX_PATH_LEN];
|
||||||
|
qop_read_path(&qop, file, path);
|
||||||
|
|
||||||
|
// Integrity check
|
||||||
|
// error_if(!qop_find(&qop, path), "could not find %s", path);
|
||||||
|
|
||||||
|
printf("%6d %016llx %10d %s\n", i, file->hash, file->size, path);
|
||||||
|
|
||||||
|
if (!list_only) {
|
||||||
|
error_if(create_path(path, 0755) != 0, "Could not create path %s", path);
|
||||||
|
copy_out(qop.fh, qop.files_offset + file->offset + file->path_len, file->size, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(qop.hashmap);
|
||||||
|
qop_close(&qop);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Pack
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
qop_file *files;
|
||||||
|
int len;
|
||||||
|
int capacity;
|
||||||
|
int size;
|
||||||
|
char dest_path[MAX_PATH_LEN];
|
||||||
|
} pack_state;
|
||||||
|
|
||||||
|
int canonicalize_path(const char *path, char *buffer, size_t buffer_len) {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
return _fullpath(buffer, path, buffer_len) != NULL;
|
||||||
|
#else
|
||||||
|
UNUSED(buffer_len);
|
||||||
|
return realpath(path, buffer) != NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_16(unsigned int v, FILE *fh) {
|
||||||
|
unsigned char b[sizeof(unsigned short)];
|
||||||
|
b[0] = 0xff & (v );
|
||||||
|
b[1] = 0xff & (v >> 8);
|
||||||
|
int written = fwrite(b, sizeof(unsigned short), 1, fh);
|
||||||
|
error_if(!written, "Write error");
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_32(unsigned int v, FILE *fh) {
|
||||||
|
unsigned char b[sizeof(unsigned int)];
|
||||||
|
b[0] = 0xff & (v );
|
||||||
|
b[1] = 0xff & (v >> 8);
|
||||||
|
b[2] = 0xff & (v >> 16);
|
||||||
|
b[3] = 0xff & (v >> 24);
|
||||||
|
int written = fwrite(b, sizeof(unsigned int), 1, fh);
|
||||||
|
error_if(!written, "Write error");
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_64(qop_uint64_t v, FILE *fh) {
|
||||||
|
unsigned char b[sizeof(qop_uint64_t)];
|
||||||
|
b[0] = 0xff & (v );
|
||||||
|
b[1] = 0xff & (v >> 8);
|
||||||
|
b[2] = 0xff & (v >> 16);
|
||||||
|
b[3] = 0xff & (v >> 24);
|
||||||
|
b[4] = 0xff & (v >> 32);
|
||||||
|
b[5] = 0xff & (v >> 40);
|
||||||
|
b[6] = 0xff & (v >> 48);
|
||||||
|
b[7] = 0xff & (v >> 56);
|
||||||
|
int written = fwrite(b, sizeof(qop_uint64_t), 1, fh);
|
||||||
|
error_if(!written, "Write error");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int copy_into(const char *src_path, FILE *dest) {
|
||||||
|
FILE *src = fopen(src_path, "rb");
|
||||||
|
error_if(!src, "Could not open file %s for reading", src_path);
|
||||||
|
|
||||||
|
char buffer[BUFFER_SIZE];
|
||||||
|
size_t bytes_read, bytes_written;
|
||||||
|
unsigned int bytes_total = 0;
|
||||||
|
|
||||||
|
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src)) > 0) {
|
||||||
|
bytes_written = fwrite(buffer, 1, bytes_read, dest);
|
||||||
|
error_if(bytes_written != bytes_read, "Write error");
|
||||||
|
bytes_total += bytes_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_if(ferror(src), "read error for file %s", src_path);
|
||||||
|
fclose(src);
|
||||||
|
return bytes_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_file(const char *path, FILE *dest, pack_state *state) {
|
||||||
|
if (state->dest_path[0]) {
|
||||||
|
char absolute[MAX_PATH_LEN];
|
||||||
|
if (canonicalize_path(path, absolute, MAX_PATH_LEN) && strcmp(absolute, state->dest_path) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->len >= state->capacity) {
|
||||||
|
state->capacity *= 2;
|
||||||
|
state->files = realloc(state->files, state->capacity * sizeof(qop_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip leading "./" from path for archive
|
||||||
|
const char *archive_path = path;
|
||||||
|
if (path[0] == '.' && path[1] == '/') {
|
||||||
|
archive_path = path + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
qop_uint64_t hash = qop_hash(archive_path);
|
||||||
|
|
||||||
|
|
||||||
|
// Write the path into the archive
|
||||||
|
int path_len = strlen(archive_path) + 1;
|
||||||
|
int path_written = fwrite(archive_path, sizeof(char), path_len, dest);
|
||||||
|
error_if(path_written != path_len, "Write error");
|
||||||
|
|
||||||
|
// Copy the file into the archive
|
||||||
|
unsigned int size = copy_into(path, dest);
|
||||||
|
|
||||||
|
printf("%6d %016llx %10d %s\n", state->len, hash, size, archive_path);
|
||||||
|
|
||||||
|
// Collect file info for the index
|
||||||
|
state->files[state->len] = (qop_file){
|
||||||
|
.hash = hash,
|
||||||
|
.offset = state->size,
|
||||||
|
.size = size,
|
||||||
|
.path_len = path_len,
|
||||||
|
.flags = QOP_FLAG_NONE
|
||||||
|
};
|
||||||
|
state->size += size + path_len;
|
||||||
|
state->len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_dir(const char *path, FILE *dest, pack_state *state) {
|
||||||
|
pi_dir *dir = pi_dir_open(path);
|
||||||
|
error_if(!dir, "Could not open directory %s for reading", path);
|
||||||
|
|
||||||
|
pi_dirent *entry;
|
||||||
|
while ((entry = pi_dir_next(dir))) {
|
||||||
|
if (
|
||||||
|
entry->is_dir &&
|
||||||
|
strcmp(entry->name, ".") != 0 &&
|
||||||
|
strcmp(entry->name, "..") != 0
|
||||||
|
) {
|
||||||
|
char subpath[MAX_PATH_LEN];
|
||||||
|
snprintf(subpath, MAX_PATH_LEN, "%s/%s", path, entry->name);
|
||||||
|
add_dir(subpath, dest, state);
|
||||||
|
}
|
||||||
|
else if (entry->is_file) {
|
||||||
|
char subpath[MAX_PATH_LEN];
|
||||||
|
snprintf(subpath, MAX_PATH_LEN, "%s/%s", path, entry->name);
|
||||||
|
add_file(subpath, dest, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pi_dir_close(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pack(const char *read_dir, char **sources, int sources_len, const char *archive_path) {
|
||||||
|
FILE *dest = fopen(archive_path, "wb");
|
||||||
|
error_if(!dest, "Could not open file %s for writing", archive_path);
|
||||||
|
|
||||||
|
pack_state state;
|
||||||
|
state.files = malloc(sizeof(qop_file) * 1024);
|
||||||
|
state.len = 0;
|
||||||
|
state.capacity = 1024;
|
||||||
|
state.size = 0;
|
||||||
|
state.dest_path[0] = '\0';
|
||||||
|
if (!canonicalize_path(archive_path, state.dest_path, MAX_PATH_LEN)) {
|
||||||
|
state.dest_path[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read_dir) {
|
||||||
|
error_if(chdir(read_dir) != 0, "Could not change to directory %s", read_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add files/directories
|
||||||
|
for (int i = 0; i < sources_len; i++) {
|
||||||
|
struct stat s;
|
||||||
|
error_if(stat(sources[i], &s) != 0, "Could not stat file %s", sources[i]);
|
||||||
|
if (S_ISDIR(s.st_mode)) {
|
||||||
|
add_dir(sources[i], dest, &state);
|
||||||
|
}
|
||||||
|
else if (S_ISREG(s.st_mode)) {
|
||||||
|
add_file(sources[i], dest, &state);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
die("Path %s is neither a directory nor a regular file", sources[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write index and header
|
||||||
|
unsigned int total_size = state.size + QOP_HEADER_SIZE;
|
||||||
|
for (int i = 0; i < state.len; i++) {
|
||||||
|
write_64(state.files[i].hash, dest);
|
||||||
|
write_32(state.files[i].offset, dest);
|
||||||
|
write_32(state.files[i].size, dest);
|
||||||
|
write_16(state.files[i].path_len, dest);
|
||||||
|
write_16(state.files[i].flags, dest);
|
||||||
|
total_size += 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_32(state.len, dest);
|
||||||
|
write_32(total_size, dest);
|
||||||
|
write_32(QOP_MAGIC, dest);
|
||||||
|
|
||||||
|
free(state.files);
|
||||||
|
fclose(dest);
|
||||||
|
|
||||||
|
printf("files: %d, size: %d bytes\n", state.len, total_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit_usage(void) {
|
||||||
|
puts(
|
||||||
|
"Usage: qopconv [OPTION...] FILE...\n"
|
||||||
|
"\n"
|
||||||
|
"Examples:\n"
|
||||||
|
" qopconv dir1 archive.qop # Create archive.qop from dir1/\n"
|
||||||
|
" qopconv foo bar archive.qop # Create archive.qop from files foo and bar\n"
|
||||||
|
" qoponvv -u archive.qop # Unpack archive.qop in current directory\n"
|
||||||
|
" qopconv -l archive.qop # List files in archive.qop\n"
|
||||||
|
" qopconv -d dir1 dir2 archive.qop # Use dir1 prefix for reading, create\n"
|
||||||
|
" archive.qop from files in dir1/dir2/\n"
|
||||||
|
"\n"
|
||||||
|
"Options (mutually exclusive):\n"
|
||||||
|
" -u <archive> ... unpack archive\n"
|
||||||
|
" -l <archive> ... list contents of archive\n"
|
||||||
|
" -d <dir> ....... change read dir when creating archives\n"
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 3) {
|
||||||
|
exit_usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack
|
||||||
|
if (strcmp(argv[1], "-u") == 0) {
|
||||||
|
unpack(argv[2], 0);
|
||||||
|
}
|
||||||
|
else if (strcmp(argv[1], "-l") == 0) {
|
||||||
|
unpack(argv[2], 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int files_start = 1;
|
||||||
|
char *read_dir = NULL;
|
||||||
|
if (strcmp(argv[1], "-d") == 0) {
|
||||||
|
read_dir = argv[2];
|
||||||
|
files_start = 3;
|
||||||
|
}
|
||||||
|
if (argc < 2 + files_start) {
|
||||||
|
exit_usage();
|
||||||
|
}
|
||||||
|
pack(read_dir, argv + files_start, argc - 1 - files_start, argv[argc-1]);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -71,6 +71,7 @@ var use_embed = hidden.use_embed
|
|||||||
var use_dyn = hidden.use_dyn
|
var use_dyn = hidden.use_dyn
|
||||||
var enet = hidden.enet
|
var enet = hidden.enet
|
||||||
var nota = hidden.nota
|
var nota = hidden.nota
|
||||||
|
var fd = use_embed('fd')
|
||||||
|
|
||||||
// Wota decode timing tracking
|
// Wota decode timing tracking
|
||||||
var wota_decode_times = []
|
var wota_decode_times = []
|
||||||
@@ -109,16 +110,33 @@ actor_mod.on_exception(disrupt)
|
|||||||
var js = use_embed('js')
|
var js = use_embed('js')
|
||||||
var io = use_embed('io')
|
var io = use_embed('io')
|
||||||
|
|
||||||
if (!io.exists('.cell')) {
|
//log.console(json.encode(fd))
|
||||||
console_mod.print("No cell directory found. Make one.\n");
|
//log.console(fd.fstat)
|
||||||
|
//log.console(json.encode(fd.fstat('.cell')))
|
||||||
|
//log.console(fd.fstat('.cell').isDirectory)
|
||||||
|
|
||||||
|
if (!fd.stat('.cell').isDirectory) {
|
||||||
|
log.console("No cell directory found. Make one.\n");
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function slurp(path) {
|
||||||
|
var st = fd.stat(path)
|
||||||
|
if (!st.isFile) return null
|
||||||
|
|
||||||
|
var fd_handle = fd.open(path, 'r')
|
||||||
|
var content_blob = fd.read(fd_handle, st.size)
|
||||||
|
fd.close(fd_handle)
|
||||||
|
|
||||||
|
return text(content_blob)
|
||||||
|
}
|
||||||
|
|
||||||
var module_alias = {}
|
var module_alias = {}
|
||||||
var use_cache = {}
|
var use_cache = {}
|
||||||
|
|
||||||
var BASEPATH = 'base' + MOD_EXT
|
var BASEPATH = 'base' + MOD_EXT
|
||||||
var script = io.slurp(BASEPATH)
|
var script = slurp(BASEPATH)
|
||||||
|
log.console(script)
|
||||||
var fnname = "base"
|
var fnname = "base"
|
||||||
script = `(function ${fnname}() { ${script}; })`
|
script = `(function ${fnname}() { ${script}; })`
|
||||||
js.eval(BASEPATH, script)()
|
js.eval(BASEPATH, script)()
|
||||||
@@ -268,7 +286,9 @@ config.system.__proto__ = default_config
|
|||||||
|
|
||||||
ENETSERVICE = config.system.net_service
|
ENETSERVICE = config.system.net_service
|
||||||
REPLYTIMEOUT = config.system.reply_timeout
|
REPLYTIMEOUT = config.system.reply_timeout
|
||||||
|
|
||||||
|
log.console(`config loaded in ${time.number()-st_now} seconds`)
|
||||||
|
|
||||||
globalThis.text = use('text')
|
globalThis.text = use('text')
|
||||||
|
|
||||||
// Load actor-specific configuration
|
// Load actor-specific configuration
|
||||||
@@ -312,6 +332,8 @@ function deepFreeze(object) {
|
|||||||
return Object.freeze(object);
|
return Object.freeze(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.console(`stone initialized in ${time.number()-st_now} seconds`)
|
||||||
|
|
||||||
globalThis.stone = deepFreeze
|
globalThis.stone = deepFreeze
|
||||||
stone.p = function(object)
|
stone.p = function(object)
|
||||||
{
|
{
|
||||||
@@ -681,6 +703,8 @@ load_actor_config(cell.args.program)
|
|||||||
|
|
||||||
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer)
|
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer)
|
||||||
|
|
||||||
|
log.console(`actor registered in ${time.number()-st_now} seconds`)
|
||||||
|
|
||||||
if (config.system.actor_memory)
|
if (config.system.actor_memory)
|
||||||
js.mem_limit(config.system.actor_memory)
|
js.mem_limit(config.system.actor_memory)
|
||||||
|
|
||||||
@@ -829,6 +853,9 @@ var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var ar
|
|||||||
// queue up its first turn instead of run immediately
|
// queue up its first turn instead of run immediately
|
||||||
|
|
||||||
var startfn = js.eval(cell.args.program, prog_script);
|
var startfn = js.eval(cell.args.program, prog_script);
|
||||||
|
|
||||||
|
log.console(`program compiled in ${time.number()-st_now} seconds`)
|
||||||
|
|
||||||
$_.clock(_ => {
|
$_.clock(_ => {
|
||||||
var val = startfn($_, cell.args.arg);
|
var val = startfn($_, cell.args.arg);
|
||||||
|
|
||||||
@@ -836,6 +863,6 @@ $_.clock(_ => {
|
|||||||
throw new Error('Program must not return anything');
|
throw new Error('Program must not return anything');
|
||||||
})
|
})
|
||||||
|
|
||||||
log.console(`startup took ${time.number()-st_now}`)
|
log.console(`program executed in ${time.number()-st_now} seconds`)
|
||||||
|
|
||||||
})()
|
})()
|
||||||
181
source/cell.c
181
source/cell.c
@@ -52,11 +52,15 @@
|
|||||||
#elif defined(__linux__) || defined(__GLIBC__)
|
#elif defined(__linux__) || defined(__GLIBC__)
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
|
#include <unistd.h>
|
||||||
#define MALLOC_OVERHEAD 8
|
#define MALLOC_OVERHEAD 8
|
||||||
#else
|
#else
|
||||||
#define MALLOC_OVERHEAD 0
|
#define MALLOC_OVERHEAD 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define QOP_IMPLEMENTATION
|
||||||
|
#include "qop.h"
|
||||||
|
|
||||||
#define likely(x) __builtin_expect(!!(x), 1)
|
#define likely(x) __builtin_expect(!!(x), 1)
|
||||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||||
|
|
||||||
@@ -76,6 +80,8 @@ static SDL_Mutex *actors_mutex = NULL;
|
|||||||
static struct { char *key; cell_rt *value; } *actors = NULL;
|
static struct { char *key; cell_rt *value; } *actors = NULL;
|
||||||
static unsigned char *zip_buffer_global = NULL;
|
static unsigned char *zip_buffer_global = NULL;
|
||||||
static char *prosperon = NULL;
|
static char *prosperon = NULL;
|
||||||
|
static qop_desc qop_core;
|
||||||
|
static qop_file *qop_hashmap = NULL;
|
||||||
cell_rt *root_cell = NULL;
|
cell_rt *root_cell = NULL;
|
||||||
|
|
||||||
static SDL_AtomicInt shutting_down;
|
static SDL_AtomicInt shutting_down;
|
||||||
@@ -110,6 +116,13 @@ static void exit_handler(void)
|
|||||||
if (actors_mutex)
|
if (actors_mutex)
|
||||||
SDL_DestroyMutex(actors_mutex);
|
SDL_DestroyMutex(actors_mutex);
|
||||||
|
|
||||||
|
/* Clean up QOP resources */
|
||||||
|
if (qop_hashmap) {
|
||||||
|
free(qop_hashmap);
|
||||||
|
qop_hashmap = NULL;
|
||||||
|
}
|
||||||
|
qop_close(&qop_core);
|
||||||
|
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@@ -348,72 +361,59 @@ static void free_zip(void)
|
|||||||
zip_buffer_global = NULL;
|
zip_buffer_global = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_executable_path(char *buffer, unsigned int buffer_size) {
|
||||||
|
#if defined(__linux__)
|
||||||
|
ssize_t len = readlink("/proc/self/exe", buffer, buffer_size - 1);
|
||||||
|
if (len == -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
buffer[len] = '\0';
|
||||||
|
return len;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
if (_NSGetExecutablePath(buffer, &buffer_size) == 0) {
|
||||||
|
return buffer_size;
|
||||||
|
}
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
return GetModuleFileName(NULL, buffer, buffer_size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int prosperon_mount_core(void)
|
int prosperon_mount_core(void)
|
||||||
{
|
{
|
||||||
size_t size;
|
|
||||||
char exe_path[PATH_MAX];
|
char exe_path[PATH_MAX];
|
||||||
|
int exe_path_len = get_executable_path(exe_path, sizeof(exe_path));
|
||||||
// Get the full path of the executable
|
if (exe_path_len == 0) {
|
||||||
const char *base_dir = PHYSFS_getBaseDir();
|
printf("ERROR: Could not get executable path\n");
|
||||||
if (base_dir) {
|
|
||||||
snprintf(exe_path, sizeof(exe_path), "%s%s", base_dir, PHYSFS_getDirSeparator());
|
|
||||||
|
|
||||||
// Extract just the executable name from argv[0]
|
|
||||||
const char *exe_name = strrchr(prosperon, '/');
|
|
||||||
if (!exe_name) exe_name = strrchr(prosperon, '\\');
|
|
||||||
if (exe_name) exe_name++; else exe_name = prosperon;
|
|
||||||
|
|
||||||
strncat(exe_path, exe_name, sizeof(exe_path) - strlen(exe_path) - 1);
|
|
||||||
} else {
|
|
||||||
strncpy(exe_path, prosperon, sizeof(exe_path) - 1);
|
|
||||||
exe_path[sizeof(exe_path) - 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *f = fopen(exe_path, "rb");
|
|
||||||
if (!f) return perror("fopen"), 0;
|
|
||||||
if (fseek(f, 0, SEEK_END) != 0) return perror("fseek"), fclose(f), 0;
|
|
||||||
size = ftell(f);
|
|
||||||
if (size < 0) return perror("ftell"), fclose(f), 0;
|
|
||||||
zip_buffer_global = malloc(size);
|
|
||||||
if (!zip_buffer_global) return perror("malloc"), fclose(f), 0;
|
|
||||||
rewind(f);
|
|
||||||
if (fread(zip_buffer_global, 1, size, f) != size) {
|
|
||||||
perror("fread");
|
|
||||||
free(zip_buffer_global);
|
|
||||||
fclose(f);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
|
|
||||||
long max_comment_len = 0xFFFF;
|
|
||||||
long eocd_search_start = (size > max_comment_len + 22) ? size - (max_comment_len + 22) : 0;
|
|
||||||
long eocd_pos = -1;
|
|
||||||
for (long i = size - 22; i >= eocd_search_start; i--)
|
|
||||||
if (zip_buffer_global[i] == 'P' && zip_buffer_global[i + 1] == 'K' &&
|
|
||||||
zip_buffer_global[i + 2] == 0x05 && zip_buffer_global[i + 3] == 0x06) {
|
|
||||||
eocd_pos = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (eocd_pos < 0) {
|
|
||||||
free(zip_buffer_global);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t comment_length = zip_buffer_global[eocd_pos + 20] | (zip_buffer_global[eocd_pos + 21] << 8);
|
// Open the QOP archive appended to this executable
|
||||||
int eocd_size = 22 + comment_length;
|
int archive_size = qop_open(exe_path, &qop_core);
|
||||||
uint32_t cd_size = zip_buffer_global[eocd_pos + 12] | (zip_buffer_global[eocd_pos + 13] << 8) |
|
if (archive_size == 0) {
|
||||||
(zip_buffer_global[eocd_pos + 14] << 16) | (zip_buffer_global[eocd_pos + 15] << 24);
|
printf("ERROR: Could not open QOP archive\n");
|
||||||
uint32_t cd_offset_rel = zip_buffer_global[eocd_pos + 16] | (zip_buffer_global[eocd_pos + 17] << 8) |
|
return 0;
|
||||||
(zip_buffer_global[eocd_pos + 18] << 16) | (zip_buffer_global[eocd_pos + 19] << 24);
|
|
||||||
uint32_t appended_zip_size = cd_offset_rel + cd_size + eocd_size;
|
|
||||||
long zip_offset = size - appended_zip_size;
|
|
||||||
if (zip_offset < 0 || zip_offset >= size) return fprintf(stderr, "Invalid zip offset: %ld\n", zip_offset), free(zip_buffer_global), 0;
|
|
||||||
|
|
||||||
int ret = PHYSFS_mountMemory(zip_buffer_global + zip_offset, appended_zip_size, free_zip, "core.zip", NULL, 0);
|
|
||||||
if (!ret) {
|
|
||||||
printf("COULD NOT MOUNT! Reason: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
|
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
|
// Read the archive index
|
||||||
|
qop_hashmap = malloc(qop_core.hashmap_size);
|
||||||
|
if (!qop_hashmap) {
|
||||||
|
printf("ERROR: Could not allocate memory for QOP hashmap\n");
|
||||||
|
qop_close(&qop_core);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index_len = qop_read_index(&qop_core, qop_hashmap);
|
||||||
|
if (index_len == 0) {
|
||||||
|
printf("ERROR: Could not read QOP index\n");
|
||||||
|
free(qop_hashmap);
|
||||||
|
qop_hashmap = NULL;
|
||||||
|
qop_close(&qop_core);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||||
@@ -814,20 +814,29 @@ void script_startup(cell_rt *prt)
|
|||||||
prt->context = js;
|
prt->context = js;
|
||||||
ffi_load(js);
|
ffi_load(js);
|
||||||
|
|
||||||
PHYSFS_File *eng = PHYSFS_openRead(ENGINE);
|
// Find and load engine.cm from QOP archive
|
||||||
if (!eng) {
|
qop_file *engine_file = qop_find(&qop_core, ENGINE);
|
||||||
printf("ERROR: Could not open file %s! %s\n", ENGINE, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
|
if (!engine_file) {
|
||||||
|
printf("ERROR: Could not find file %s in QOP archive!\n", ENGINE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PHYSFS_Stat stat;
|
|
||||||
PHYSFS_stat(ENGINE, &stat);
|
char *data = malloc(engine_file->size + 1);
|
||||||
char *data = malloc(stat.filesize+1);
|
if (!data) {
|
||||||
PHYSFS_readBytes(eng, data, stat.filesize);
|
printf("ERROR: Could not allocate memory for %s!\n", ENGINE);
|
||||||
PHYSFS_close(eng);
|
return;
|
||||||
data[stat.filesize] = 0;
|
}
|
||||||
|
|
||||||
|
int bytes_read = qop_read(&qop_core, engine_file, (unsigned char *)data);
|
||||||
|
if (bytes_read != (int)engine_file->size) {
|
||||||
|
printf("ERROR: Could not read file %s from QOP archive!\n", ENGINE);
|
||||||
|
free(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[engine_file->size] = 0;
|
||||||
|
|
||||||
prt->state = ACTOR_RUNNING;
|
prt->state = ACTOR_RUNNING;
|
||||||
JSValue v = JS_Eval(js, data, (size_t)stat.filesize, ENGINE, 0);
|
JSValue v = JS_Eval(js, data, (size_t)engine_file->size, ENGINE, 0);
|
||||||
uncaught_exception(js, v);
|
uncaught_exception(js, v);
|
||||||
prt->state = ACTOR_IDLE;
|
prt->state = ACTOR_IDLE;
|
||||||
set_actor_state(prt);
|
set_actor_state(prt);
|
||||||
@@ -947,43 +956,13 @@ int main(int argc, char **argv)
|
|||||||
prosperon = argv[0];
|
prosperon = argv[0];
|
||||||
PHYSFS_init(argv[0]);
|
PHYSFS_init(argv[0]);
|
||||||
|
|
||||||
/* Mount core.zip attached to executable - this is now mandatory! */
|
/* Load QOP package attached to executable - this is now mandatory! */
|
||||||
int mounted = prosperon_mount_core();
|
int mounted = prosperon_mount_core();
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
printf("ERROR: Could not mount core. Reason: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
|
printf("ERROR: Could not load core QOP package.\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check for .cell directory in the current directory */
|
|
||||||
char *cell_parent_dir = NULL;
|
|
||||||
struct stat st;
|
|
||||||
char *current_dir = SDL_GetCurrentDirectory();
|
|
||||||
char test_path[PATH_MAX];
|
|
||||||
snprintf(test_path, sizeof(test_path), "%s/.cell", current_dir);
|
|
||||||
if (stat(test_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
||||||
cell_parent_dir = strdup(current_dir);
|
|
||||||
}
|
|
||||||
SDL_free(current_dir);
|
|
||||||
|
|
||||||
if (cell_parent_dir) {
|
|
||||||
/* 1) Strip any trailing slash from cell_parent_dir (except if it's just "/") */
|
|
||||||
size_t proj_len = strlen(cell_parent_dir);
|
|
||||||
if (proj_len > 1 && cell_parent_dir[proj_len - 1] == '/')
|
|
||||||
cell_parent_dir[proj_len - 1] = '\0';
|
|
||||||
|
|
||||||
char cellpath[PATH_MAX];
|
|
||||||
snprintf(cellpath, sizeof(cellpath), "%s/.cell/modules", cell_parent_dir);
|
|
||||||
|
|
||||||
PHYSFS_mount(cellpath, NULL, 0);
|
|
||||||
PHYSFS_mount(cell_parent_dir, NULL, 0);
|
|
||||||
PHYSFS_setWriteDir(cell_parent_dir);
|
|
||||||
|
|
||||||
free(cell_parent_dir);
|
|
||||||
} else {
|
|
||||||
printf("Cell requires a .cell folder to run.\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create the initial actor from the command line */
|
/* Create the initial actor from the command line */
|
||||||
int actor_argc = argc - script_start;
|
int actor_argc = argc - script_start;
|
||||||
char **actor_argv = argv + script_start;
|
char **actor_argv = argv + script_start;
|
||||||
|
|||||||
@@ -180,7 +180,8 @@ JSC_CCALL(fd_fstat,
|
|||||||
struct stat st;
|
struct stat st;
|
||||||
if (fstat(fd, &st) != 0)
|
if (fstat(fd, &st) != 0)
|
||||||
return JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno));
|
return JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno));
|
||||||
|
|
||||||
|
printf("fstat on %s\n", argv[0]);
|
||||||
JSValue obj = JS_NewObject(js);
|
JSValue obj = JS_NewObject(js);
|
||||||
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
|
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
|
||||||
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode));
|
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode));
|
||||||
@@ -213,6 +214,42 @@ JSC_CCALL(fd_fstat,
|
|||||||
return obj;
|
return obj;
|
||||||
)
|
)
|
||||||
|
|
||||||
|
JSC_SCALL(fd_stat,
|
||||||
|
struct stat st;
|
||||||
|
if (stat(str, &st) != 0)
|
||||||
|
ret = JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno));
|
||||||
|
|
||||||
|
JSValue obj = JS_NewObject(js);
|
||||||
|
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
|
||||||
|
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode));
|
||||||
|
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid));
|
||||||
|
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid));
|
||||||
|
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime));
|
||||||
|
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime));
|
||||||
|
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime));
|
||||||
|
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink));
|
||||||
|
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino));
|
||||||
|
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev));
|
||||||
|
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev));
|
||||||
|
#ifndef _WIN32
|
||||||
|
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize));
|
||||||
|
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks));
|
||||||
|
#else
|
||||||
|
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096));
|
||||||
|
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Add boolean properties for file type
|
||||||
|
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
|
||||||
|
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
|
||||||
|
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
|
||||||
|
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
|
||||||
|
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
|
||||||
|
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
|
||||||
|
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
)
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_fd_funcs[] = {
|
static const JSCFunctionListEntry js_fd_funcs[] = {
|
||||||
MIST_FUNC_DEF(fd, open, 2),
|
MIST_FUNC_DEF(fd, open, 2),
|
||||||
@@ -224,6 +261,7 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
|
|||||||
MIST_FUNC_DEF(fd, mkdir, 1),
|
MIST_FUNC_DEF(fd, mkdir, 1),
|
||||||
MIST_FUNC_DEF(fd, fsync, 1),
|
MIST_FUNC_DEF(fd, fsync, 1),
|
||||||
MIST_FUNC_DEF(fd, close, 1),
|
MIST_FUNC_DEF(fd, close, 1),
|
||||||
|
MIST_FUNC_DEF(fd, stat, 1),
|
||||||
MIST_FUNC_DEF(fd, fstat, 1),
|
MIST_FUNC_DEF(fd, fstat, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "qjs_qop.h"
|
#include "qjs_qop.h"
|
||||||
#define QOP_IMPLEMENTATION
|
|
||||||
#include "qop.h"
|
#include "qop.h"
|
||||||
#include "qjs_blob.h"
|
#include "qjs_blob.h"
|
||||||
#include "jsffi.h"
|
#include "jsffi.h"
|
||||||
|
|||||||
Reference in New Issue
Block a user