summaryrefslogtreecommitdiff
path: root/src/luarocks/fs/win32/tools.lua
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
committerMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
commit5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch)
treedeca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/fs/win32/tools.lua
fetch tarballHEADmaster
Diffstat (limited to 'src/luarocks/fs/win32/tools.lua')
-rw-r--r--src/luarocks/fs/win32/tools.lua330
1 files changed, 330 insertions, 0 deletions
diff --git a/src/luarocks/fs/win32/tools.lua b/src/luarocks/fs/win32/tools.lua
new file mode 100644
index 0000000..86cbb45
--- /dev/null
+++ b/src/luarocks/fs/win32/tools.lua
@@ -0,0 +1,330 @@
+
+--- fs operations implemented with third-party tools for Windows platform abstractions.
+-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
+-- used by this module.
+local tools = {}
+
+local fs = require("luarocks.fs")
+local dir = require("luarocks.dir")
+local cfg = require("luarocks.core.cfg")
+
+local vars = setmetatable({}, { __index = function(_,k) return cfg.variables[k] end })
+
+local dir_sep = package.config:sub(1, 1)
+
+--- Adds prefix to command to make it run from a directory.
+-- @param directory string: Path to a directory.
+-- @param cmd string: A command-line string.
+-- @param exit_on_error bool: Exits immediately if entering the directory failed.
+-- @return string: The command-line with prefix.
+function tools.command_at(directory, cmd, exit_on_error)
+ local drive = directory:match("^([A-Za-z]:)")
+ local op = " & "
+ if exit_on_error then
+ op = " && "
+ end
+ local cmd_prefixed = "cd " .. fs.Q(directory) .. op .. cmd
+ if drive then
+ cmd_prefixed = drive .. " & " .. cmd_prefixed
+ end
+ return cmd_prefixed
+end
+
+--- Create a directory if it does not already exist.
+-- If any of the higher levels in the path name does not exist
+-- too, they are created as well.
+-- @param directory string: pathname of directory to create.
+-- @return boolean: true on success, false on failure.
+function tools.make_dir(directory)
+ assert(directory)
+ directory = dir.normalize(directory)
+ fs.execute_quiet(vars.MKDIR, directory)
+ if not fs.is_dir(directory) then
+ return false, "failed making directory "..directory
+ end
+ return true
+end
+
+--- Remove a directory if it is empty.
+-- Does not return errors (for example, if directory is not empty or
+-- if already does not exist)
+-- @param directory string: pathname of directory to remove.
+function tools.remove_dir_if_empty(directory)
+ assert(directory)
+ fs.execute_quiet(vars.RMDIR, directory)
+end
+
+--- Remove a directory if it is empty.
+-- Does not return errors (for example, if directory is not empty or
+-- if already does not exist)
+-- @param directory string: pathname of directory to remove.
+function tools.remove_dir_tree_if_empty(directory)
+ assert(directory)
+ while true do
+ fs.execute_quiet(vars.RMDIR, directory)
+ local parent = dir.dir_name(directory)
+ if parent ~= directory then
+ directory = parent
+ else
+ break
+ end
+ end
+end
+
+--- Copy a file.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function tools.copy(src, dest)
+ assert(src and dest)
+ if dest:match("[/\\]$") then dest = dest:sub(1, -2) end
+ local ok = fs.execute(vars.CP, src, dest)
+ if ok then
+ return true
+ else
+ return false, "Failed copying "..src.." to "..dest
+ end
+end
+
+--- Recursively copy the contents of a directory.
+-- @param src string: Pathname of source
+-- @param dest string: Pathname of destination
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message.
+function tools.copy_contents(src, dest)
+ assert(src and dest)
+ if not fs.is_dir(src) then
+ return false, src .. " is not a directory"
+ end
+ if fs.make_dir(dest) and fs.execute_quiet(vars.CP, "-dR", src.."\\*.*", dest) then
+ return true
+ else
+ return false, "Failed copying "..src.." to "..dest
+ end
+end
+
+--- Delete a file or a directory and all its contents.
+-- For safety, this only accepts absolute paths.
+-- @param arg string: Pathname of source
+-- @return nil
+function tools.delete(arg)
+ assert(arg)
+ assert(arg:match("^[a-zA-Z]?:?[\\/]"))
+ fs.execute_quiet("if exist "..fs.Q(arg.."\\*").." ( RMDIR /S /Q "..fs.Q(arg).." ) else ( DEL /Q /F "..fs.Q(arg).." )")
+end
+
+--- Recursively scan the contents of a directory.
+-- @param at string or nil: directory to scan (will be the current
+-- directory if none is given).
+-- @return table: an array of strings with the filenames representing
+-- the contents of a directory. Paths are returned with forward slashes.
+function tools.find(at)
+ assert(type(at) == "string" or not at)
+ if not at then
+ at = fs.current_dir()
+ end
+ if not fs.is_dir(at) then
+ return {}
+ end
+ local result = {}
+ local pipe = io.popen(fs.command_at(at, fs.quiet_stderr(vars.FIND), true))
+ for file in pipe:lines() do
+ -- Windows find is a bit different
+ local first_two = file:sub(1,2)
+ if first_two == ".\\" or first_two == "./" then file=file:sub(3) end
+ if file ~= "." then
+ table.insert(result, (file:gsub("[\\/]", dir_sep)))
+ end
+ end
+ pipe:close()
+ return result
+end
+
+--- Compress files in a .zip archive.
+-- @param zipfile string: pathname of .zip archive to be created.
+-- @param ... Filenames to be stored in the archive are given as
+-- additional arguments.
+-- @return boolean: true on success, nil and error message on failure.
+function tools.zip(zipfile, ...)
+ if fs.execute_quiet(vars.SEVENZ.." -aoa a -tzip", zipfile, ...) then
+ return true
+ else
+ return nil, "failed compressing " .. zipfile
+ end
+end
+
+--- Uncompress files from a .zip archive.
+-- @param zipfile string: pathname of .zip archive to be extracted.
+-- @return boolean: true on success, nil and error message on failure.
+function tools.unzip(zipfile)
+ assert(zipfile)
+ if fs.execute_quiet(vars.SEVENZ.." -aoa x", zipfile) then
+ return true
+ else
+ return nil, "failed extracting " .. zipfile
+ end
+end
+
+local function sevenz(default_ext, infile, outfile)
+ assert(type(infile) == "string")
+ assert(outfile == nil or type(outfile) == "string")
+
+ local dropext = infile:gsub("%."..default_ext.."$", "")
+ local outdir = dir.dir_name(dropext)
+
+ infile = fs.absolute_name(infile)
+
+ local cmdline = vars.SEVENZ.." -aoa -t* -o"..fs.Q(outdir).." x "..fs.Q(infile)
+ local ok, err = fs.execute_quiet(cmdline)
+ if not ok then
+ return nil, "failed extracting " .. infile
+ end
+
+ if outfile then
+ outfile = fs.absolute_name(outfile)
+ dropext = fs.absolute_name(dropext)
+ ok, err = os.rename(dropext, outfile)
+ if not ok then
+ return nil, "failed creating new file " .. outfile
+ end
+ end
+
+ return true
+end
+
+--- Uncompresses a .gz file.
+-- @param infile string: pathname of .gz file to be extracted.
+-- @param outfile string or nil: pathname of output file to be produced.
+-- If not given, name is derived from input file.
+-- @return boolean: true on success; nil and error message on failure.
+function tools.gunzip(infile, outfile)
+ return sevenz("gz", infile, outfile)
+end
+
+--- Uncompresses a .bz2 file.
+-- @param infile string: pathname of .bz2 file to be extracted.
+-- @param outfile string or nil: pathname of output file to be produced.
+-- If not given, name is derived from input file.
+-- @return boolean: true on success; nil and error message on failure.
+function tools.bunzip2(infile, outfile)
+ return sevenz("bz2", infile, outfile)
+end
+
+--- Helper function for fs.set_permissions
+-- @return table: an array of all system users
+local function get_system_users()
+ local exclude = {
+ [""] = true,
+ ["Name"] = true,
+ ["\128\164\172\168\173\168\225\226\224\160\226\174\224"] = true, -- Administrator in cp866
+ ["Administrator"] = true,
+ }
+ local result = {}
+ local fd = assert(io.popen("wmic UserAccount get name"))
+ for user in fd:lines() do
+ user = user:gsub("%s+$", "")
+ if not exclude[user] then
+ table.insert(result, user)
+ end
+ end
+ return result
+end
+
+--- Set permissions for file or directory
+-- @param filename string: filename whose permissions are to be modified
+-- @param mode string ("read" or "exec"): permission to set
+-- @param scope string ("user" or "all"): the user(s) to whom the permission applies
+-- @return boolean or (boolean, string): true on success, false on failure,
+-- plus an error message
+function tools.set_permissions(filename, mode, scope)
+ assert(filename and mode and scope)
+
+ if scope == "user" then
+ local perms
+ if mode == "read" then
+ perms = "(R,W,M)"
+ elseif mode == "exec" then
+ perms = "(F)"
+ end
+
+ local ok
+ -- Take ownership of the given file
+ ok = fs.execute_quiet("takeown /f " .. fs.Q(filename))
+ if not ok then
+ return false, "Could not take ownership of the given file"
+ end
+ local username = os.getenv('USERNAME')
+ -- Grant the current user the proper rights
+ ok = fs.execute_quiet(vars.ICACLS .. " " .. fs.Q(filename) .. " /inheritance:d /grant:r " .. fs.Q(username) .. ":" .. perms)
+ if not ok then
+ return false, "Failed setting permission " .. mode .. " for " .. scope
+ end
+ -- Finally, remove all the other users from the ACL in order to deny them access to the file
+ for _, user in pairs(get_system_users()) do
+ if username ~= user then
+ local ok = fs.execute_quiet(vars.ICACLS .. " " .. fs.Q(filename) .. " /remove " .. fs.Q(user))
+ if not ok then
+ return false, "Failed setting permission " .. mode .. " for " .. scope
+ end
+ end
+ end
+ elseif scope == "all" then
+ local my_perms, others_perms
+ if mode == "read" then
+ my_perms = "(R,W,M)"
+ others_perms = "(R)"
+ elseif mode == "exec" then
+ my_perms = "(F)"
+ others_perms = "(RX)"
+ end
+
+ local ok
+ -- Grant permissions available to all users
+ ok = fs.execute_quiet(vars.ICACLS .. " " .. fs.Q(filename) .. " /inheritance:d /grant:r *S-1-1-0:" .. others_perms)
+ if not ok then
+ return false, "Failed setting permission " .. mode .. " for " .. scope
+ end
+
+ -- Grant permissions available only to the current user
+ ok = fs.execute_quiet(vars.ICACLS .. " " .. fs.Q(filename) .. " /inheritance:d /grant \"%USERNAME%\":" .. my_perms)
+
+ -- This may not be necessary if the above syntax is correct,
+ -- but I couldn't really test the extra quotes above, so if that
+ -- fails we try again with the syntax used in previous releases
+ -- just to be on the safe side
+ if not ok then
+ ok = fs.execute_quiet(vars.ICACLS .. " " .. fs.Q(filename) .. " /inheritance:d /grant %USERNAME%:" .. my_perms)
+ end
+
+ if not ok then
+ return false, "Failed setting permission " .. mode .. " for " .. scope
+ end
+ end
+
+ return true
+end
+
+function tools.browser(url)
+ return fs.execute(cfg.web_browser..' "Starting docs..." '..fs.Q(url))
+end
+
+-- Set access and modification times for a file.
+-- @param filename File to set access and modification times for.
+-- @param time may be a string or number containing the format returned
+-- by os.time, or a table ready to be processed via os.time; if
+-- nil, current time is assumed.
+function tools.set_time(filename, time)
+ return true -- FIXME
+end
+
+function tools.lock_access(dirname)
+ -- NYI
+ return {}
+end
+
+function tools.unlock_access(lock)
+ -- NYI
+end
+
+return tools