Add writing to existing files and overwriting cp
This commit is contained in:
@@ -21,3 +21,4 @@ mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/shell ::bin/s
|
|||||||
mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/echo ::bin/echo
|
mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/echo ::bin/echo
|
||||||
mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/ls ::bin/ls
|
mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/ls ::bin/ls
|
||||||
mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/cat ::bin/cat
|
mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/cat ::bin/cat
|
||||||
|
mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/cp ::bin/cp
|
||||||
|
|||||||
+39
@@ -310,3 +310,42 @@ custom_target(
|
|||||||
command: ['objcopy', '-O', 'binary', '@INPUT@', '@OUTPUT@'],
|
command: ['objcopy', '-O', 'binary', '@INPUT@', '@OUTPUT@'],
|
||||||
build_by_default: true,
|
build_by_default: true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cp_start = custom_target(
|
||||||
|
'cp_start',
|
||||||
|
input: 'src/user/start.asm',
|
||||||
|
output: 'cp_start.o',
|
||||||
|
command: [nasm, '-f', 'elf64', '-o', '@OUTPUT@', '@INPUT@'],
|
||||||
|
)
|
||||||
|
|
||||||
|
cp_syscall = custom_target(
|
||||||
|
'cp_syscall',
|
||||||
|
input: 'src/user/syscall.asm',
|
||||||
|
output: 'cp_syscall.o',
|
||||||
|
command: [nasm, '-f', 'elf64', '-o', '@OUTPUT@', '@INPUT@'],
|
||||||
|
)
|
||||||
|
|
||||||
|
cp_elf = executable(
|
||||||
|
'cp.elf',
|
||||||
|
sources: [cp_start, cp_syscall, lib_sources, 'src/user/app/cp/cp.c'],
|
||||||
|
c_args: [
|
||||||
|
'-ffreestanding',
|
||||||
|
'-nostdlib',
|
||||||
|
'-fno-stack-protector',
|
||||||
|
'-mcmodel=large',
|
||||||
|
'-DVERSION="' + meson.project_version() + '"',
|
||||||
|
],
|
||||||
|
link_args: [
|
||||||
|
'-T', meson.project_source_root() / 'src/user/linker.ld',
|
||||||
|
'-nostdlib',
|
||||||
|
],
|
||||||
|
link_depends: 'src/user/linker.ld',
|
||||||
|
)
|
||||||
|
|
||||||
|
custom_target(
|
||||||
|
'cp',
|
||||||
|
input: cp_elf,
|
||||||
|
output: 'cp',
|
||||||
|
command: ['objcopy', '-O', 'binary', '@INPUT@', '@OUTPUT@'],
|
||||||
|
build_by_default: true,
|
||||||
|
)
|
||||||
|
|||||||
+313
-115
@@ -33,15 +33,96 @@ typedef struct __attribute__((packed)) {
|
|||||||
uint32_t size;
|
uint32_t size;
|
||||||
} fat16_dir_entry_t;
|
} fat16_dir_entry_t;
|
||||||
|
|
||||||
|
#define MAX_INODES 256
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t refs;
|
||||||
|
uint16_t first_cluster;
|
||||||
|
uint32_t size;
|
||||||
|
uint16_t dir_cluster;
|
||||||
|
uint16_t dir_index;
|
||||||
|
uint8_t valid;
|
||||||
|
} fat16_inode_t;
|
||||||
|
|
||||||
|
static fat16_inode_t inodes[MAX_INODES];
|
||||||
|
|
||||||
|
static fat16_inode_t *inode_create(uint16_t first_cluster, uint32_t size, uint16_t dir_cluster, uint16_t dir_index) {
|
||||||
|
for (uint64_t i = 0; i < MAX_INODES; i++) {
|
||||||
|
if (!inodes[i].valid) {
|
||||||
|
inodes[i] = (fat16_inode_t){
|
||||||
|
.refs = 1,
|
||||||
|
.first_cluster = first_cluster,
|
||||||
|
.size = size,
|
||||||
|
.dir_cluster = dir_cluster,
|
||||||
|
.dir_index = dir_index,
|
||||||
|
.valid = 1,
|
||||||
|
};
|
||||||
|
return &inodes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NUL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fat16_inode_t *inode_get(uint32_t dir_cluster, uint16_t dir_index) {
|
||||||
|
for (uint64_t i = 0; i < MAX_INODES; i++) {
|
||||||
|
if (inodes[i].valid && inodes[i].dir_cluster == dir_cluster && inodes[i].dir_index == dir_index) {
|
||||||
|
return &inodes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NUL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fat16_inode_t *inode_use(uint16_t first_cluster, uint32_t size, uint16_t dir_cluster, uint16_t dir_index) {
|
||||||
|
fat16_inode_t *result = inode_get(dir_cluster, dir_index);
|
||||||
|
if (!result) {
|
||||||
|
result = inode_create(first_cluster, size, dir_cluster, dir_index);
|
||||||
|
} else {
|
||||||
|
result->refs++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void inode_free(fat16_inode_t *inode) {
|
||||||
|
inode->refs--;
|
||||||
|
if (!inode->refs) {
|
||||||
|
for (uint64_t i = 0; i < MAX_INODES; i++) {
|
||||||
|
if (&inodes[i] == inode) {
|
||||||
|
inodes[i].valid = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
fs_node_t base;
|
fs_node_t base;
|
||||||
fat16_dir_entry_t entry;
|
fat16_inode_t *inode;
|
||||||
} fat16_node_t;
|
} fat16_node_t;
|
||||||
|
|
||||||
static fat16_bpb_t bpb; // Assuming one partition.
|
static fat16_bpb_t bpb; // Assuming one partition.
|
||||||
|
static uint32_t data_start;
|
||||||
static fs_node_t *fs = NUL;
|
static fs_node_t *fs = NUL;
|
||||||
static uint16_t *fat = NUL;
|
static uint16_t *fat = NUL;
|
||||||
|
|
||||||
|
fs_node_t *fat16_mount() { // Assuming one partition.
|
||||||
|
uint8_t sector[512];
|
||||||
|
ata_read_sectors(FIRST_PARTITION_SECTOR, 1, §or);
|
||||||
|
memory_copy(sector + 11, sizeof(fat16_bpb_t), &bpb);
|
||||||
|
data_start = FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count +
|
||||||
|
(bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE;
|
||||||
|
fat16_node_t *node = memory_allocate(sizeof(fat16_node_t));
|
||||||
|
node->base.type = FAT16;
|
||||||
|
node->base.name[0] = '/';
|
||||||
|
node->base.size = sizeof(fat16_dir_entry_t) * bpb.root_entry_count;
|
||||||
|
node->base.is_dir = 1;
|
||||||
|
node->inode = inode_use(0, node->base.size, 0, UINT16_MAX);
|
||||||
|
if (!node->inode) {
|
||||||
|
memory_free(node);
|
||||||
|
return NUL;
|
||||||
|
}
|
||||||
|
return fs = (fs_node_t *)node;
|
||||||
|
}
|
||||||
|
|
||||||
static void to_8_3(const char *name, char *output) {
|
static void to_8_3(const char *name, char *output) {
|
||||||
memory_set(' ', 11, output);
|
memory_set(' ', 11, output);
|
||||||
output[11] = '\0';
|
output[11] = '\0';
|
||||||
@@ -75,18 +156,6 @@ static void from_8_3(const char *name, const char *extension, char *output) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs_node_t *fat16_mount() { // Assuming one partition.
|
|
||||||
uint8_t sector[512];
|
|
||||||
ata_read_sectors(FIRST_PARTITION_SECTOR, 1, §or);
|
|
||||||
memory_copy(sector + 11, sizeof(fat16_bpb_t), &bpb);
|
|
||||||
fat16_node_t *node = memory_allocate(sizeof(fat16_node_t));
|
|
||||||
node->base.type = FAT16;
|
|
||||||
node->base.name[0] = node->entry.name[0] = '/';
|
|
||||||
node->base.size = node->entry.size = sizeof(fat16_dir_entry_t) * bpb.root_entry_count;
|
|
||||||
node->base.is_dir = 1;
|
|
||||||
return fs = (fs_node_t *)node;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ensure_fat() {
|
static void ensure_fat() {
|
||||||
ASSERT(bpb.sectors_per_fat < 256, "ensure_fat: big FAT not implemented")
|
ASSERT(bpb.sectors_per_fat < 256, "ensure_fat: big FAT not implemented")
|
||||||
if (!fat) {
|
if (!fat) {
|
||||||
@@ -95,17 +164,32 @@ static void ensure_fat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fat16_dir_entry_t *load_directory(fat16_node_t *directory) {
|
static uint16_t allocate_cluster() {
|
||||||
|
ensure_fat();
|
||||||
|
for (uint16_t i = 2; i < bpb.sectors_per_fat * SECTOR_SIZE / 2; i++) {
|
||||||
|
if (fat[i] == 0x0000) {
|
||||||
|
fat[i] = 0xFFFF;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void flush_fat() {
|
||||||
|
ata_write_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors, (uint8_t)bpb.sectors_per_fat, fat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fat16_dir_entry_t *read_directory(uint16_t dir_cluster) {
|
||||||
fat16_dir_entry_t *entries;
|
fat16_dir_entry_t *entries;
|
||||||
|
|
||||||
if (!directory->entry.first_cluster) {
|
if (!dir_cluster) {
|
||||||
uint8_t sectors = (uint8_t)((directory->base.size + SECTOR_SIZE - 1) / SECTOR_SIZE);
|
uint8_t sectors = (uint8_t)((sizeof(fat16_dir_entry_t) * bpb.root_entry_count + SECTOR_SIZE - 1) / SECTOR_SIZE);
|
||||||
entries = memory_allocate(sectors * SECTOR_SIZE + sizeof(fat16_dir_entry_t)); // One extra as null terminator.
|
entries = memory_allocate(sectors * SECTOR_SIZE + sizeof(fat16_dir_entry_t)); // One extra as null terminator.
|
||||||
ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.fats_count * bpb.sectors_per_fat, sectors, entries);
|
ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.fats_count * bpb.sectors_per_fat, sectors, entries);
|
||||||
} else {
|
} else {
|
||||||
ensure_fat();
|
ensure_fat();
|
||||||
|
|
||||||
uint16_t next_cluster = directory->entry.first_cluster;
|
uint16_t next_cluster = dir_cluster;
|
||||||
uint32_t cluster_count = 0;
|
uint32_t cluster_count = 0;
|
||||||
while (next_cluster < 0xFFF8) {
|
while (next_cluster < 0xFFF8) {
|
||||||
cluster_count++;
|
cluster_count++;
|
||||||
@@ -115,12 +199,9 @@ static fat16_dir_entry_t *load_directory(fat16_node_t *directory) {
|
|||||||
memory_allocate(cluster_count * bpb.sectors_per_cluster * SECTOR_SIZE + sizeof(fat16_dir_entry_t)); // One extra as null terminator.
|
memory_allocate(cluster_count * bpb.sectors_per_cluster * SECTOR_SIZE + sizeof(fat16_dir_entry_t)); // One extra as null terminator.
|
||||||
|
|
||||||
uint8_t *chunk = (uint8_t *)entries;
|
uint8_t *chunk = (uint8_t *)entries;
|
||||||
next_cluster = directory->entry.first_cluster;
|
next_cluster = dir_cluster;
|
||||||
while (next_cluster < 0xFFF8) {
|
while (next_cluster < 0xFFF8) {
|
||||||
ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count +
|
ata_read_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, chunk);
|
||||||
(bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE +
|
|
||||||
bpb.sectors_per_cluster * (next_cluster - 2),
|
|
||||||
bpb.sectors_per_cluster, chunk);
|
|
||||||
chunk += bpb.sectors_per_cluster * SECTOR_SIZE;
|
chunk += bpb.sectors_per_cluster * SECTOR_SIZE;
|
||||||
next_cluster = fat[next_cluster];
|
next_cluster = fat[next_cluster];
|
||||||
}
|
}
|
||||||
@@ -129,50 +210,80 @@ static fat16_dir_entry_t *load_directory(fat16_node_t *directory) {
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fs_node_t *open_entry(const char *name, const fat16_dir_entry_t *entry) {
|
static fs_node_t *open_entry(uint16_t dir_cluster, const fat16_dir_entry_t *entries, uint16_t index) {
|
||||||
|
const fat16_dir_entry_t *entry = &entries[index];
|
||||||
|
|
||||||
if (!entry->name[0]) {
|
if (!entry->name[0]) {
|
||||||
return NUL;
|
return NUL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fat16_node_t *result = memory_allocate(sizeof(fat16_node_t));
|
fat16_node_t *result = memory_allocate(sizeof(fat16_node_t));
|
||||||
memory_copy((char *)name, string_length(name) + 1, &(result->base.name));
|
|
||||||
result->base.type = FAT16;
|
result->base.type = FAT16;
|
||||||
|
from_8_3(entry->name, entry->ext, result->base.name);
|
||||||
|
|
||||||
result->base.size = entry->size;
|
result->base.size = entry->size;
|
||||||
result->base.is_dir = entry->attributes & ATTRIBUTE_SUBDIRECTORY;
|
result->base.is_dir = entry->attributes & ATTRIBUTE_SUBDIRECTORY;
|
||||||
memory_copy((fat16_dir_entry_t *)entry, sizeof(fat16_dir_entry_t), (uint8_t *)result + sizeof(fs_node_t));
|
result->inode = inode_use(entry->first_cluster, entry->size, dir_cluster, index);
|
||||||
|
|
||||||
|
if (!result->inode) {
|
||||||
|
memory_free(result);
|
||||||
|
return NUL;
|
||||||
|
}
|
||||||
|
|
||||||
return (fs_node_t *)result;
|
return (fs_node_t *)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void write_directory(uint16_t dir_cluster, const fat16_dir_entry_t *entries) {
|
||||||
|
if (!dir_cluster) {
|
||||||
|
ata_write_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.fats_count * bpb.sectors_per_fat,
|
||||||
|
(uint8_t)((sizeof(fat16_dir_entry_t) * bpb.root_entry_count + SECTOR_SIZE - 1) / SECTOR_SIZE), entries);
|
||||||
|
} else {
|
||||||
|
ensure_fat();
|
||||||
|
|
||||||
|
// Assuming no entries were added or removed.
|
||||||
|
uint8_t *chunk = (uint8_t *)entries;
|
||||||
|
uint16_t next_cluster = dir_cluster;
|
||||||
|
while (next_cluster < 0xFFF8) {
|
||||||
|
ata_write_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, chunk);
|
||||||
|
chunk += bpb.sectors_per_cluster * SECTOR_SIZE;
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fs_node_t *fat16_open_by(const fs_node_t *directory, const char *name) {
|
fs_node_t *fat16_open_by(const fs_node_t *directory, const char *name) {
|
||||||
ASSERT(directory->type == FAT16, "fat16_open_by: directory is not FAT16");
|
if (directory->type != FAT16 || !directory->is_dir) {
|
||||||
ASSERT(directory->is_dir, "fat16_open_by: directory is not a directory");
|
return NUL;
|
||||||
|
}
|
||||||
|
|
||||||
char name_8_3[12];
|
char name_8_3[12];
|
||||||
to_8_3(name, name_8_3);
|
to_8_3(name, name_8_3);
|
||||||
|
|
||||||
fat16_dir_entry_t *entries = load_directory((fat16_node_t *)directory);
|
fat16_dir_entry_t *entries = read_directory(((fat16_node_t *)directory)->inode->first_cluster);
|
||||||
|
|
||||||
fat16_dir_entry_t *entry = entries;
|
fat16_dir_entry_t *entry = entries;
|
||||||
|
uint16_t index = 0;
|
||||||
while (entry->name[0]) {
|
while (entry->name[0]) {
|
||||||
if ((uint8_t)entry->name[0] != 0xE5 && (uint8_t)entry->attributes != 0x0F && bytes_equal(name_8_3, (char *)entry, 11)) {
|
if ((uint8_t)entry->name[0] != 0xE5 && (uint8_t)entry->attributes != 0x0F && bytes_equal(name_8_3, (char *)entry, 11)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
entry++;
|
entry++;
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs_node_t *result = open_entry(name, entry);
|
fs_node_t *result = open_entry(((fat16_node_t *)directory)->inode->first_cluster, entries, index);
|
||||||
memory_free(entries);
|
memory_free(entries);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs_node_t *fat16_open_at(const fs_node_t *directory, uint64_t index) {
|
fs_node_t *fat16_open_at(const fs_node_t *directory, uint64_t index) {
|
||||||
ASSERT(directory->type == FAT16, "fat16_open_at: directory is not FAT16");
|
if (directory->type != FAT16 || !directory->is_dir) {
|
||||||
ASSERT(directory->is_dir, "fat16_open_at: directory is not a directory");
|
return NUL;
|
||||||
|
}
|
||||||
|
|
||||||
fat16_dir_entry_t *entries = load_directory((fat16_node_t *)directory);
|
fat16_dir_entry_t *entries = read_directory(((fat16_node_t *)directory)->inode->first_cluster);
|
||||||
|
|
||||||
uint64_t ei = 0, vi = 0;
|
uint16_t ei = 0, vi = 0;
|
||||||
while (entries[ei].name[0]) {
|
while (entries[ei].name[0]) {
|
||||||
if ((uint8_t)entries[ei].name[0] != 0xE5 && (uint8_t)entries[ei].attributes != 0x0F) {
|
if ((uint8_t)entries[ei].name[0] != 0xE5 && (uint8_t)entries[ei].attributes != 0x0F) {
|
||||||
if (vi == index) {
|
if (vi == index) {
|
||||||
@@ -183,14 +294,7 @@ fs_node_t *fat16_open_at(const fs_node_t *directory, uint64_t index) {
|
|||||||
ei++;
|
ei++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entries[ei].name[0]) {
|
fs_node_t *result = open_entry(((fat16_node_t *)directory)->inode->first_cluster, entries, ei);
|
||||||
return NUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char name[13];
|
|
||||||
from_8_3(entries[ei].name, entries[ei].ext, name);
|
|
||||||
|
|
||||||
fs_node_t *result = open_entry(name, entries + ei);
|
|
||||||
memory_free(entries);
|
memory_free(entries);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -198,30 +302,168 @@ fs_node_t *fat16_open_at(const fs_node_t *directory, uint64_t index) {
|
|||||||
fs_node_t *fat16_open_again(const fs_node_t *source) {
|
fs_node_t *fat16_open_again(const fs_node_t *source) {
|
||||||
fs_node_t *result = memory_allocate(sizeof(fat16_node_t));
|
fs_node_t *result = memory_allocate(sizeof(fat16_node_t));
|
||||||
memory_copy(source, sizeof(fat16_node_t), result);
|
memory_copy(source, sizeof(fat16_node_t), result);
|
||||||
|
((fat16_node_t *)result)->inode->refs++;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t fat16_read(const fs_node_t *file, uint32_t offset, uint32_t bytes, void *to) {
|
||||||
|
fat16_node_t *fat_file = (fat16_node_t *)file;
|
||||||
|
|
||||||
|
if (fat_file->base.type != FAT16 || fat_file->base.is_dir) {
|
||||||
|
return (uint64_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset >= fat_file->inode->size) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + bytes >= fat_file->inode->size) {
|
||||||
|
bytes = fat_file->inode->size - offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_fat();
|
||||||
|
|
||||||
|
uint32_t cluster_size = bpb.sectors_per_cluster * SECTOR_SIZE;
|
||||||
|
uint32_t next_cluster = fat_file->inode->first_cluster;
|
||||||
|
|
||||||
|
while (offset >= cluster_size) {
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
offset -= cluster_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t readden = 0;
|
||||||
|
|
||||||
|
uint8_t *cursor = to;
|
||||||
|
|
||||||
|
uint8_t *tmp = memory_allocate(cluster_size);
|
||||||
|
ata_read_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, tmp);
|
||||||
|
uint32_t prefix_size = bytes <= cluster_size - offset ? bytes : cluster_size - offset;
|
||||||
|
memory_copy(tmp + offset, prefix_size, cursor);
|
||||||
|
bytes -= prefix_size;
|
||||||
|
readden += prefix_size;
|
||||||
|
cursor += prefix_size;
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
|
||||||
|
while (bytes >= cluster_size) {
|
||||||
|
ata_read_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, cursor);
|
||||||
|
bytes -= cluster_size;
|
||||||
|
readden += cluster_size;
|
||||||
|
cursor += cluster_size;
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes) {
|
||||||
|
ata_read_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, tmp);
|
||||||
|
memory_copy(tmp, bytes, cursor);
|
||||||
|
readden += bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_free(tmp);
|
||||||
|
|
||||||
|
return readden;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t fat16_write(fs_node_t *file, uint32_t offset, const void *from, uint32_t bytes) {
|
||||||
|
fat16_node_t *fat_file = (fat16_node_t *)file;
|
||||||
|
|
||||||
|
if (fat_file->base.type != FAT16 || fat_file->base.is_dir) {
|
||||||
|
return (uint64_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_fat();
|
||||||
|
|
||||||
|
if (!fat_file->inode->first_cluster) {
|
||||||
|
fat_file->inode->first_cluster = allocate_cluster();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cluster_size = bpb.sectors_per_cluster * SECTOR_SIZE;
|
||||||
|
uint32_t next_cluster = fat_file->inode->first_cluster;
|
||||||
|
|
||||||
|
uint32_t allocated = cluster_size;
|
||||||
|
while (allocated < offset + bytes) {
|
||||||
|
if (fat[next_cluster] == 0xFFFF) {
|
||||||
|
fat[next_cluster] = allocate_cluster();
|
||||||
|
}
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
allocated += cluster_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
flush_fat();
|
||||||
|
|
||||||
|
cluster_size = bpb.sectors_per_cluster * SECTOR_SIZE;
|
||||||
|
next_cluster = fat_file->inode->first_cluster;
|
||||||
|
|
||||||
|
while (offset >= cluster_size) {
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
offset -= cluster_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t written = 0;
|
||||||
|
|
||||||
|
const uint8_t *cursor = from;
|
||||||
|
|
||||||
|
uint8_t *tmp = memory_allocate(cluster_size);
|
||||||
|
ata_read_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, tmp);
|
||||||
|
uint32_t prefix_size = bytes <= cluster_size - offset ? bytes : cluster_size - offset;
|
||||||
|
memory_copy(cursor, prefix_size, tmp + offset);
|
||||||
|
ata_write_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, tmp);
|
||||||
|
bytes -= prefix_size;
|
||||||
|
written += prefix_size;
|
||||||
|
cursor += prefix_size;
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
|
||||||
|
while (bytes >= cluster_size) {
|
||||||
|
ata_write_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, cursor);
|
||||||
|
bytes -= cluster_size;
|
||||||
|
written += cluster_size;
|
||||||
|
cursor += cluster_size;
|
||||||
|
next_cluster = fat[next_cluster];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes) {
|
||||||
|
ata_read_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, tmp);
|
||||||
|
memory_copy(cursor, bytes, tmp);
|
||||||
|
ata_write_sectors(data_start + bpb.sectors_per_cluster * (next_cluster - 2), bpb.sectors_per_cluster, tmp);
|
||||||
|
written += bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
memory_free(tmp);
|
||||||
|
|
||||||
|
if (offset + written > fat_file->inode->size) {
|
||||||
|
fat_file->inode->size = offset + written;
|
||||||
|
|
||||||
|
fat16_dir_entry_t *entries = read_directory(fat_file->inode->dir_cluster);
|
||||||
|
entries[fat_file->inode->dir_index].first_cluster = fat_file->inode->first_cluster;
|
||||||
|
entries[fat_file->inode->dir_index].size = fat_file->inode->size;
|
||||||
|
write_directory(fat_file->inode->dir_cluster, entries);
|
||||||
|
memory_free(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
stream_t stream;
|
stream_t stream;
|
||||||
fs_node_t *node;
|
fs_node_t *node;
|
||||||
uint64_t offset;
|
uint32_t offset;
|
||||||
} fat16_file_stream_t;
|
} fat16_file_stream_t;
|
||||||
|
|
||||||
static uint64_t file_stream_write(__attribute__((unused)) const stream_t *self, __attribute__((unused)) const char *from,
|
static uint64_t file_stream_write(stream_t *self, const char *from, uint64_t bytes) {
|
||||||
__attribute__((unused)) uint64_t bytes) {
|
fat16_file_stream_t *ffs = (fat16_file_stream_t *)self;
|
||||||
return (uint64_t)-1;
|
uint64_t written = fat16_write(ffs->node, ffs->offset, from, bytes > UINT32_MAX ? UINT32_MAX : (uint32_t)bytes);
|
||||||
|
if (written != (uint64_t)-1) {
|
||||||
|
ffs->offset += written;
|
||||||
|
}
|
||||||
|
return written;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t file_stream_read(const stream_t *self, uint64_t max, char *to) {
|
static uint64_t file_stream_read(stream_t *self, uint64_t max, char *to) {
|
||||||
fat16_file_stream_t *ffs = (fat16_file_stream_t *)self;
|
fat16_file_stream_t *ffs = (fat16_file_stream_t *)self;
|
||||||
if (ffs->offset >= ffs->node->size) {
|
uint64_t readden = fat16_read(ffs->node, ffs->offset, max > UINT32_MAX ? UINT32_MAX : (uint32_t)max, to);
|
||||||
return 0;
|
if (readden != (uint64_t)-1) {
|
||||||
|
ffs->offset += readden;
|
||||||
}
|
}
|
||||||
uint64_t remaining = ffs->node->size - ffs->offset;
|
return readden;
|
||||||
uint64_t to_read = remaining > max ? max : remaining;
|
|
||||||
fat16_read(ffs->node, ffs->offset, to_read, to);
|
|
||||||
ffs->offset += to_read;
|
|
||||||
return to_read;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void file_stream_close(stream_t *self) {
|
static void file_stream_close(stream_t *self) {
|
||||||
@@ -233,15 +475,15 @@ static void file_stream_close(stream_t *self) {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
stream_t stream;
|
stream_t stream;
|
||||||
fs_node_t *node;
|
fs_node_t *node;
|
||||||
uint64_t index;
|
uint32_t index;
|
||||||
} fat16_directory_stream_t;
|
} fat16_directory_stream_t;
|
||||||
|
|
||||||
static uint64_t directory_stream_write(__attribute__((unused)) const stream_t *self, __attribute__((unused)) const char *from,
|
static uint64_t directory_stream_write(__attribute__((unused)) stream_t *self, __attribute__((unused)) const char *from,
|
||||||
__attribute__((unused)) uint64_t bytes) {
|
__attribute__((unused)) uint64_t bytes) {
|
||||||
return (uint64_t)-1;
|
return (uint64_t)-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t directory_stream_read(const stream_t *self, uint64_t max, char *to) {
|
static uint64_t directory_stream_read(stream_t *self, uint64_t max, char *to) {
|
||||||
fat16_directory_stream_t *fds = (fat16_directory_stream_t *)self;
|
fat16_directory_stream_t *fds = (fat16_directory_stream_t *)self;
|
||||||
fs_node_t *node = fat16_open_at(fds->node, fds->index++);
|
fs_node_t *node = fat16_open_at(fds->node, fds->index++);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -250,8 +492,7 @@ static uint64_t directory_stream_read(const stream_t *self, uint64_t max, char *
|
|||||||
uint64_t length = string_length(node->name);
|
uint64_t length = string_length(node->name);
|
||||||
uint64_t to_read = length + 1 > max ? max : length + 1;
|
uint64_t to_read = length + 1 > max ? max : length + 1;
|
||||||
memory_copy(node->name, to_read, to);
|
memory_copy(node->name, to_read, to);
|
||||||
to[max - 1] = '\0';
|
to[to_read - 1] = '\0';
|
||||||
|
|
||||||
return to_read;
|
return to_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,68 +522,25 @@ stream_t *fat16_open_stream(const fs_node_t *source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fat16_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to) {
|
|
||||||
ASSERT(file->type == FAT16, "fat16_read: file is not FAT16");
|
|
||||||
ASSERT(!file->is_dir, "fat16_read: can not read directory");
|
|
||||||
ASSERT(file->size >= offset + size, "fat16_read: offset/size are out of bounds");
|
|
||||||
|
|
||||||
ensure_fat();
|
|
||||||
|
|
||||||
fat16_node_t *fat_file = (fat16_node_t *)file;
|
|
||||||
|
|
||||||
uint32_t cluster_size = bpb.sectors_per_cluster * SECTOR_SIZE;
|
|
||||||
uint32_t next_cluster = fat_file->entry.first_cluster;
|
|
||||||
|
|
||||||
// Assuming filesystem is correct. TODO Check for real.
|
|
||||||
|
|
||||||
while (offset >= cluster_size) {
|
|
||||||
next_cluster = fat[next_cluster];
|
|
||||||
offset -= cluster_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *cursor = to;
|
|
||||||
|
|
||||||
uint8_t *tmp = memory_allocate(cluster_size);
|
|
||||||
ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count +
|
|
||||||
(bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE +
|
|
||||||
bpb.sectors_per_cluster * (next_cluster - 2),
|
|
||||||
bpb.sectors_per_cluster, tmp);
|
|
||||||
uint64_t prefix_size = size <= cluster_size - offset ? size : cluster_size - offset;
|
|
||||||
memory_copy(tmp + offset, prefix_size, cursor);
|
|
||||||
size -= prefix_size;
|
|
||||||
cursor += prefix_size;
|
|
||||||
next_cluster = fat[next_cluster];
|
|
||||||
|
|
||||||
while (size >= cluster_size) {
|
|
||||||
ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count +
|
|
||||||
(bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE +
|
|
||||||
bpb.sectors_per_cluster * (next_cluster - 2),
|
|
||||||
bpb.sectors_per_cluster, cursor);
|
|
||||||
size -= cluster_size;
|
|
||||||
cursor += cluster_size;
|
|
||||||
next_cluster = fat[next_cluster];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size) {
|
|
||||||
ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count +
|
|
||||||
(bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE +
|
|
||||||
bpb.sectors_per_cluster * (next_cluster - 2),
|
|
||||||
bpb.sectors_per_cluster, tmp);
|
|
||||||
memory_copy(tmp, size, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
memory_free(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void fat16_close(fs_node_t *node) {
|
void fat16_close(fs_node_t *node) {
|
||||||
ASSERT(node->type == FAT16, "fat16_close: node is not FAT16");
|
if (node->type != FAT16 || node == fs) {
|
||||||
ASSERT(node != fs, "fat16_close: can not unmount FS");
|
return;
|
||||||
|
}
|
||||||
|
inode_free(((fat16_node_t *)node)->inode);
|
||||||
memory_free(node);
|
memory_free(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fat16_unmount(fs_node_t *node) {
|
void fat16_unmount(fs_node_t *node) {
|
||||||
ASSERT(node->type == FAT16, "fat16_unmount: node is not FAT16");
|
if (node->type != FAT16 || node != fs) {
|
||||||
ASSERT(node == fs, "fat16_unmount: node is not filesystem");
|
return;
|
||||||
|
}
|
||||||
|
inode_free(((fat16_node_t *)node)->inode);
|
||||||
memory_free(node);
|
memory_free(node);
|
||||||
|
|
||||||
|
if (fat) {
|
||||||
|
memory_free(fat);
|
||||||
|
fat = NUL;
|
||||||
|
}
|
||||||
|
|
||||||
fs = NUL;
|
fs = NUL;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -15,7 +15,9 @@ fs_node_t *fat16_open_again(const fs_node_t *source);
|
|||||||
|
|
||||||
stream_t *fat16_open_stream(const fs_node_t *source);
|
stream_t *fat16_open_stream(const fs_node_t *source);
|
||||||
|
|
||||||
void fat16_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to);
|
uint64_t fat16_read(const fs_node_t *file, uint32_t offset, uint32_t bytes, void *to);
|
||||||
|
|
||||||
|
uint64_t fat16_write(fs_node_t *file, uint32_t offset, const void *from, uint32_t bytes);
|
||||||
|
|
||||||
void fat16_close(fs_node_t *node);
|
void fat16_close(fs_node_t *node);
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -21,8 +21,12 @@ stream_t *fs_open_stream(const fs_node_t *source) {
|
|||||||
return fat16_open_stream(source);
|
return fat16_open_stream(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fs_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to) {
|
void fs_read(const fs_node_t *file, uint32_t offset, uint32_t bytes, void *to) {
|
||||||
fat16_read(file, offset, size, to);
|
fat16_read(file, offset, bytes, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs_write(fs_node_t *file, uint32_t offset, const void *from, uint32_t bytes) {
|
||||||
|
fat16_write(file, offset, from, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fs_close(fs_node_t *node) {
|
void fs_close(fs_node_t *node) {
|
||||||
|
|||||||
+3
-1
@@ -22,7 +22,9 @@ fs_node_t *fs_open_again(const fs_node_t *source);
|
|||||||
|
|
||||||
stream_t *fs_open_stream(const fs_node_t *source);
|
stream_t *fs_open_stream(const fs_node_t *source);
|
||||||
|
|
||||||
void fs_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to);
|
void fs_read(const fs_node_t *file, uint32_t offset, uint32_t bytes, void *to);
|
||||||
|
|
||||||
|
void fs_write(fs_node_t *file, uint32_t offset, const void *from, uint32_t bytes);
|
||||||
|
|
||||||
void fs_close(fs_node_t *node);
|
void fs_close(fs_node_t *node);
|
||||||
|
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ static void append_sequence(const char *s) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t stream_write(__attribute__((unused)) const stream_t *self, __attribute__((unused)) const char *from,
|
static uint64_t stream_write(__attribute__((unused)) stream_t *self, __attribute__((unused)) const char *from,
|
||||||
__attribute__((unused)) uint64_t bytes) {
|
__attribute__((unused)) uint64_t bytes) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t stream_read(__attribute__((unused)) const stream_t *self, uint64_t max, char *to) {
|
static uint64_t stream_read(__attribute__((unused)) stream_t *self, uint64_t max, char *to) {
|
||||||
while (!buffer_length) {
|
while (!buffer_length) {
|
||||||
__asm__ volatile("sti");
|
__asm__ volatile("sti");
|
||||||
process_next();
|
process_next();
|
||||||
|
|||||||
+4
-4
@@ -23,17 +23,17 @@ typedef struct {
|
|||||||
pipe_t *pipe;
|
pipe_t *pipe;
|
||||||
} pipe_write_stream_t;
|
} pipe_write_stream_t;
|
||||||
|
|
||||||
static uint64_t null_read(__attribute__((unused)) const stream_t *self, __attribute__((unused)) uint64_t max,
|
static uint64_t null_read(__attribute__((unused)) stream_t *self, __attribute__((unused)) uint64_t max,
|
||||||
__attribute__((unused)) char *to) {
|
__attribute__((unused)) char *to) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t null_write(__attribute__((unused)) const stream_t *self, __attribute__((unused)) const char *from,
|
static uint64_t null_write(__attribute__((unused)) stream_t *self, __attribute__((unused)) const char *from,
|
||||||
__attribute__((unused)) uint64_t bytes) {
|
__attribute__((unused)) uint64_t bytes) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t pipe_read(const stream_t *self, uint64_t max, char *to) {
|
static uint64_t pipe_read(stream_t *self, uint64_t max, char *to) {
|
||||||
pipe_t *pipe = ((pipe_read_stream_t *)self)->pipe;
|
pipe_t *pipe = ((pipe_read_stream_t *)self)->pipe;
|
||||||
|
|
||||||
while (pipe->begin == pipe->end) {
|
while (pipe->begin == pipe->end) {
|
||||||
@@ -60,7 +60,7 @@ static uint64_t pipe_read(const stream_t *self, uint64_t max, char *to) {
|
|||||||
return to_read;
|
return to_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t pipe_write(const stream_t *self, const char *from, uint64_t bytes) {
|
static uint64_t pipe_write(stream_t *self, const char *from, uint64_t bytes) {
|
||||||
pipe_t *pipe = ((pipe_read_stream_t *)self)->pipe;
|
pipe_t *pipe = ((pipe_read_stream_t *)self)->pipe;
|
||||||
|
|
||||||
uint64_t written = 0;
|
uint64_t written = 0;
|
||||||
|
|||||||
+2
-2
@@ -27,8 +27,8 @@
|
|||||||
|
|
||||||
typedef struct stream stream_t;
|
typedef struct stream stream_t;
|
||||||
|
|
||||||
typedef uint64_t (*stream_write_t)(const stream_t *self, const char *from, uint64_t bytes);
|
typedef uint64_t (*stream_write_t)(stream_t *self, const char *from, uint64_t bytes);
|
||||||
typedef uint64_t (*stream_read_t)(const stream_t *self, uint64_t max, char *to);
|
typedef uint64_t (*stream_read_t)(stream_t *self, uint64_t max, char *to);
|
||||||
typedef void (*stream_close_t)(stream_t *self);
|
typedef void (*stream_close_t)(stream_t *self);
|
||||||
|
|
||||||
typedef struct stream {
|
typedef struct stream {
|
||||||
|
|||||||
+2
-2
@@ -142,7 +142,7 @@ static void on_char_received(char c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t stream_write(__attribute__((unused)) const stream_t *self, const char *from, uint64_t bytes) {
|
static uint64_t stream_write(__attribute__((unused)) stream_t *self, const char *from, uint64_t bytes) {
|
||||||
uint64_t left = bytes;
|
uint64_t left = bytes;
|
||||||
while (left--) {
|
while (left--) {
|
||||||
on_char_received(*from++);
|
on_char_received(*from++);
|
||||||
@@ -150,7 +150,7 @@ static uint64_t stream_write(__attribute__((unused)) const stream_t *self, const
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t stream_read(__attribute__((unused)) const stream_t *self, __attribute__((unused)) uint64_t max,
|
static uint64_t stream_read(__attribute__((unused)) stream_t *self, __attribute__((unused)) uint64_t max,
|
||||||
__attribute__((unused)) char *to) {
|
__attribute__((unused)) char *to) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#include "src/lib/util.h"
|
||||||
|
#include "src/user/syscall.h"
|
||||||
|
|
||||||
|
#define BLOCK_SIZE 65536
|
||||||
|
|
||||||
|
#define PRINT_S(s) write(1, s, sizeof(s) - 1);
|
||||||
|
#define PRINT_D(s) write(1, s, string_length(s));
|
||||||
|
|
||||||
|
exit_code_t main(uint64_t argc, const char **argv) {
|
||||||
|
if (argc != 3) {
|
||||||
|
PRINT_S("cp: requires source and target");
|
||||||
|
return EXIT_CODE_GENERAL_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t source = open(argv[1]);
|
||||||
|
if (source == (uint64_t)-1) {
|
||||||
|
PRINT_S("cp: source does not exist\n");
|
||||||
|
return EXIT_CODE_GENERAL_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t target = open(argv[2]);
|
||||||
|
if (target == (uint64_t)-1) {
|
||||||
|
PRINT_S("cp: target does not exist\n");
|
||||||
|
return EXIT_CODE_GENERAL_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char buffer[BLOCK_SIZE];
|
||||||
|
uint64_t bytes;
|
||||||
|
while ((bytes = read(source, BLOCK_SIZE, buffer))) {
|
||||||
|
if (bytes == (uint64_t)-1) {
|
||||||
|
PRINT_S("cp: could not read file\n");
|
||||||
|
close(source);
|
||||||
|
close(target);
|
||||||
|
return EXIT_CODE_GENERAL_FAILURE;
|
||||||
|
}
|
||||||
|
if (write(target, buffer, bytes) == (uint64_t)-1) {
|
||||||
|
PRINT_S("cp: could not write file\n");
|
||||||
|
close(source);
|
||||||
|
close(target);
|
||||||
|
return EXIT_CODE_GENERAL_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(source);
|
||||||
|
close(target);
|
||||||
|
|
||||||
|
return EXIT_CODE_OK;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user