/src/luarocks/fs/win32.lua

http://github.com/keplerproject/luarocks · Lua · 354 lines · 315 code · 15 blank · 24 comment · 21 complexity · 46e689192997178541a392b384374321 MD5 · raw file

  1. --- Windows implementation of filesystem and platform abstractions.
  2. -- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
  3. -- used by this module.
  4. local win32 = {}
  5. local fs = require("luarocks.fs")
  6. local cfg = require("luarocks.core.cfg")
  7. local dir = require("luarocks.dir")
  8. local path = require("luarocks.path")
  9. local util = require("luarocks.util")
  10. -- Monkey patch io.popen and os.execute to make sure quoting
  11. -- works as expected.
  12. -- See http://lua-users.org/lists/lua-l/2013-11/msg00367.html
  13. local _prefix = "type NUL && "
  14. local _popen, _execute = io.popen, os.execute
  15. io.popen = function(cmd, ...) return _popen(_prefix..cmd, ...) end
  16. os.execute = function(cmd, ...) return _execute(_prefix..cmd, ...) end
  17. --- Annotate command string for quiet execution.
  18. -- @param cmd string: A command-line string.
  19. -- @return string: The command-line, with silencing annotation.
  20. function win32.quiet(cmd)
  21. return cmd.." 2> NUL 1> NUL"
  22. end
  23. --- Annotate command string for execution with quiet stderr.
  24. -- @param cmd string: A command-line string.
  25. -- @return string: The command-line, with stderr silencing annotation.
  26. function win32.quiet_stderr(cmd)
  27. return cmd.." 2> NUL"
  28. end
  29. -- Split path into drive, root and the rest.
  30. -- Example: "c:\\hello\\world" becomes "c:" "\\" "hello\\world"
  31. -- if any part is missing from input, it becomes an empty string.
  32. local function split_root(pathname)
  33. local drive = ""
  34. local root = ""
  35. local rest = ""
  36. local unquoted = pathname:match("^['\"](.*)['\"]$")
  37. if unquoted then
  38. pathname = unquoted
  39. end
  40. if pathname:match("^.:") then
  41. drive = pathname:sub(1, 2)
  42. pathname = pathname:sub(3)
  43. end
  44. if pathname:match("^[\\/]") then
  45. root = pathname:sub(1, 1)
  46. rest = pathname:sub(2)
  47. else
  48. rest = pathname
  49. end
  50. return drive, root, rest
  51. end
  52. --- Quote argument for shell processing. Fixes paths on Windows.
  53. -- Adds double quotes and escapes.
  54. -- @param arg string: Unquoted argument.
  55. -- @return string: Quoted argument.
  56. function win32.Q(arg)
  57. assert(type(arg) == "string")
  58. -- Use Windows-specific directory separator for paths.
  59. -- Paths should be converted to absolute by now.
  60. local drive, root, rest = split_root(arg)
  61. if root ~= "" then
  62. arg = arg:gsub("/", "\\")
  63. end
  64. if arg == "\\" then
  65. return '\\' -- CHDIR needs special handling for root dir
  66. end
  67. -- URLs and anything else
  68. arg = arg:gsub('\\(\\*)"', '\\%1%1"')
  69. arg = arg:gsub('\\+$', '%0%0')
  70. arg = arg:gsub('"', '\\"')
  71. arg = arg:gsub('(\\*)%%', '%1%1"%%"')
  72. return '"' .. arg .. '"'
  73. end
  74. --- Quote argument for shell processing in batch files.
  75. -- Adds double quotes and escapes.
  76. -- @param arg string: Unquoted argument.
  77. -- @return string: Quoted argument.
  78. function win32.Qb(arg)
  79. assert(type(arg) == "string")
  80. -- Use Windows-specific directory separator for paths.
  81. -- Paths should be converted to absolute by now.
  82. local drive, root, rest = split_root(arg)
  83. if root ~= "" then
  84. arg = arg:gsub("/", "\\")
  85. end
  86. if arg == "\\" then
  87. return '\\' -- CHDIR needs special handling for root dir
  88. end
  89. -- URLs and anything else
  90. arg = arg:gsub('\\(\\*)"', '\\%1%1"')
  91. arg = arg:gsub('\\+$', '%0%0')
  92. arg = arg:gsub('"', '\\"')
  93. arg = arg:gsub('%%', '%%%%')
  94. return '"' .. arg .. '"'
  95. end
  96. --- Return an absolute pathname from a potentially relative one.
  97. -- @param pathname string: pathname to convert.
  98. -- @param relative_to string or nil: path to prepend when making
  99. -- pathname absolute, or the current dir in the dir stack if
  100. -- not given.
  101. -- @return string: The pathname converted to absolute.
  102. function win32.absolute_name(pathname, relative_to)
  103. assert(type(pathname) == "string")
  104. assert(type(relative_to) == "string" or not relative_to)
  105. relative_to = (relative_to or fs.current_dir()):gsub("[\\/]*$", "")
  106. local drive, root, rest = split_root(pathname)
  107. if root:match("[\\/]$") then
  108. -- It's an absolute path already. Ensure is not quoted.
  109. return drive .. root .. rest
  110. else
  111. -- It's a relative path, join it with base path.
  112. -- This drops drive letter from paths like "C:foo".
  113. return relative_to .. "/" .. rest
  114. end
  115. end
  116. --- Return the root directory for the given path.
  117. -- For example, for "c:\hello", returns "c:\"
  118. -- @param pathname string: pathname to use.
  119. -- @return string: The root of the given pathname.
  120. function win32.root_of(pathname)
  121. local drive, root, rest = split_root(fs.absolute_name(pathname))
  122. return drive .. root
  123. end
  124. --- Create a wrapper to make a script executable from the command-line.
  125. -- @param script string: Pathname of script to be made executable.
  126. -- @param target string: wrapper target pathname (without wrapper suffix).
  127. -- @param name string: rock name to be used in loader context.
  128. -- @param version string: rock version to be used in loader context.
  129. -- @return boolean or (nil, string): True if succeeded, or nil and
  130. -- an error message.
  131. function win32.wrap_script(script, target, deps_mode, name, version, ...)
  132. assert(type(script) == "string" or not script)
  133. assert(type(target) == "string")
  134. assert(type(deps_mode) == "string")
  135. assert(type(name) == "string" or not name)
  136. assert(type(version) == "string" or not version)
  137. local batname = target .. ".bat"
  138. local wrapper = io.open(batname, "wb")
  139. if not wrapper then
  140. return nil, "Could not open "..batname.." for writing."
  141. end
  142. local lpath, lcpath = path.package_paths(deps_mode)
  143. local luainit = {
  144. "package.path="..util.LQ(lpath..";").."..package.path",
  145. "package.cpath="..util.LQ(lcpath..";").."..package.cpath",
  146. }
  147. local remove_interpreter = false
  148. if target == "luarocks" or target == "luarocks-admin" then
  149. if cfg.is_binary then
  150. remove_interpreter = true
  151. end
  152. luainit = {
  153. "package.path="..util.LQ(package.path),
  154. "package.cpath="..util.LQ(package.cpath),
  155. }
  156. end
  157. if name and version then
  158. local addctx = "local k,l,_=pcall(require,'luarocks.loader') _=k " ..
  159. "and l.add_context('"..name.."','"..version.."')"
  160. table.insert(luainit, addctx)
  161. end
  162. local argv = {
  163. fs.Qb(dir.path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)),
  164. "-e",
  165. fs.Qb(table.concat(luainit, ";")),
  166. script and fs.Qb(script) or "%I%",
  167. ...
  168. }
  169. if remove_interpreter then
  170. table.remove(argv, 1)
  171. table.remove(argv, 1)
  172. table.remove(argv, 1)
  173. end
  174. wrapper:write("@echo off\r\n")
  175. wrapper:write("setlocal\r\n")
  176. if not script then
  177. wrapper:write([[IF "%*"=="" (set I=-i) ELSE (set I=)]] .. "\r\n")
  178. end
  179. wrapper:write("set "..fs.Qb("LUAROCKS_SYSCONFDIR="..cfg.sysconfdir) .. "\r\n")
  180. wrapper:write(table.concat(argv, " ") .. " %*\r\n")
  181. wrapper:write("exit /b %ERRORLEVEL%\r\n")
  182. wrapper:close()
  183. return true
  184. end
  185. function win32.is_actual_binary(name)
  186. name = name:lower()
  187. if name:match("%.bat$") or name:match("%.exe$") then
  188. return true
  189. end
  190. return false
  191. end
  192. function win32.copy_binary(filename, dest)
  193. local ok, err = fs.copy(filename, dest)
  194. if not ok then
  195. return nil, err
  196. end
  197. local exe_pattern = "%.[Ee][Xx][Ee]$"
  198. local base = dir.base_name(filename)
  199. dest = dir.dir_name(dest)
  200. if base:match(exe_pattern) then
  201. base = base:gsub(exe_pattern, ".lua")
  202. local helpname = dest.."/"..base
  203. local helper = io.open(helpname, "w")
  204. if not helper then
  205. return nil, "Could not open "..helpname.." for writing."
  206. end
  207. helper:write('package.path=\"'..package.path:gsub("\\","\\\\")..';\"..package.path\n')
  208. helper:write('package.cpath=\"'..package.path:gsub("\\","\\\\")..';\"..package.cpath\n')
  209. helper:close()
  210. end
  211. return true
  212. end
  213. --- Move a file on top of the other.
  214. -- The new file ceases to exist under its original name,
  215. -- and takes over the name of the old file.
  216. -- On Windows this is done by removing the original file and
  217. -- renaming the new file to its original name.
  218. -- @param old_file The name of the original file,
  219. -- which will be the new name of new_file.
  220. -- @param new_file The name of the new file,
  221. -- which will replace old_file.
  222. -- @return boolean or (nil, string): True if succeeded, or nil and
  223. -- an error message.
  224. function win32.replace_file(old_file, new_file)
  225. os.remove(old_file)
  226. return os.rename(new_file, old_file)
  227. end
  228. function win32.is_dir(file)
  229. file = fs.absolute_name(file)
  230. file = dir.normalize(file)
  231. local fd, _, code = io.open(file, "r")
  232. if code == 13 then -- directories return "Permission denied"
  233. fd, _, code = io.open(file .. "\\", "r")
  234. if code == 2 then -- directories return 2, files return 22
  235. return true
  236. end
  237. end
  238. if fd then
  239. fd:close()
  240. end
  241. return false
  242. end
  243. function win32.is_file(file)
  244. file = fs.absolute_name(file)
  245. file = dir.normalize(file)
  246. local fd, _, code = io.open(file, "r")
  247. if code == 13 then -- if "Permission denied"
  248. fd, _, code = io.open(file .. "\\", "r")
  249. if code == 2 then -- directories return 2, files return 22
  250. return false
  251. elseif code == 22 then
  252. return true
  253. end
  254. end
  255. if fd then
  256. fd:close()
  257. return true
  258. end
  259. return false
  260. end
  261. --- Test is file/dir is writable.
  262. -- Warning: testing if a file/dir is writable does not guarantee
  263. -- that it will remain writable and therefore it is no replacement
  264. -- for checking the result of subsequent operations.
  265. -- @param file string: filename to test
  266. -- @return boolean: true if file exists, false otherwise.
  267. function win32.is_writable(file)
  268. assert(file)
  269. file = dir.normalize(file)
  270. local result
  271. local tmpname = 'tmpluarockstestwritable.deleteme'
  272. if fs.is_dir(file) then
  273. local file2 = dir.path(file, tmpname)
  274. local fh = io.open(file2, 'wb')
  275. result = fh ~= nil
  276. if fh then fh:close() end
  277. if result then
  278. -- the above test might give a false positive when writing to
  279. -- c:\program files\ because of VirtualStore redirection on Vista and up
  280. -- So check whether it's really there
  281. result = fs.exists(file2)
  282. end
  283. os.remove(file2)
  284. else
  285. local fh = io.open(file, 'r+b')
  286. result = fh ~= nil
  287. if fh then fh:close() end
  288. end
  289. return result
  290. end
  291. --- Create a temporary directory.
  292. -- @param name_pattern string: name pattern to use for avoiding conflicts
  293. -- when creating temporary directory.
  294. -- @return string or (nil, string): name of temporary directory or (nil, error message) on failure.
  295. function win32.make_temp_dir(name_pattern)
  296. assert(type(name_pattern) == "string")
  297. name_pattern = dir.normalize(name_pattern)
  298. local temp_dir = os.getenv("TMP") .. "/luarocks_" .. name_pattern:gsub("/", "_") .. "-" .. tostring(math.floor(math.random() * 10000))
  299. local ok, err = fs.make_dir(temp_dir)
  300. if ok then
  301. return temp_dir
  302. else
  303. return nil, err
  304. end
  305. end
  306. function win32.tmpname()
  307. return os.getenv("TMP")..os.tmpname()
  308. end
  309. function win32.current_user()
  310. return os.getenv("USERNAME")
  311. end
  312. function win32.export_cmd(var, val)
  313. return ("SET %s=%s"):format(var, val)
  314. end
  315. function win32.system_cache_dir()
  316. return dir.path(fs.system_temp_dir(), "cache")
  317. end
  318. return win32