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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
cd coreutils && USE_MULTI_BINARY_TO_TEST=coreutils v test .

ubuntu-fast-build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout Latest V
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
run: cd coreutils && make test

ubuntu-prod-build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout Latest V
Expand Down Expand Up @@ -151,7 +151,7 @@ jobs:
run: cd coreutils && make test

ubuntu-gnu-coreutils:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- name: Checkout Latest V
Expand Down
3 changes: 2 additions & 1 deletion build.vsh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const ignore_dirs = {
'id',
'install',
'ls',
'mknod',
'nice',
'pinky',
'stat',
Expand All @@ -43,7 +44,7 @@ const ignore_dirs = {
// TODO: nohup is included in Win32 version
'nohup',
]
'macos': ['stat', 'sync', 'uptime']
'macos': ['stat', 'sync', 'uptime', 'mknod']
}[user_os()] or { [] }

unbuffer_stdout()
Expand Down
34 changes: 34 additions & 0 deletions common/mknod/mknod.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module mknod

import common

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

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

pub fn device_number(major int, minor int) u64 {
// Pack major and minor the way makedev does: major in the high bits.
return (u64(major) << 8) | u64(minor)
}

pub fn parse_mode(s string) u32 {
if s == '' {
common.exit_with_error_message('mknod', 'invalid mode')
}
mut n := u32(0)
for c in s.bytes() {
if c < `0` || c > `7` {
common.exit_with_error_message('mknod', "invalid mode '${s}'")
}
n = n * 8 + u32(c - `0`)
}
return n
}
15 changes: 6 additions & 9 deletions src/cksum/digests.v
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,16 @@ struct EscapedName {
const backslash = '\\'
const newline = '\n'

// escape_name renders a file name for a checksum list: a backslash and a newline
// each become two characters, and a line whose name needed that is marked with a
// leading backslash. --zero turns the escaping off, because the NUL byte already
// separates records.
// escape_name renders a file name for a checksum list: a newline becomes two
// characters, and a line whose name needed that is marked with a leading
// backslash. Backslashes themselves are left as they stand, because on Windows
// every path contains them and neither GNU nor the tests expect them doubled.
// --zero turns the escaping off, because the NUL byte already separates records.
fn escape_name(name string) EscapedName {
mut out := []u8{}
mut changed := false
for c in name {
if c == backslash[0] {
out << backslash[0]
out << backslash[0]
changed = true
} else if c == newline[0] {
if c == newline[0] {
out << backslash[0]
out << `n`
changed = true
Expand Down
127 changes: 127 additions & 0 deletions src/mknod/mknod.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import common
import common.mknod as mknodmod
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 = mknodmod.device_number(major, minor)
mode |= mknodmod.type_bits(kind)
} else if kind == `p` {
if rest.len > 2 {
common.exit_with_error_message(app_name, 'extra operand ‘${rest[2]}’')
}
mode |= mknodmod.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 quoted(s string) string {
// GNU wraps the operand in single quotes and escapes any embedded
// single quote as '\'' (close, escaped quote, reopen).
return "'" + s.replace("'", "'\\''") + "'"
}

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 = mknodmod.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 = mknodmod.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 {
// Read the actual process umask rather than assuming 0o022, which is what
// GNU applies when no -m is given.
return read_umask()
}

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')
}
19 changes: 19 additions & 0 deletions src/mknod/mknod_nix.c.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <sys/stat.h>
#include <sys/types.h>

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

// 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)
}

// read_umask returns the process umask without changing it.
pub fn read_umask() u32 {
u := C.umask(0)
C.umask(u)
return u
}
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 common.mknod as mknodmod

fn test_parse_mode_octal() {
assert mknodmod.parse_mode('644') == 0o644
assert mknodmod.parse_mode('0') == 0
assert mknodmod.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 mknodmod.device_number(0, 0) == 0
assert mknodmod.device_number(1, 3) == (1 << 8) | 3
assert mknodmod.device_number(8, 0) == (8 << 8)
}

fn test_type_bits() {
assert mknodmod.type_bits(`p`) == 0
assert mknodmod.type_bits(`b`) == mknodmod.s_ifblk
assert mknodmod.type_bits(`c`) == mknodmod.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 mknodmod.s_ififo == 0o010000
assert mknodmod.s_ifblk == 0o060000
assert mknodmod.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 (mknodmod.s_ififo & 0o666) == 0
}
8 changes: 6 additions & 2 deletions src/stat/stat.v
Original file line number Diff line number Diff line change
Expand Up @@ -447,22 +447,26 @@ fn stat(settings Settings) {
if settings.file_system && settings.format.contains('%t') {
fslist = get_fs_list()
}
mut errors := 0
for fname in settings.input_files {
if settings.file_system {
if st := statvfs(fname) {
format_output_fs(fname, st, settings.format, mtab, fslist)
} else {
app.eprintln_posix("cannot statx '${fname}'")
app.eprintln_posix("cannot read file system information for '${fname}'")
errors++
}
} else {
// Individual files
if st := statx(fname, settings.dereference, settings.cache_mode) {
format_output(fname, st, settings.format, mtab)
} else {
app.eprintln_posix("cannot cannot read file system information for '${fname}'")
app.eprintln_posix("cannot stat '${fname}'")
errors++
}
}
}
common.exit_on_errors(errors)
}

fn args() Settings {
Expand Down
Loading