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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ compare against the true GNU coreutils version on the Linux-based tests first.
| ✓ | hostid | Print numeric host identifier | |
| ✓ | hostname | Print or set system name | |
| ✓ | id | Print user identity | |
| | install | Copy files and set attributes | |
| ✓ | install | Copy files and set attributes | |
| ✓ | join | Join lines on a common field | ✓ |
| | kill | Send a signal to processes | ✓ |
| ✓ | link | Make a hard link via the link syscall | ✓ |
Expand Down
185 changes: 185 additions & 0 deletions common/install/args.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
module install

import common
import os

pub fn parse_args(args []string) Settings {
mut s := Settings{}
mut operands := []string{}

for i := 0; i < args.len; i++ {
a := args[i]
if a == '--' {
operands << args[i + 1..]
break
}
if a == '--help' {
print_help()
exit(0)
}
if a == '--version' {
println('install (V coreutils) ${common.version}')
exit(0)
}
if a == '-' || !a.starts_with('-') {
operands << a
continue
}

if a.starts_with('--') {
name := a.all_before('=')
val := a.all_after('=')
has_val := a.contains('=')
match name {
'--mode' {
v, next := take_value(args, i, val, has_val, name)
i = next
s.mode = v
s.mode_given = true
}
'--directory' {
s.dir_flag = true
}
'--target-directory' {
v, next := take_value(args, i, val, has_val, name)
i = next
// -t DIRECTORY: the directory is the destination and
// every operand is a source.
s.target = v
s.target_dir = true
}
'--no-target-directory' {}
'--preserve-timestamps' {
s.preserve = true
}
'--strip' {
s.strip = true
}
'--backup' {
s.backup = true
}
'--compare' {
s.compare = true
}
'--verbose' {
s.verbose = true
}
'--group' {
s.group = true
}
'--owner' {
s.owner = true
}
else {
common.exit_with_error_message('install', "unrecognized option '${a}'")
}
}
continue
}

mut j := 1
for j < a.len {
c := a[j]
match c {
`D` {
s.dir_flag = true
}
`d` {
s.dir_flag = true
}
`C` {
s.no_clobber = true
}
`c` {}
`p` {
s.preserve = true
}
`s` {
s.strip = true
}
`b` {
s.backup = true
}
`v` {
s.verbose = true
}
`g` {
s.group = true
}
`o` {
s.owner = true
}
`m` {
v, next := take_short(args, i, a, j)
i = next
s.mode = v
s.mode_given = true
j = a.len
continue
}
`t` {
v, next := take_short(args, i, a, j)
i = next
s.target = v
s.target_dir = true
j = a.len
continue
}
`-` {
j++
continue
}
else {
common.exit_with_error_message('install', "invalid option -- '${c.ascii_str()}'")
}
}
j++
}
}

s.operands = operands
return s
}

fn take_short(args []string, i int, a string, j int) (string, int) {
rest := a[j + 1..]
if rest != '' {
return rest, i
}
if i + 1 >= args.len {
common.exit_with_error_message('install', "option requires an argument -- '${a[j].ascii_str()}'")
}
return args[i + 1], i + 1
}

fn take_value(args []string, i int, inline_val string, has_val bool, name string) (string, int) {
if has_val {
return inline_val, i
}
if i + 1 >= args.len {
common.exit_with_error_message('install', "option '${name}' requires an argument")
}
return args[i + 1], i + 1
}

fn print_help() {
println('Usage: install [OPTION]... [-T] SOURCE DEST')
println(' or: install [OPTION]... SOURCE... DIRECTORY')
println(' or: install [OPTION]... -t DIRECTORY SOURCE...')
println(' or: install [OPTION]... -d DIRECTORY...')
println('')
println(' -b, --backup make a backup of each existing destination file')
println(' -C compare each pair of source and destination files')
println(' -d, --directory treat all arguments as directory names')
println(' -D create all leading components of DEST')
println(' -g, --group=GROUP set group ownership')
println(' -m, --mode=MODE set permission mode (as in chmod)')
println(' -o, --owner=OWNER set ownership')
println(' -p, --preserve-timestamps apply access/modification times')
println(' -s, --strip strip symbol tables')
println(' -t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY')
println(' -T, --no-target-directory treat DEST as a normal file')
println(' -v, --verbose print the name of each directory as it is created')
println(' --help display this help and exit')
println(' --version output version information and exit')
}
156 changes: 156 additions & 0 deletions common/install/install.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
module install

import common
import os

pub struct Settings {
pub mut:
mode string = '755'
mode_given bool
dir_flag bool // -D: create leading directories
target_dir bool // -t: copy sources into a directory
no_clobber bool // -C
preserve bool // -p
verbose bool // -v
backup bool // -b
dir_mode string
compare bool
strip bool
group bool
owner bool
target string
operands []string
}

// run performs the copy and returns 1 if anything failed.
pub fn run(set Settings) int {
bits, ok := parse_mode(set.mode)
if !ok {
eprintln('install: invalid mode ‘${set.mode}’')
return 1
}
mut dir_bits := u32(0o755)
if set.dir_mode != '' {
d, ok2 := parse_mode(set.dir_mode)
if ok2 {
dir_bits = d
}
}

mut sources := []string{}
mut dest := ''
if set.target_dir {
// install -t DIR SOURCES... : the directory came from the -t option
// itself, so every operand is a source.
if set.target == '' {
common.exit_with_error_message('install', 'missing file operand')
}
sources = set.operands.clone()
dest = set.target
} else if set.operands.len < 2 {
if set.operands.len == 1 {
common.exit_with_error_message('install', "missing destination file operand after '${set.operands[0]}'")
}
common.exit_with_error_message('install', 'missing file operand')
} else {
last := set.operands.len - 1
sources = set.operands[..last].clone()
dest = set.operands[last]
}

// More than one source is only meaningful into a directory, which is
// what -t asks for; GNU also accepts an existing directory as the last
// operand.
if sources.len > 1 && !set.target_dir && !os.is_dir(dest) {
common.exit_with_error_message('install', 'extra operand ${sources[1]}')
}

mut failed := 0
for src in sources {
mut target := dest
if set.target_dir || os.is_dir(dest) {
name := os.base(src)
target = os.join_path(dest, name)
}
if set.dir_flag {
parent := os.dir(target)
mkdir_all(parent) or {
eprintln('install: cannot create directory ${parent}')
failed++
continue
}
set_mode(parent, dir_bits)
}
// -C leaves an existing destination alone rather than overwriting.
if set.no_clobber && os.exists(target) {
continue
}
if set.backup && os.exists(target) {
rename(target, '${target}~') or {}
}
if set.verbose && os.exists(target) {
eprintln("removed '${target}'")
}
copy_file(src, target) or {
eprintln("install: cannot stat '${src}': No such file or directory")
failed++
continue
}

set_mode(target, bits)
if set.verbose {
eprintln("'${src}' -> '${target}'")
}
}
return if failed > 0 { 1 } else { 0 }
}

fn mkdir_all(path string) ? {
if path == '' {
return
}
os.mkdir_all(path) or { return none }
}

fn set_mode(path string, bits u32) {
os.chmod(path, int(bits)) or {}
}

fn rename(from string, to string) ? {
os.mv(from, to) or { return none }
}

fn copy_file(src string, dst string) ? {
if !os.exists(src) {
return none
}
data := read_source(src)
mut f := os.create(dst) or { return none }
f.write(data) or {
f.close()
return none
}
f.close()
}

// read_source is a one-line wrapper so the array-returning call sits at the
// top level of a function, where codegen handles it.
fn read_source(path string) []u8 {
return os.read_file_array(path)
}

// parse_mode accepts an octal mode such as 640 or 755, and reports failure for
// anything GNU would reject.
pub fn parse_mode(spec string) (u32, bool) {
if spec.len < 1 || spec.len > 4 {
return 0, false
}
mut n := u32(0)
for c in spec.bytes() {
if c < `0` || c > `7` {
return 0, false
}
n = n * 8 + u32(c - `0`)
}
return n & 0o7777, true
}
7 changes: 7 additions & 0 deletions src/install/install.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import common.install as installmod
import os

fn main() {
set := installmod.parse_args(os.args[1..])
exit(installmod.run(set))
}
Loading
Loading