#Need pointers on converting bytes to structs

21 messages · Page 1 of 1 (latest)

solemn geyser
#

I'm using inotify on a project, and it requires me to read N inotify_event structures and a dynamic sized null-terminated string from the inotify file descriptor every time there's an event. I'm currently implementing it like this (keeping just the important things):

buffer := make([]u8, 4096)
bytes_read = linux.read(in_fd, buffer)

in_events: [dynamic] linux.Inotify_Event

offset := 0
for offset < bytes_read {
    event := (^linux.Inotify_Event)(raw_data(buffer[offset:]))^
    name := cstring(raw_data(buffer[offset + size_of(event):][:event.len]))
    append(&in_events, event) // Currently ignoring name, but shouldn't (I'll need it later)

    offset += size_of(event) + int(event.len)
}

I have a few questions, but the biggest one is: Is there a better way to do this read into a struct? Or maybe just the conversion from []u8 to said struct.

Also, the structure kinda acknowledges that name field, but it's defines it as [0]u8. Can I somehow store the name inside the structure without relying on external storage (maybe just a memory allocation) to pass the name forward (as the comment suggests, I'll need it for later). As in, can I store the string, or a pointer to a string on that [0]u8 field?

I should also clarify that I need to read a few bytes more than just size_of(Inotify_Event). I thought about using mem.ptr_to_bytes(&event) (somebody [already recommended](#beginners message) that to me for another code I was writing), but if I do so, the call to read will throw me an EINVAL error. It needs to read at least sizeof(struct inotify_event) + NAME_MAX + 1 (straight from inotify(7)'s man page). I hard-coded a buffer size of 4096 for now, but I'll get back to that later...

jolly geode
#

What you're doing is more or less how I would do it.
The name strings (as indicated by the [0]u8 are after the struct in memory.
There's a lot of options for exactly how you go about managing those strings; if you only want them temporarily, could even just clone them into the temp allocator.
The user then clones them again if they want to hold onto them after handling the events.

Event :: struct {
    name: string,
    // ...
}
incoming_events: [dynamic]Event

buffer: [4096]byte = ---
read_size, read_err := os.read(in_fd, buffer[:])
assert(read_err == nil)

for offset := 0; offset < read_size; {
    event := cast(^linux.Inotify_Event) &buffer[offset]
    name_offset := offset + size_of(linux.Inotify_Event)
    name := string(buffer[name_offset:][:event.len])
    offset = name_offset + len(name) // possibly +1 for a null?
    append(&incoming_events, Event{ 
        name = strings.clone(name, context.temp_allocator),
        // ...
    })
}

// note: if you are just gonna handle the events right here,
// then you can just do `append(&incoming_events, Event{name = name})` instead.
// they'll point into the `buffer` which lives this long anyway, so no need
// for the clone.
#

You could, however, use the array field to help with the offset.

name := string(slice.bytes_from_ptr(&event.name, int(event.len)))
offset += size_of(linux.Inotify_Event) + len(name)
#

So now it becomes:

Event :: struct {
    name: string,
    // ...
}
incoming_events: [dynamic]Event

buffer: [4096]byte = ---
read_size, read_err := os.read(in_fd, buffer[:])
assert(read_err == nil)

for offset := 0; offset < read_size; {
    event := cast(^linux.Inotify_Event) &buffer[offset]
    name := string(slice.bytes_from_ptr(&event.name, int(event.len)))
    offset += size_of(linux.Inotify_Event) + len(name)
    append(&incoming_events, Event{ 
        name = strings.clone(name, context.temp_allocator),
        // ...
    })
}

// note: if you are just gonna handle the events right here,
// then you can just do `append(&incoming_events, Event{name = name})` instead.
// they'll point into the `buffer` which lives this long anyway, so no need
// for the clone.
#

Strikes me as less error prone

solemn geyser
#

Thanks for the reply! After taking a look, I could, in fact, handle the event right there. I won't be needing the incoming_events array or copy any strings. 🙂
The fixed array for the buffer was very good (I was looking at make at the time, and I think my mind automatically went there to create new buffers :P)
Also, I was looking for the reverse operation of mem.ptr_to_bytes, it was in core:slice all along.
Using pointers to work with the event is also a good idea, I don't need to be copying that anywhere. That &event.name was also a very good tip...

Will be reading on the temp_allocator later

#

(also, offset won't need a +1, the null byte is already included in event.len)

solemn geyser
#

Still, I had to use string(transmute(cstring)(&event.name)) (from cstring to string) to get the name. Getting it through a slice (string(slice.bytes_from_ptr(&event.name, int(event.len))) or string(buffer[offset + size_of(event):][:event.len])) added null bytes to the end of them(?) couldn't figure out why...
This was the weird string outputs (printed with "%w"): "test_dir/file\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

jolly geode
#

Safer

#

Odd that event.len wouldn't be enough though

#

Or wait

#

Is the event.len the total size of the event structure?

#

Maybe do size := event.len - size_of(linux.Inotify_Event) ?

solemn geyser
#

that's not what the doc says. I'll print debug it tho just to make sure

jolly geode
#

🤷‍♂️

#

Is the name UTF-16?

#

Or rather, is the event.len in "number of u16s"

#

Or wait - this isn't Windows lol

solemn geyser
#

I think it's just inotify being funky. It's always returning a size of 16 and padding the end with zeroes. I'll just use truncate_to_byte

#

I think it's doing some kind of alignment or maybe it has a min name length