package shm

import (
	"os"

	"golang.org/x/sys/unix"
)

const devShm = "/dev/shm/"

// Taken from shm_open(3):
//
//	shm_open() creates and opens a new, or opens an existing, POSIX shared
//	memory object. A POSIX shared memory object is in effect a handle which
//	can be used by unrelated processes to mmap(2) the same region of shared
//	memory. The shm_unlink() function performs the converse operation,
//	removing an object previously created by shm_open().
//
//	The operation of shm_open() is analogous to that of open(2). name
//	specifies the shared memory object to be created or opened. For
//	portable use, a shared memory object should be identified by a name of
//	the form /somename; that is, a null-terminated string of up to NAME_MAX
//	(i.e., 255) characters consisting of an initial slash, followed by one
//	or more characters, none of which are slashes.
func Open(name string, flag int, perm os.FileMode) (int, error) {

	for len(name) != 0 && name[0] == '/' {
		name = name[1:]
	}

	if len(name) == 0 {
		return 0, &os.PathError{Op: "open", Path: name, Err: unix.EINVAL}
	}

	o := uint32(perm.Perm())
	if perm&os.ModeSetuid != 0 {
		o |= unix.S_ISUID
	}
	if perm&os.ModeSetgid != 0 {
		o |= unix.S_ISGID
	}
	if perm&os.ModeSticky != 0 {
		o |= unix.S_ISVTX
	}

	fd, err := unix.Open(devShm+name, flag|unix.O_CLOEXEC, o)
	if err != nil {
		return 0, &os.PathError{Op: "open", Path: name, Err: err}
	}

	return fd, nil
}

// Taken from shm_unlink(3):
//
//	The  operation  of shm_unlink() is analogous to unlink(2): it removes a
//	shared memory object name, and, once all processes  have  unmapped  the
//	object, de-allocates and destroys the contents of the associated memory
//	region.  After a successful shm_unlink(),  attempts  to  shm_open()  an
//	object  with  the same name will fail (unless O_CREAT was specified, in
//	which case a new, distinct object is created).
func Unlink(name string) error {
	fileName := name

	for len(name) != 0 && name[0] == '/' {
		name = name[1:]
	}

	if len(name) == 0 {
		return &os.PathError{Op: "unlink", Path: fileName, Err: unix.EINVAL}
	}

	if err := unix.Unlink(devShm + name); err != nil {
		return &os.PathError{Op: "unlink", Path: fileName, Err: err}
	}

	return nil
}
