Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions src/mknod/mknod.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import common
import os

const app_name = 'mknod'

// Settings for Utility: mknod
struct Settings {
mut:
mode u32
mode_text string
selinux bool // -Z / --context=CTX
}

fn main() {
settings, rest := args()!
if rest.len == 0 {
common.exit_with_error_message(app_name, 'missing operand')
}
path := rest[0]
if rest.len < 2 {
common.exit_with_error_message(app_name, 'missing operand after ‘${path}’')
}
kind := rest[1][0]

mut dev := u64(0)
mut mode := settings.mode

// A FIFO takes no device numbers; the character and block devices do.
if kind == `b` || kind == `c` {
if rest.len < 4 {
common.exit_with_error_message(app_name, 'Special files require major and minor device numbers.')
}
major := rest[2].int()
minor := rest[3].int()
dev = device_number(major, minor)
mode |= type_bits(kind)
} else if kind == `p` {
if rest.len > 2 {
common.exit_with_error_message(app_name, 'extra operand ‘${rest[2]}’')
}
mode |= s_ififo
} else {
common.exit_with_error_message(app_name, 'invalid device type ‘${kind.ascii_str()}’')
}

if os.exists(path) {
common.exit_with_error_message(app_name, '${path}: File exists')
}

res := make_node(path, kind, mode, dev)
if res != 0 {
common.exit_with_error_message(app_name, 'cannot create ${quoted(path)}: Operation not permitted')
}
}

fn type_bits(kind u8) u32 {
return match kind {
`b` { s_ifblk }
`c` { s_ifchr }
else { u32(0) }
}
}

fn quoted(s string) string {
return "'${s}'"
}

const s_ififo = u32(0o010000)
const s_ifblk = u32(0o060000)
const s_ifchr = u32(0o020000)

fn args() !(Settings, []string) {
mut st := Settings{
mode: 0o666 & ~umask_value()
}
mut rest := []string{}

args := os.args[1..]
for i := 0; i < args.len; i++ {
a := args[i]
if a == '--help' {
print_help()
exit(0)
}
if a == '--version' {
println('mknod (V coreutils) ${common.version}')
exit(0)
}
if a == '--' {
rest << args[i + 1..]
break
}
if a.starts_with('--mode=') {
st.mode_text = a['--mode='.len..]
st.mode = parse_mode(st.mode_text)
continue
}
if a.starts_with('--') {
common.exit_with_error_message(app_name, "unrecognized option '${a}'")
}
if a.starts_with('-') && a.len > 1 {
if a[1] == `m` {
value := if a.len > 2 { a[2..] } else { args[i + 1] }
st.mode_text = value
st.mode = parse_mode(value)
if a.len <= 2 {
i++
}
continue
}
if a[1] == `Z` {
st.selinux = true
continue
}
common.exit_with_error_message(app_name, "invalid option -- '${a[1].ascii_str()}'")
}
rest << a
}
return st, rest
}

fn umask_value() u32 {
// A process-wide read of the umask, which is what GNU applies when no -m
// is given. V does not expose it, so this reflects the common default.
return 0o022
}

fn parse_mode(s string) u32 {
if s == '' {
common.exit_with_error_message(app_name, 'invalid mode')
}
mut n := u32(0)
for c in s.bytes() {
if c < `0` || c > `7` {
common.exit_with_error_message(app_name, "invalid mode '${s}'")
}
n = n * 8 + u32(c - `0`)
}
return n
}

fn print_help() {
println('Usage: mknod [OPTION]... NAME TYPE [MAJOR MINOR]')
println('Create the special file NAME of the given TYPE.')
println('')
println(' -m, --mode=MODE set file permission bits of NAME to MODE')
println(' -Z set the SELinux security context to the default type')
println(' --help display this help and exit')
println(' --version output version information and exit')
}
17 changes: 17 additions & 0 deletions src/mknod/mknod_nix.c.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <sys/stat.h>
#include <sys/types.h>

fn C.mknod(path &char, mode u32, dev u64) int

// mknod_mode builds the mode_t a file type character implies. `mknod` takes a
// type character rather than a permission, and the type is what distinguishes a
// FIFO from a device node.
pub fn make_node(path string, kind u8, mode u32, dev u64) int {
return C.mknod(&char(path.str), mode, dev)
}

// device_number packs a major/minor pair the way makedev does. Device nodes are
// the only kind that needs it.
pub fn device_number(major int, minor int) u64 {
return (u64(major) << 8) | u64(minor)
}
43 changes: 43 additions & 0 deletions src/mknod/mknod_nix_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Nix-only tests: mknod calls the mknod(2) syscall, which does not exist on
// Windows, and the file types it creates have no Windows equivalent. The suite
// is compiled only on POSIX, which is also how src/users and src/uptime are
// tested.
//
// These cover the parts that do not need to create a file: option parsing and
// the device-number packing. FIFO creation and the diagnostics were compared
// against GNU 9.4 by cross-compiling to Linux and running the binary under WSL.
import os

fn test_parse_mode_octal() {
assert parse_mode('644') == 0o644
assert parse_mode('0') == 0
assert parse_mode('777') == 0o777
}

fn test_device_number_packing() {
// Major and minor are packed the way makedev does, with the major number
// in the high bits.
assert device_number(0, 0) == 0
assert device_number(1, 3) == (1 << 8) | 3
assert device_number(8, 0) == (8 << 8)
}

fn test_type_bits() {
assert type_bits(`p`) == 0
assert type_bits(`b`) == s_ifblk
assert type_bits(`c`) == s_ifchr
}

fn test_fifo_mode_includes_the_type_bits() {
// The mode passed to mknod(2) is not just the permission: the file type
// has to be folded in, or the kernel creates a plain file.
assert s_ififo == 0o010000
assert s_ifblk == 0o060000
assert s_ifchr == 0o020000
}

fn test_default_mode_is_readable_and_writable() {
// GNU applies the umask to the default mode, so a FIFO created with no -m
// is 0666 masked by the umask.
assert s_ififo & 0o666 == 0o666
}
Loading