193 lines
7.5 KiB
Plaintext
193 lines
7.5 KiB
Plaintext
Header_Os_Specific :: struct {
|
||
magic: u32;
|
||
}
|
||
|
||
Read_Channel_Os_Specific :: struct {
|
||
shm: s32;
|
||
}
|
||
|
||
Write_Channel_Os_Specific :: struct {
|
||
shm: s32;
|
||
}
|
||
|
||
//
|
||
// Reader stuff:
|
||
//
|
||
|
||
// O_EXCL: the caller unlinks any stale object first, so an existing one belongs to a live reader.
|
||
reader_create_channel_os_specific :: (name: string, bytes: s64) -> (success: bool, channel: Read_Channel, already_exists: bool = false) {
|
||
P :: Posix;
|
||
|
||
total_bytes := bytes + OFFSET_TO_MESSAGE_DATA;
|
||
c_name := Basic.temp_c_string(name);
|
||
|
||
shm := P.shm_open(c_name, P.O_CREAT | P.O_RDWR | P.O_EXCL, CHANNEL_FILE_MODE);
|
||
if shm == -1 {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
if error_code == P.EEXIST return false, .{}, true;
|
||
Basic.log_error("Unable to map a view of '%' for % bytes (which is the user-requested % bytes, plus overhead). Error code %, %", name, total_bytes, bytes, error_code, error_string);
|
||
return false, .{};
|
||
}
|
||
|
||
do_truncate := true;
|
||
#if OS == .MACOS {
|
||
// On macOS, ftruncate can only be called once after creation of the shared memory object.
|
||
// Every subsequent call fails with EINVAL. So let’s check the size and only call ftruncate if it doesn’t match.
|
||
// -rluba, 2023-05-15
|
||
mem_stat: P.stat_t;
|
||
stat_result := P.fstat(shm, *mem_stat);
|
||
if stat_result == -1 {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("Unable to stat '%'. Error code %, %. The file descriptor was %.", name, error_code, error_string, shm);
|
||
P.close(shm);
|
||
return false, .{};
|
||
}
|
||
|
||
if mem_stat.st_size != 0 {
|
||
if mem_stat.st_size >= total_bytes {
|
||
// Already has the right size, don’t try to ftruncate again. It would fail on macOS.
|
||
// We’re comparing with ">=" instead of "=" because the actual size seems to always be slightly bigger than the one requested via ftruncate.
|
||
do_truncate = false;
|
||
} else {
|
||
// We need to remove the old memory object and re-create it to change its size.
|
||
P.close(shm);
|
||
unlink_result := P.shm_unlink(c_name);
|
||
if unlink_result != 0 {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("Unable to remove old '%' to re-create it with the right size. Error code %, %.", name, error_code, error_string);
|
||
return false, .{};
|
||
}
|
||
|
||
shm = P.shm_open(c_name, P.O_CREAT | P.O_RDWR | P.O_EXCL, CHANNEL_FILE_MODE);
|
||
if shm == -1 {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("Unable to re-create a view of '%' for % bytes (which is the user-requested % bytes, plus overhead). Error code %, %", name, total_bytes, bytes, error_code, error_string);
|
||
return false, .{};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if do_truncate {
|
||
truncate_result := P.ftruncate(shm, total_bytes);
|
||
if truncate_result == -1 {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("Unable to ftruncate '%' for % bytes (which is the user-requested % bytes, plus overhead). Error code %, %. The file descriptor was %.\n", name, total_bytes, bytes, error_code, error_string, shm);
|
||
return false, .{};
|
||
}
|
||
}
|
||
|
||
// Remove the name now, object stays alive while fd/mmap refs exist
|
||
// if (P.shm_unlink(c_name) < 0) {
|
||
// Basic.log_error("shm_unlink failed.");
|
||
// return false, .{};
|
||
// }
|
||
|
||
flags: s32 = P.MAP_SHARED;
|
||
#if OS == .LINUX {
|
||
flags |= P.MAP_POPULATE; // Not available on macOS.
|
||
}
|
||
pointer := P.mmap(null, cast(u64) total_bytes, P.PROT_READ | P.PROT_WRITE, flags, shm, 0);
|
||
if pointer == P.MAP_FAILED {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("Unable to mmap for the channel's memory segment, name '%' bytes '%'. Error code %, %. The file descriptor was %.\n", name, bytes, error_code, error_string, shm);
|
||
P.close(shm);
|
||
return false, .{};
|
||
}
|
||
|
||
data := cast(*u8) pointer;
|
||
header_os := cast(*Header_Os_Specific)(data + OFFSET_TO_HEADER_OS_SPECIFIC);
|
||
header_os.magic = (.*) cast(*u32) HEADER_OS_MAGIC.data; // @Endian
|
||
Basic.Initialize(header_os);
|
||
|
||
channel: Read_Channel;
|
||
channel.header = cast(*Header) pointer;
|
||
channel.os_specific.shm = shm;
|
||
|
||
return true, channel;
|
||
}
|
||
|
||
reader_release_channel :: (channel: *Read_Channel, channel_name: string) -> bool {
|
||
Posix.shm_unlink(Basic.temp_c_string(channel_name)); // closes kernel object.
|
||
|
||
data := cast(*u8) channel.header;
|
||
if !data return false;
|
||
|
||
Posix.munmap(data, cast(u64) channel.header.full_memory_region_size_in_bytes);
|
||
|
||
os := *channel.os_specific;
|
||
if os.shm Posix.close(os.shm);
|
||
|
||
return true; // We currently don't check for errors.
|
||
}
|
||
|
||
|
||
//
|
||
// Writer stuff:
|
||
//
|
||
writer_connect :: (name: string, name_not_found_is_quiet := false) -> (success: bool, channel: Write_Channel) {
|
||
P :: Posix;
|
||
|
||
c_name := Basic.temp_c_string(name);
|
||
shm := P.shm_open(c_name, P.O_RDWR, 0);
|
||
if shm == -1 {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("In writer_connect(), unable to map a view of name '%'. Error code %, %\n", name, error_code, error_string);
|
||
return false, .{};
|
||
}
|
||
|
||
header_pointer := P.mmap(null, size_of(Header), P.PROT_READ | P.PROT_WRITE, P.MAP_SHARED, shm, 0);
|
||
if header_pointer == P.MAP_FAILED {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("In writer_connect(), unable to mmap for the channel's memory segment, name '%'. Error code %, %. The file descriptor was %.\n", name, error_code, error_string, shm);
|
||
P.close(shm);
|
||
return false, .{};
|
||
}
|
||
|
||
// Look inside the header to see how big our view should be.
|
||
header_success, actual_bytes := get_size_of_full_mapping_from_stub_header(header_pointer);
|
||
|
||
P.munmap(header_pointer, size_of(Header));
|
||
|
||
if !header_success {
|
||
P.close(shm);
|
||
return false, .{};
|
||
}
|
||
|
||
// Now make a new pointer that is the full size.
|
||
header_pointer = P.mmap(null, cast(u64) actual_bytes, P.PROT_READ | P.PROT_WRITE, P.MAP_SHARED, shm, 0);
|
||
if header_pointer == P.MAP_FAILED {
|
||
error_code, error_string := System.get_error_value_and_string();
|
||
Basic.log_error("In writer_connect(), unable to mmap for the channel's memory segment, name '%'. Error code %, %\n", name, error_code, error_string);
|
||
P.close(shm);
|
||
return false, .{};
|
||
}
|
||
|
||
data := cast(*u8) header_pointer;
|
||
header_os := cast(*Header_Os_Specific)(data + OFFSET_TO_HEADER_OS_SPECIFIC);
|
||
|
||
Basic.Initialize(header_os);
|
||
header_os.magic = (.*) cast(*u32) HEADER_OS_MAGIC.data; // @Endian
|
||
|
||
channel: Write_Channel;
|
||
channel.header = cast(*Header) header_pointer;
|
||
channel.os_specific.shm = shm;
|
||
|
||
return true, channel;
|
||
}
|
||
|
||
writer_disconnect :: (channel: *Write_Channel) -> bool {
|
||
Posix.munmap(channel.header, cast(u64) channel.header.full_memory_region_size_in_bytes);
|
||
Posix.close(channel.os_specific.shm);
|
||
return true;
|
||
}
|
||
|
||
#scope_module
|
||
Posix :: #import "POSIX";
|
||
System :: #import "System";
|
||
|
||
HEADER_OS_MAGIC :: "ShmP";
|
||
CHANNEL_FILE_MODE :: 0x180; // 0600: the owner only; the name already carries the uid
|
||
|