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