diff options
Diffstat (limited to 'luaotfload-tool.lua')
-rwxr-xr-x | luaotfload-tool.lua | 548 |
1 files changed, 510 insertions, 38 deletions
diff --git a/luaotfload-tool.lua b/luaotfload-tool.lua index 4d25d53..4afd9d1 100755 --- a/luaotfload-tool.lua +++ b/luaotfload-tool.lua @@ -4,7 +4,7 @@ -- DESCRIPTION: database functionality -- REQUIREMENTS: luaotfload 2.2 -- AUTHOR: Khaled Hosny, Élie Roux, Philipp Gesang --- VERSION: 2.3 +-- VERSION: 2.3a -- LICENSE: GPL v2 -- MODIFIED: 2013-06-02 19:23:54+0200 ----------------------------------------------------------------------- @@ -26,6 +26,8 @@ see the luaotfload documentation for more info. Report bugs to --doc]]-- +kpse.set_program_name "luatex" + --[[doc-- We test for Lua 5.1 by means of capability detection to see if @@ -36,7 +38,28 @@ see the luaotfload documentation for more info. Report bugs to --doc]]-- -kpse.set_program_name "luatex" + +local ioopen = io.open +local iowrite = io.write +local kpsefind_file = kpse.find_file +local lfsattributes = lfs.attributes +local lfsisfile = lfs.isfile +local lfsreadlink = lfs.readlink +local md5sumhexa = md5.sumhexa +local next = next +local osdate = os.date +local osremove = os.remove +local ostype = os.type +local stringexplode = string.explode +local stringformat = string.format +local stringlower = string.lower +local stringrep = string.rep +local stringsub = string.sub +local tableconcat = table.concat +local texiowrite_nl = texio.write_nl +local texiowrite = texio.write +local tonumber = tonumber +local type = type local runtime if _G.getfenv ~= nil then -- 5.1 or LJ @@ -44,26 +67,19 @@ if _G.getfenv ~= nil then -- 5.1 or LJ runtime = { "jit", jit.version } else runtime = { "stock", _VERSION } - local oldscript = kpse.find_file "luaotfload-legacy-tool.lua" + local oldscript = kpsefind_file "luaotfload-legacy-tool.lua" return require (oldscript) end else -- 5.2 runtime = { "stock", _VERSION } end -local stringexplode = string.explode -local stringformat = string.format -local stringlower = string.lower -local stringrep = string.rep -local tableconcat = table.concat -local texiowrite_nl = texio.write_nl -local texiowrite = texio.write -local C, Ct, P, S = lpeg.C, lpeg.Ct, lpeg.P, lpeg.S +local C, Cg, Ct, P, S = lpeg.C, lpeg.Cg, lpeg.Ct, lpeg.P, lpeg.S local lpegmatch = lpeg.match local loader_file = "luatexbase.loader.lua" -local loader_path = assert(kpse.find_file(loader_file, "lua"), +local loader_path = assert(kpsefind_file(loader_file, "lua"), "File '"..loader_file.."' not found") @@ -93,7 +109,7 @@ local config = config config.luaotfload = config.luaotfload or { } config.luaotfload.names_dir = config.luaotfload.names_dir or "names" config.luaotfload.cache_dir = config.luaotfload.cache_dir or "fonts" -config.luaotfload.names_file = config.luaotfload.names_file +config.luaotfload.index_file = config.luaotfload.index_file or "luaotfload-names.lua" do -- we don’t have file.basename and the likes yet, so inline parser ftw @@ -117,9 +133,16 @@ end config.lualibs = config.lualibs or { } config.lualibs.verbose = false config.lualibs.prefer_merged = true -config.lualibs.load_extended = false +config.lualibs.load_extended = true require "lualibs" +--- dofile "util-jsn.lua" --- awaiting fix + +local lua_of_json = utilities.json.tolua +local ioloaddata = io.loaddata +local tabletohash = table.tohash +local fileiswritable = file.iswritable +local fileisreadable = file.isreadable --[[doc-- \fileent{luatex-basics-gen.lua} calls functions from the @@ -143,17 +166,15 @@ local names = fonts.names local sanitize_string = names.sanitize_string ---local db_src_out = names.path.dir.."/"..names.path.basename -local names_plain = file.join ( - caches.getwritablepath (config.luaotfload.names_dir), - config.luaotfload.names_file) -local names_bin = file.replacesuffix (names_plain, "luc") +local pathdata = names.path +local names_plain = pathdata.index.lua +local names_bin = pathdata.index.luc local help_messages = { ["luaotfload-tool"] = [[ -Usage: %s [OPTION]... - +Usage: %s [OPTIONS...] + Operations on the LuaTeX font database. This tool is part of the luaotfload package. Valid options are: @@ -169,6 +190,8 @@ This tool is part of the luaotfload package. Valid options are: -V --version print version and exit -h --help print this message + --diagnose=CHECK run a self test procedure; one of “files”, + “permissions”, or “repository” --alias=<name> force behavior of “luaotfload-tool” or legacy “mkluatexfontdb” @@ -214,7 +237,7 @@ The font cache will be written to mkluatexfontdb = [[ Usage: %s [OPTION]... - + Rebuild or update the LuaTeX font database. Valid options: @@ -233,17 +256,26 @@ The font database will be saved to %s ]], + short = [[ +Usage: luaotfload-tool [--help] [--version] [--verbose=<lvl>] + [--update] [--force] [--prefer-texmf] + [--find=<font name>] [--fuzzy] [--info] [--inspect] + [--list=<criterion>] [--fields=<field list>] + [--cache=<directive>] [--flush-lookups] + [--show-blacklist] [--diagnose=<procedure>] + +Enter 'luaotfload-tool --help' for a larger list of options. +]] } -local help_msg = function ( ) - local template = help_messages[config.luaotfload.self] - or help_messages["luaotfload-tool"] - texiowrite_nl(stringformat(template, - config.luaotfload.self, - names_plain, - names_bin, - caches.getwritablepath ( - config.luaotfload.cache_dir))) +local help_msg = function (version) + local template = help_messages[version] + iowrite(stringformat(template, + config.luaotfload.self, + names_plain, + names_bin, + caches.getwritablepath ( + config.luaotfload.cache_dir))) end local version_msg = function ( ) @@ -467,7 +499,7 @@ local display_general = function (fullinfo) val = #fullinfo[key] end elseif mode == "d" then - val = os.date("%F %T", fullinfo[key]) + val = osdate("%F %T", fullinfo[key]) end if not val then val = "<none>" @@ -648,10 +680,11 @@ set. --]]-- local action_sequence = { - "loglevel", "help", "version", "blacklist", "cache", - "flush", "generate", "list", "query", + "loglevel", "help", "version", "diagnose", + "blacklist", "cache", "flush", "generate", + "list", "query", } -local action_pending = table.tohash(action_sequence, false) +local action_pending = tabletohash(action_sequence, false) action_pending.loglevel = true --- always set the loglevel action_pending.generate = false --- this is the default action @@ -662,7 +695,7 @@ actions.loglevel = function (job) logs.set_loglevel(job.log_level) logs.names_report("info", 3, "util", "Setting log level", "%d", job.log_level) - logs.names_report("log", 0, "util", "Lua=%s", _VERSION) + logs.names_report("log", 2, "util", "Lua=%s", _VERSION) return true, true end @@ -672,7 +705,7 @@ actions.version = function (job) end actions.help = function (job) - help_msg() + help_msg (job.help_version or "luaotfload-tool") return true, false end @@ -915,6 +948,437 @@ actions.list = function (job) return true, true end +do + local out = function (...) + logs.names_report (false, 0, "diagnose", ...) + end + + local verify_files = function (errcnt, info) + out "================ verify files =================" + local hashes = info.hashes + local notes = info.notes + if not hashes or #hashes == 0 then + out ("FAILED: cannot read checksums from %s.", status_file) + return 1/0 + elseif not notes then + out ("FAILED: cannot read commit metadata from %s.", + status_file) + return 1/0 + end + + out ("Luaotfload revision %s.", notes.revision) + out ("Committed by %s.", notes.committer) + out ("Timestamp %s.", notes.timestamp) + + local nhashes = #hashes + out ("Testing %d files for integrity.", nhashes) + for i = 1, nhashes do + local fname, canonicalsum = unpack (hashes[i]) + local location = kpsefind_file (fname) + or kpsefind_file (fname, "texmfscripts") + if not location then + errcnt = errcnt + 1 + out ("FAILED: file %s missing.", fname) + else + out ("File: %s.", location) + local raw = ioloaddata (location) + if not raw then + errcnt = errcnt + 1 + out ("FAILED: file %d not readable.", fname) + else + local sum = md5sumhexa (raw) + if sum ~= canonicalsum then + errcnt = errcnt + 1 + out ("FAILED: checksum mismatch for file %s.", + fname) + out ("Expected %s.", canonicalsum) + out ("Got %s.", sum) + else + out ("Ok, %s passed.", fname) + end + end + end + end + return errcnt + end + + local get_tentative_attributes = function (file) + if not lfsisfile (file) then + local chan = ioopen (file, "w") + if chan then + chan:close () + local attributes = lfsattributes (file) + os.remove (file) + return attributes + end + end + end + + local p_permissions = Ct(Cg(Ct(C(1) * C(1) * C(1)), "u") + * Cg(Ct(C(1) * C(1) * C(1)), "g") + * Cg(Ct(C(1) * C(1) * C(1)), "o")) + + local analyze_permissions = function (raw) + return lpegmatch (p_permissions, raw) + end + + local get_permissions = function (t, location) + local attributes = lfsattributes (location) + if not attributes and t == "f" then + attributes = get_tentative_attributes (location) + if not attributes then + return false + end + end + + local permissions + + if fileisreadable (location) then + --- link handling appears to be unnecessary because + --- lfs.attributes() will return the information on + --- the link target. + if mode == "link" then --follow and repeat + location = lfsreadlink (location) + attributes = lfsattributes (location) + end + end + + permissions = analyze_permissions (attributes.permissions) + + return { + location = location, + mode = attributes.mode, + owner = attributes.uid, --- useless on windows + permissions = permissions, + attributes = attributes, + } + end + + local check_conformance = function (spec, permissions, errcnt) + local uid = permissions.attributes.uid + local gid = permissions.attributes.gid + local raw = permissions.attributes.permissions + + out ("Owner: %d, group %d, permissions %s.", uid, gid, raw) + if ostype == "unix" then + if uid == 0 or gid == 0 then + out "Owned by the superuser, permission conflict likely." + errcnt = errcnt + 1 + end + end + + local user = permissions.permissions.u + if spec.r == true then + if user[1] == "r" then + out "Readable: ok." + else + out "Not readable: permissions need fixing." + errcnt = errcnt + 1 + end + end + + if spec.w == true then + if user[2] == "w" + or fileiswritable (permissions.location) then + out "Writable: ok." + else + out "Not writable: permissions need fixing." + errcnt = errcnt + 1 + end + end + + return errcnt + end + + local path = names.path + + local desired_permissions = { + { "d", {"r","w"}, function () return caches.getwritablepath () end }, + { "d", {"r","w"}, path.globals.prefix }, + { "f", {"r","w"}, path.index.lua }, + { "f", {"r","w"}, path.index.luc }, + { "f", {"r","w"}, path.lookups.lua }, + { "f", {"r","w"}, path.lookups.luc }, + } + + local check_permissions = function (errcnt) + out [[=============== file permissions ==============]] + for i = 1, #desired_permissions do + local t, spec, path = unpack (desired_permissions[i]) + if type (path) == "function" then + path = path () + end + + spec = tabletohash (spec) + + out ("Checking permissions of %s.", path) + + local permissions = get_permissions (t, path) + if permissions then + --inspect (permissions) + errcnt = check_conformance (spec, permissions, errcnt) + else + errcnt = errcnt + 1 + end + end + return errcnt + end + + local check_upstream + + if kpsefind_file ("https.lua", "lua") == nil then + check_upstream = function (errcnt) + out [[============= upstream repository ============= + Github API access requires the luasec library. + WARNING: Cannot retrieve repository data. + Grab it from <https://github.com/brunoos/luasec> + and retry.]] + return errcnt + end + else + --- github api stuff begin + local https = require "ssl.https" + + local gh_api_root = [[https://api.github.com]] + local release_url = [[https://github.com/lualatex/luaotfload/releases]] + local luaotfload_repo = [[lualatex/luaotfload]] + local user_agent = [[lualatex/luaotfload integrity check]] + local shortbytes = 8 + + local gh_shortrevision = function (rev) + return stringsub (rev, 1, shortbytes) + end + + local gh_encode_parameters = function (parameters) + local acc = {} + for field, value in next, parameters do + --- unsafe, non-urlencoded coz it’s all ascii chars + acc[#acc+1] = field .. "=" .. value + end + return "?" .. tableconcat (acc, "&") + end + + local gh_make_url = function (components, parameters) + local url = tableconcat ({ gh_api_root, + unpack (components) }, + "/") + if parameters then + url = url .. gh_encode_parameters (parameters) + end + return url + end + + local alright = [[HTTP/1.1 200 OK]] + + local gh_api_request = function (...) + local args = {...} + local nargs = #args + local final = args[nargs] + local request = { + url = "", + headers = { ["user-agent"] = user_agent }, + } + if type (final) == "table" then + args[nargs] = nil + request = gh_make_url (args, final) + else + request = gh_make_url (args) + end + + out ("Requesting <%s>.", request) + local response, code, headers, status + = https.request (request) + if status ~= alright then + out "Request failed!" + return false + end + return response + end + + local gh_api_checklimit = function (headers) + local rawlimit = gh_api_request "rate_limit" + local limitdata = lua_of_json (rawlimit) + if not limitdata and limitdata.rate then + out "Cannot parse API rate limit." + return false + end + limitdata = limitdata.rate + + local limit = tonumber (limitdata.limit) + local left = tonumber (limitdata.remaining) + local reset = tonumber (limitdata.reset) + + out ("%d of %d Github API requests left.", left, limit) + if left == 0 then + out ("Cannot make any more API requests.") + out ("Try again later at %s.", osdate ("%F %T", reset)) + end + return true + end + + local gh_tags = function () + out "Fetching tags from repository, please stand by." + local rawtags = gh_api_request ("repos", + luaotfload_repo, + "tags") + local taglist = lua_of_json (rawtags) + if not taglist or #taglist == 0 then + out "Cannot parse response." + return false + end + + local ntags = #taglist + out ("Repository contains %d tags.", ntags) + local _idx, latest = next (taglist) + out ("The most recent release is %s (revision %s).", + latest.name, + gh_shortrevision (latest.commit.sha)) + return latest + end + + local gh_compare = function (head, base) + if base == nil then + base = "HEAD" + end + out ("Fetching comparison between %s and %s, \z + please stand by.", + gh_shortrevision (head), + gh_shortrevision (base)) + local comparison = base .. "..." .. head + local rawstatus = gh_api_request ("repos", + luaotfload_repo, + "compare", + comparison) + local status = lua_of_json (rawstatus) + if not status then + out "Cannot parse response for status request." + return false + end + return status + end + + local gh_news = function (since) + local compared = gh_compare (since) + if not compared then + return false + end + local behind_by = compared.behind_by + local ahead_by = compared.ahead_by + local status = compared.status + out ("Comparison state: %s.", status) + if behind_by > 0 then + out ("Your Luaotfload is %d \z + revisions behind upstream.", + behind_by) + return behind_by + elseif status == "ahead" then + out "Since you are obviously from the future \z + I assume you already know the repository state." + else + out "Everything up to date. \z + Luaotfload is in sync with upstream." + end + return false + end + + local gh_catchup = function (current, latest) + local compared = gh_compare (latest, current) + local ahead_by = tonumber (compared.ahead_by) + if ahead_by > 0 then + local permalink_url = compared.permalink_url + out ("Your Luaotfload is %d revisions \z + behind the most recent release.", + ahead_by) + out ("To view the commit log, visit <%s>.", + permalink_url) + out ("You can grab an up to date tarball at <%s>.", + release_url) + return true + else + out "There weren’t any new releases in the meantime." + out "Luaotfload is up to date." + end + return false + end + + check_upstream = function (current) + out "============= upstream repository =============" + local _succ = gh_api_checklimit () + local behind = gh_news (current) + if behind then + local latest = gh_tags () + local _behind = gh_catchup (current, + latest.commit.sha, + latest.name) + end + end + + --- trivium: diff since the first revision as pushed by Élie + --- in 2009 + --- local firstrevision = "c3ccb3ee07e0a67171c24960966ae974e0dd8e98" + --- check_upstream (firstrevision) + end + --- github api stuff end + + local anamneses = { "files", "repository", "permissions" } + local status_file = "luaotfload-status" + + actions.diagnose = function (job) + local errcnt = 0 + local asked = job.asked_diagnostics + if asked == "all" or asked == "thorough" then + asked = tabletohash (anamneses, true) + else + asked = lpegmatch(split_comma, asked) + asked = tabletohash (asked, true) + end + + out "Loading file hashes." + local info = require (status_file) + + if asked.files == true then + errcnt = verify_files (errcnt, info) + end + if asked.permissions == true then + errcnt = check_permissions (errcnt) + end + if asked.repository == true then + --errcnt = check_upstream (info.notes.revision) + check_upstream (info.notes.revision) + end + + + if errcnt == 0 then --> success + out ("Everything appears to be in order, \z + you may sleep well.") + return true, false + end + out ( [[=============================================== + WARNING + =============================================== + + The diagnostic detected %d errors. + + This version of luaotfload may have been + tampered with. Modified versions of the + luaotfload source are unsupported. Read the log + carefully and get a clean version from CTAN or + github: + + × http://ctan.org/tex-archive/macros/luatex/generic/luaotfload + × https://github.com/lualatex/luaotfload/releases + + If you are uncertain as to how to proceed, then + ask on the lualatex mailing list: + + http://www.tug.org/mailman/listinfo/lualatex-dev + + =============================================== +]], errcnt) + return true, false + end +end + --- stuff to be carried out prior to exit local finalizers = { } @@ -954,6 +1418,7 @@ local process_cmdline = function ( ) -- unit -> jobspec local long_options = { alias = 1, cache = 1, + diagnose = 1, ["dry-run"] = "D", ["flush-lookups"] = "l", fields = 1, @@ -1049,13 +1514,20 @@ local process_cmdline = function ( ) -- unit -> jobspec config.luaotfload.prioritize = "texmf" elseif v == "b" then action_pending["blacklist"] = true + elseif v == "diagnose" then + action_pending["diagnose"] = true + result.asked_diagnostics = optarg[n] end end if config.luaotfload.self == "mkluatexfontdb" then + result.help_version = "mkluatexfontdb" action_pending["generate"] = true - result.log_level = math.max(2, result.log_level) + result.log_level = math.max(1, result.log_level) logs.set_logout"stdout" + elseif nopts == 0 then + action_pending["help"] = true + result.help_version = "short" end return result end |