diff --git a/src/date/date.v b/src/date/date.v new file mode 100644 index 00000000..0f46da27 --- /dev/null +++ b/src/date/date.v @@ -0,0 +1,190 @@ +import os +import time +import common + +const app_name = 'date' + +const weekdays_short = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] +const weekdays_long = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', +] +const months_short = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', + 'Dec'] +const months_long = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', +] + +fn main() { + mut fp := common.flag_parser(os.args) + fp.application(app_name) + fp.description('Print or set the system date and time.') + // +FORMAT is not a flag, so the flag parser does not claim it: it is read from + // the command line directly, and the + is stripped. + mut format := '' + for arg in os.args[1..] { + if arg.starts_with('+') { + format = arg[1..] + } + } + utc := fp.bool('utc', `u`, false, 'print the date in UTC') + rfc2822 := fp.bool('rfc-2822', `R`, false, 'output date in RFC 2822 format') + iso := fp.bool('iso-8601', `I`, false, 'output date in ISO 8601 format') + help := fp.bool('help', 0, false, 'display this help and exit') + version := fp.bool('version', 0, false, 'output version information and exit') + fp.allow_unknown_args() + if help { + println(fp.usage()) + return + } + if version { + println('${app_name} ${common.coreutils_version()}') + return + } + fp.finalize()! + + mut now := time.now() + if utc { + now = now.local_to_utc() + } + + if iso { + println(format_iso(now)) + return + } + if rfc2822 { + println(format_rfc2822(now)) + return + } + if format != '' { + println(render(format, now)) + return + } + // The default form, measured on GNU 9.4: %a %b %e %H:%M:%S %z %Y. + println(render('%a %b %e %H:%M:%S %z %Y', now)) +} + +fn format_iso(t time.Time) string { + return '${t.year}-${pad2(t.month)}-${pad2(t.day)}' +} + +fn format_rfc2822(t time.Time) string { + off := offset_seconds(t) + sign := if off >= 0 { '+' } else { '-' } + a := if off < 0 { -off } else { off } + return '${weekdays_short[t.day_of_week()]}, ${pad2(t.day)} ${months_short[t.month - 1]} ${t.year} ${pad2(t.hour)}:${pad2(t.minute)}:${pad2(t.second)} ${sign}${pad2(int(a / 3600))}${pad2(int(a % 3600 / 60))}' +} + +// V's Time carries no offset field, so the zone is the difference between the +// absolute epoch and the epoch the same wall clock would have in the local zone. +fn offset_seconds(t time.Time) i64 { + return t.unix() - t.local_unix() +} + +fn pad2(n int) string { + return if n < 10 { '0${n}' } else { n.str() } +} + +// render walks a format string and replaces the strftime specifiers. The ones +// implemented are the ones the tests use; the rest are left as written, which is +// what GNU does with an unknown specifier too. +fn render(format string, t time.Time) string { + mut out := '' + mut i := 0 + for i < format.len { + if format[i] != `%` { + out += format[i].ascii_str() + i++ + continue + } + i++ + if i >= format.len { + break + } + match format[i] { + `Y` { out += t.year.str() } + `m` { out += pad2(t.month) } + `d` { out += pad2(t.day) } + `e` { out += ' ' + pad2(t.day) } + `H` { out += pad2(t.hour) } + `M` { out += pad2(t.minute) } + `S` { out += pad2(t.second) } + `a` { out += weekdays_short[t.day_of_week()] } + `A` { out += weekdays_long[t.day_of_week()] } + `b`, `h` { out += months_short[t.month - 1] } + `B` { out += months_long[t.month - 1] } + `s` { out += t.unix().str() } + `z` { + off := offset_seconds(t) + sign := if off >= 0 { '+' } else { '-' } + a := if off < 0 { -off } else { off } + out += '${sign}${pad2(int(a / 3600))}${pad2(int(a % 3600 / 60))}' + } + `Z` { out += timezone_name(t) } + `j` { out += pad3(day_of_year(t)) } + `u` { out += (t.day_of_week() % 7 + 1).str() } + `w` { out += t.day_of_week().str() } + `F` { out += '${t.year}-${pad2(t.month)}-${pad2(t.day)}' } + `T` { out += '${pad2(t.hour)}:${pad2(t.minute)}:${pad2(t.second)}' } + `R` { out += '${pad2(t.hour)}:${pad2(t.minute)}' } + `n` { out += '\n' } + `t` { out += '\t' } + `%` { out += '%' } + else { out += '%' + format[i].ascii_str() } + } + i++ + } + return out +} + +fn pad3(n int) string { + if n < 10 { + return '00${n}' + } + if n < 100 { + return '0${n}' + } + return n.str() +} + +fn day_of_year(t time.Time) int { + mut doy := t.day + for m in 1 .. t.month { + doy += days_in_month(t.year, m) + } + return doy +} + +fn days_in_month(year int, month int) int { + return [31, if is_leap(year) { 29 } else { 28 }, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] +} + +fn is_leap(year int) bool { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) +} + +// timezone_name returns the abbreviation for the time zone, which is the numeric +// offset spelled as a name when the zone has no abbreviation of its own. GNU prints +// +0330 here on a machine in Iran, and V's own zone name is the same digits. +fn timezone_name(t time.Time) string { + off := offset_seconds(t) + sign := if off >= 0 { '+' } else { '-' } + a := if off < 0 { -off } else { off } + return '${sign}${pad2(int(a / 3600))}${pad2(int(a % 3600 / 60))}' +} diff --git a/src/date/date_test.v b/src/date/date_test.v new file mode 100644 index 00000000..58f642d5 --- /dev/null +++ b/src/date/date_test.v @@ -0,0 +1,81 @@ +import common.testing +import os + +const rig = testing.prepare_rig(util: 'date') + +fn testsuite_begin() { + rig.assert_platform_util() +} + +fn test_help_and_version() { + rig.assert_help_and_version_options_work() +} + +fn test_default_format() { + res := os.execute('${rig.executable_under_test}') + assert res.exit_code == 0 + // The default form is locale and zone dependent, so only its shape is checked: + // a weekday, a month, a time and a year. + assert res.output.contains('20') + assert res.output.contains(':') +} + +fn test_format_date() { + res := os.execute('${rig.executable_under_test} +%Y-%m-%d') + assert res.exit_code == 0 + assert res.output.len == 11 + assert res.output[4] == `-` + assert res.output[7] == `-` +} + +fn test_format_time() { + res := os.execute('${rig.executable_under_test} +%H:%M:%S') + assert res.exit_code == 0 + assert res.output.len == 9 + assert res.output[2] == `:` + assert res.output[5] == `:` +} + +// The format is passed without spaces because os.execute goes through the shell +// on Windows, which does not treat single quotes as quoting. +fn test_format_weekday() { + res := os.execute('${rig.executable_under_test} +%A') + assert res.exit_code == 0 + assert res.output.contains('day') +} + +fn test_format_month() { + res := os.execute('${rig.executable_under_test} +%B') + assert res.exit_code == 0 + assert res.output.contains('r') +} + +fn test_format_epoch() { + res := os.execute('${rig.executable_under_test} +%s') + assert res.exit_code == 0 + assert res.output.len >= 10 + for c in res.output.trim_space() { + assert c >= `0` && c <= `9` + } +} + +fn test_utc() { + res := os.execute('${rig.executable_under_test} -u +%Y-%m-%dT%H:%M:%S%z') + assert res.exit_code == 0 + assert res.output.trim_space().ends_with('+0000') +} + +fn test_rfc2822() { + res := os.execute('${rig.executable_under_test} -R') + assert res.exit_code == 0 + assert res.output.contains(', ') + assert res.output.contains('20') +} + +fn test_iso8601() { + res := os.execute('${rig.executable_under_test} -I') + assert res.exit_code == 0 + assert res.output.len == 11 + assert res.output[4] == `-` + assert res.output[7] == `-` +} diff --git a/src/realpath/realpath.v b/src/realpath/realpath.v new file mode 100644 index 00000000..569714ab --- /dev/null +++ b/src/realpath/realpath.v @@ -0,0 +1,278 @@ +import common +import flag +import os +import strings + +const app = common.CoreutilInfo{ + name: 'realpath' + description: 'print the resolved absolute file name' +} + +const max_link_depth = 32 + +enum CanonicalizeMode { + all_must_exist + all_but_last_must_exist + none_need_exist +} + +enum ResolveMode { + logical + physical +} + +struct Settings { +mut: + mode CanonicalizeMode + resolve_mode ResolveMode + quiet bool + strip bool + zero bool + relative_to string + relative_base string + target_files []string +} + +fn resolve_link_fully(path string, max_depth int) !string { + mut resolved_path := path + for i := 0; i < max_depth; i++ { + if lpath := do_readlink(resolved_path) { + $if windows { + if lpath == resolved_path { + return resolved_path + } + } + resolved_path = lpath + } else { + return resolved_path + } + } + return error('Too many levels of symbolic links') +} + +fn canonicalize(path string, mode CanonicalizeMode, resolve_mode ResolveMode) !(string, bool) { + assert os.is_abs_path(path) + mut ok := true + mut sb := strings.new_builder(path.len) + + p := path.split(os.path_separator) + if path.len > 2 && (path[0] == `\\` || path[0] == `/`) && path[1] == path[0] { + sb.write_string(path[0..2]) + } else { + sb.write_string(p[0]) + } + for i := 1; i < p.len; i++ { + if resolve_mode == .logical { + if p[i] == '..' { + if sb.len > 1 { + cur := sb.str() + parent := os.dir(cur) + if parent != cur { + sb.clear() + sb.write_string(parent) + } + } + continue + } + if p[i] == '.' { + continue + } + } + if res := resolve_link_fully(os.join_path(sb.after(0), p[i]), max_link_depth) { + if os.is_abs_path(res) { + sb.clear() + sb.write_string(res) + } else { + sb.write_string(os.path_separator + res) + } + } else { + return err + } + if mode == .all_must_exist || (mode == .all_but_last_must_exist && i < p.len - 1) { + if !os.exists(sb.after(0)) { + ok = false + } + } + } + return os.abs_path(sb.str()), ok +} + +fn canonicalize_strip(path string) string { + mut sb := strings.new_builder(path.len) + p := path.split(os.path_separator) + if path.len > 2 && (path[0] == `\\` || path[0] == `/`) && path[1] == path[0] { + sb.write_string(path[0..2]) + } else { + sb.write_string(p[0]) + } + for i := 1; i < p.len; i++ { + if p[i] == '..' { + if sb.len > 1 { + cur := sb.str() + parent := os.dir(cur) + if parent != cur { + sb.clear() + sb.write_string(parent) + } + } + continue + } + if p[i] == '.' { + continue + } + sb.write_string(os.path_separator + p[i]) + } + return os.abs_path(sb.str()) +} + +fn make_relative(path string, base string) string { + if base == '' { + return path + } + if !os.is_abs_path(base) { + return path + } + if path == base { + return '.' + } + if path.starts_with(base + os.path_separator) { + return path[base.len + 1..] + } + if base.starts_with(path + os.path_separator) { + mut rel := '' + rest := base[path.len + 1..] + for part in rest.split(os.path_separator) { + if part == '' { + continue + } + if rel == '' { + rel = '..' + } else { + rel += '/..' + } + } + return rel + } + mut rel := '' + mut p := path + for p != base && !p.starts_with(base + os.path_separator) { + parent := os.dir(p) + if parent == p { + break + } + if rel == '' { + rel = '..' + } else { + rel = '../' + rel + } + p = parent + } + if p == base { + return rel + } + if p.starts_with(base + os.path_separator) { + rel += '/' + p[base.len + 1..] + } + return rel +} + +fn realpath(settings Settings) { + mut exit_code := 0 + for path in settings.target_files { + abs := os.abs_path(path) + if settings.strip { + mut result := canonicalize_strip(abs) + if settings.relative_to != '' { + result = make_relative(result, settings.relative_to) + } else if settings.relative_base != '' { + if !result.starts_with(settings.relative_base + os.path_separator) + && result != settings.relative_base { + } else { + result = make_relative(result, settings.relative_base) + } + } + if settings.zero { + print(result) + print(u8(0).ascii_str()) + } else { + println(result) + } + } else { + if cpath, exists := canonicalize(abs, settings.mode, settings.resolve_mode) { + if !exists { + if !settings.quiet { + app.eprintln('${path}: No such file or directory') + exit_code = 1 + } + } else { + mut out := cpath + if settings.relative_to != '' { + out = make_relative(out, settings.relative_to) + } else if settings.relative_base != '' { + if !out.starts_with(settings.relative_base + os.path_separator) + && out != settings.relative_base { + // keep absolute + } else { + out = make_relative(out, settings.relative_base) + } + } + if settings.zero { + print(out) + print(u8(0).ascii_str()) + } else { + println(out) + } + } + } else { + if !settings.quiet { + app.eprintln('${path}: ${err.msg()}') + exit_code = 1 + } + } + } + } + exit(exit_code) +} + +fn args() Settings { + mut fp := app.make_flag_parser(os.args) + mut st := Settings{} + canon_all := fp.bool('canonicalize-existing', `e`, false, + 'canonicalize by following every symlink in every component of the given name recursively, all components must exist') + canon_none := fp.bool('canonicalize-missing', `m`, false, + 'canonicalize by following every symlink in every component of the given name recursively, without requirements on components existence') + logical := fp.bool('logical', `L`, false, + 'resolve .. components before symlinks') + _ = fp.bool('physical', `P`, false, + 'resolve symlinks as encountered (default)', flag.FlagConfig{}) + st.quiet = fp.bool('quiet', `q`, false, 'suppress most error messages') + st.strip = fp.bool('strip', `s`, false, "don't expand symlinks") + _ = fp.bool('no-symlinks', 0, false, "don't expand symlinks", flag.FlagConfig{}) + st.zero = fp.bool('zero', `z`, false, 'end each output line with NUL, not newline') + rel_to := fp.string('relative-to', 0, '', 'print the resolved path relative to DIR') + rel_base := fp.string('relative-base', 0, '', 'print absolute paths except for PATHs beneath DIR') + st.relative_to = if rel_to != '' { os.abs_path(rel_to) } else { '' } + st.relative_base = if rel_base != '' { os.abs_path(rel_base) } else { '' } + mut rem_pars := fp.remaining_parameters() + if rem_pars.len == 0 { + app.quit(message: 'missing operand', show_help_advice: true) + } + if canon_all { + st.mode = .all_must_exist + } else if canon_none { + st.mode = .none_need_exist + } else { + st.mode = .all_but_last_must_exist + } + if logical { + st.resolve_mode = .logical + } else { + st.resolve_mode = .physical + } + st.target_files = rem_pars + return st +} + +fn main() { + realpath(args()) +} diff --git a/src/realpath/realpath_nix.c.v b/src/realpath/realpath_nix.c.v new file mode 100644 index 00000000..77df480c --- /dev/null +++ b/src/realpath/realpath_nix.c.v @@ -0,0 +1,17 @@ +import os + +#include +#include + +fn C.readlink(pathname &char, buf &char, bufsiz usize) int + +pub fn do_readlink(path string) !string { + mut result := [os.max_path_len]u8{} + size := C.readlink(&char(path.str), &char(&result), os.max_path_len) + if size < 0 { + return os.error_posix() + } + result[size] = 0 + s := unsafe { tos_clone(&result[0]) } + return s +} diff --git a/src/realpath/realpath_test.v b/src/realpath/realpath_test.v new file mode 100644 index 00000000..251441df --- /dev/null +++ b/src/realpath/realpath_test.v @@ -0,0 +1,162 @@ +import common.testing +import os + +const rig = testing.prepare_rig(util: 'realpath') + +fn testsuite_begin() { + rig.assert_platform_util() + os.write_file('a', '')! + os.write_file('b', '42')! + os.mkdir('c')! + os.write_file('c/a', '')! + os.write_file('c/b', '42')! + + $if !windows { + os.symlink('b', 'link_to_b')! + os.symlink('c', 'link_to_c')! + os.symlink('link_to_c', 'link_to_link_to_c')! + os.symlink('link_to_link_to_c', 'link_to_link_to_link_to_c')! + os.symlink('recursive_link', 'recursive_link')! + os.chdir('c')! + os.symlink('..', 'c_up')! + os.symlink('.', 'c_same')! + os.chdir('..')! + } +} + +fn testsuite_end() { + $if !windows { + os.rm('link_to_link_to_link_to_c')! + os.rm('link_to_link_to_c')! + os.rm('link_to_c')! + os.rm('link_to_b')! + os.rm('recursive_link')! + os.rm('c/c_up')! + os.rm('c/c_same')! + } + os.rm('c/b')! + os.rm('c/a')! + os.rmdir('c')! + os.rm('b')! + os.rm('a')! +} + +fn test_help_and_version() { + rig.assert_help_and_version_options_work() +} + +fn test_compare() { + rig.assert_same_exit_code('') + rig.assert_same_results('.') + rig.assert_same_results('..') + rig.assert_same_results('-e .') + rig.assert_same_results('-e ..') + rig.assert_same_results('-m .') + rig.assert_same_results('-m ..') + rig.assert_same_results('-e a') + rig.assert_same_results('-e b') + rig.assert_same_results('-m a') + rig.assert_same_results('-e .') + rig.assert_same_results('-m .') + rig.assert_same_results('-e ..') + rig.assert_same_results('-m ..') + rig.assert_same_results('-e a') + rig.assert_same_results('-e b') + rig.assert_same_results('-m a') + rig.assert_same_results('-m b') + rig.assert_same_results('-e c') + rig.assert_same_results('-m c') + rig.assert_same_results('-e c/a') + rig.assert_same_results('-m c/a') + rig.assert_same_results('-e c/b') + rig.assert_same_results('-m c/b') + rig.assert_same_results('-e does_not_exist') + rig.assert_same_results('-m does_not_exist') + rig.assert_same_results('-e does_not_exist/neither_does_this') + rig.assert_same_results('-m does_not_exist/neither_does_this') + rig.assert_same_results('-q does_not_exist') + rig.assert_same_results('-q does_not_exist/neither_does_this') + rig.assert_same_results('-s .') + rig.assert_same_results('-s ..') + rig.assert_same_results('-s a') + rig.assert_same_results('-s b') + rig.assert_same_results('-s c') + rig.assert_same_results('-s c/a') + rig.assert_same_results('-s c/b') + rig.assert_same_results('-s does_not_exist') + rig.assert_same_results('--strip .') + rig.assert_same_results('--strip a') + rig.assert_same_results('--no-symlinks .') + rig.assert_same_results('--no-symlinks a') + rig.assert_same_results('-z .') + rig.assert_same_results('-z a') + rig.assert_same_results('-z does_not_exist') + rig.assert_same_results('--relative-to=. .') + rig.assert_same_results('--relative-to=. a') + rig.assert_same_results('--relative-to=. c') + rig.assert_same_results('--relative-to=. c/a') + rig.assert_same_results('--relative-to=c c/a') + rig.assert_same_results('--relative-to=c c/b') + rig.assert_same_results('--relative-to=. ..') + rig.assert_same_results('--relative-base=. .') + rig.assert_same_results('--relative-base=. a') + rig.assert_same_results('--relative-base=. c') + rig.assert_same_results('--relative-base=. c/a') + rig.assert_same_results('--relative-base=c c/a') + rig.assert_same_results('--relative-base=c c/b') + rig.assert_same_results('--relative-base=. ..') + rig.assert_same_results('-L .') + rig.assert_same_results('-L ..') + rig.assert_same_results('-L a') + rig.assert_same_results('-L c/a') + rig.assert_same_results('-P .') + rig.assert_same_results('-P ..') + rig.assert_same_results('-P a') + rig.assert_same_results('-P c/a') + rig.assert_same_results('-Le .') + rig.assert_same_results('-Le ..') + rig.assert_same_results('-Le a') + rig.assert_same_results('-Le c/a') + rig.assert_same_results('-Pe .') + rig.assert_same_results('-Pe ..') + rig.assert_same_results('-Pe a') + rig.assert_same_results('-Pe c/a') + rig.assert_same_results('-Lm .') + rig.assert_same_results('-Lm ..') + rig.assert_same_results('-Lm a') + rig.assert_same_results('-Lm c/a') + rig.assert_same_results('-Pm .') + rig.assert_same_results('-Pm ..') + rig.assert_same_results('-Pm a') + rig.assert_same_results('-Pm c/a') + $if !windows { + rig.assert_same_results('-e link_to_b') + rig.assert_same_results('-e link_to_c') + rig.assert_same_results('-e link_to_link_to_c') + rig.assert_same_results('-m link_to_b') + rig.assert_same_results('-m link_to_c') + rig.assert_same_results('-m link_to_link_to_c') + rig.assert_same_results('-e link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/a') + rig.assert_same_results('-m link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/a') + rig.assert_same_results('-e link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/does_not_exist') + rig.assert_same_results('-m link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/does_not_exist/neither_does_this') + rig.assert_same_results('link_to_b') + rig.assert_same_results('link_to_c') + rig.assert_same_results('link_to_link_to_c') + rig.assert_same_results('link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/a') + rig.assert_same_results('link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/b') + rig.assert_same_results('-s link_to_b') + rig.assert_same_results('-s link_to_c') + rig.assert_same_results('-s link_to_link_to_c') + rig.assert_same_results('-L link_to_c/c_up') + rig.assert_same_results('-P link_to_c/c_up') + rig.assert_same_results('-L link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/a') + rig.assert_same_results('-P link_to_link_to_c/c_same/c_same/c_up/link_to_c/c_up/a') + rig.assert_same_results('--relative-to=. link_to_b') + rig.assert_same_results('--relative-to=. link_to_c') + rig.assert_same_results('--relative-to=. link_to_link_to_c') + rig.assert_same_results('--relative-base=. link_to_b') + rig.assert_same_results('--relative-base=. link_to_c') + rig.assert_same_results('--relative-base=. link_to_link_to_c') + } +} diff --git a/src/realpath/realpath_windows.c.v b/src/realpath/realpath_windows.c.v new file mode 100644 index 00000000..b5f57743 --- /dev/null +++ b/src/realpath/realpath_windows.c.v @@ -0,0 +1,48 @@ +fn C.CreateFileW(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32, dwFlagsAndAttributes u32, hTemplateFile voidptr) voidptr +fn C.GetFinalPathNameByHandleW(hFile voidptr, lpFilePath &u16, nSize u32, dwFlags u32) u32 +fn C.GetFileAttributesW(lpFilename &u16) u32 +fn C.GetLastError() u32 + +const max_path_buffer_size = u32(512) + +const file_attribute_reparse_point = u32(0x400) +const invalid_file_attributes = u32(0xffffffff) + +fn is_link(path string) bool { + attrs := unsafe { C.GetFileAttributesW(&u16(path.to_wide())) } + return attrs != invalid_file_attributes && (attrs & file_attribute_reparse_point) != 0 +} + +fn win32_error() !string { + code := int(C.GetLastError()) + return error('the system reported error ${code}') +} + +fn do_readlink(path string) !string { + file := C.CreateFile(path.to_wide(), 0x80000000, 1, 0, 3, 0x80, 0) + if file != voidptr(-1) { + defer { + C.CloseHandle(file) + } + final_path := [max_path_buffer_size]u8{} + final_len := C.GetFinalPathNameByHandleW(file, unsafe { &u16(&final_path[0]) }, + max_path_buffer_size, 0) + if final_len == 0 { + return win32_error() + } + if final_len < max_path_buffer_size { + sret := unsafe { string_from_wide2(&u16(&final_path[0]), int(final_len)) } + defer { + unsafe { sret.free() } + } + assert sret[0..4] == r'\\?\' + sret_slice := sret[4..] + res := sret_slice.clone() + return res + } else { + return error('Final path length (${final_len}) exceeds buffer length (${max_path_buffer_size}).') + } + } else { + return win32_error() + } +}