Current File : //usr/share/texlive/texmf-dist/tex/luatex/luaotfload/luaotfload-diagnostics.lua |
-----------------------------------------------------------------------
-- FILE: luaotfload-diagnostics.lua
-- DESCRIPTION: part of luaotfload-tool / functionality accessible by the --diagnose option
-- REQUIREMENTS: luaotfload-tool.lua
-- AUTHOR: Philipp Gesang <phg@phi-gamma.net>
-----------------------------------------------------------------------
assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") {
name = "luaotfload-diagnostics",
version = "3.17", --TAGVERSION
date = "2021-01-08", --TAGDATE
description = "luaotfload-tool submodule / diagnostics",
license = "GPL v2.0"
}
local names = fonts.names
local kpse = require "kpse"
local kpseexpand_path = kpse.expand_path
local kpseexpand_var = kpse.expand_var
local kpsefind_file = kpse.find_file
local lfs = require "lfs"
local lfsattributes = lfs.attributes
local lfsisfile = lfs.isfile
local lfsreadlink = lfs.readlink
local md5 = require "md5"
local md5sumhexa = md5.sumhexa
local ioopen = io.open
local osgetenv = os.getenv
local osname = os.name
local osremove = os.remove
local ostype = os.type
local stringformat = string.format
local stringlower = string.lower
local stringsub = string.sub
local fileisreadable = file.isreadable
local fileiswritable = file.iswritable
local filesplitpath = file.splitpath
local filesuffix = file.suffix
local ioloaddata = io.loaddata
local lua_of_json = utilities.json.tolua
local tableconcat = table.concat
local tablesortedkeys = table.sortedkeys
local tabletohash = table.tohash
local lpeg = require "lpeg"
local C, Cg, Ct = lpeg.C, lpeg.Cg, lpeg.Ct
local lpegmatch = lpeg.match
local report = luaotfload.log.report
local function out (...)
report (false, 0, "diagnose", ...)
end
local parsers = luaotfload.parsers
local stripslashes = parsers.stripslashes
local splitcomma = parsers.splitcomma
local function check_index (errcnt)
out "================= font names =================="
local namedata = names.data()
if not namedata then
namedata = names.load ()
end
local mappings = namedata.mappings
if not namedata and namedata.formats and namedata.version then
out "Database corrupt."
return errcnt + 1
end
out ("Database version: %.3f.", namedata.meta.version)
out ("Font formats indexed: %s.",
tableconcat (namedata.meta.formats, ", "))
out ("%d font files indexed.", #mappings)
local by_format = { }
for i = 1, #mappings do
local record = mappings[i]
local format = stringlower (filesuffix (record.filename))
local count = by_format[format]
if count then
by_format[format] = count + 1
else
by_format[format] = 1
end
end
local formats = tablesortedkeys (by_format)
for i = 1, #formats do
local format = formats[i]
out ("%20s: %5d", format, by_format[format])
end
return errcnt
end
local function verify_files (errcnt)
out "================ verify files ================="
local status = config.luaotfload.status
local hashes = status.hashes
local notes = status.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 function get_tentative_attributes (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 function analyze_permissions (raw)
return lpegmatch (p_permissions, raw)
end
local function get_permissions (t, location)
if stringsub (location, #location) == "/" then
--- strip trailing slashes (lfs idiosyncrasy on Win)
location = lpegmatch (stripslashes, location)
end
local attributes = lfsattributes (location)
if not attributes then
print""
print("attr", location, attributes)
os.exit()
end
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 function check_conformance (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 function init_desired_permissions ()
local paths = config.luaotfload.paths
return {
{ "d", {"r","w"}, function () return caches.getwritablepath ("", "") end },
{ "d", {"r","w"}, paths.prefix },
{ "f", {"r","w"}, paths.index_path_lua .. ".gz" },
{ "f", {"r","w"}, paths.index_path_luc },
{ "f", {"r","w"}, paths.lookup_path_lua },
{ "f", {"r","w"}, paths.lookup_path_luc },
}
end
local function check_permissions (errcnt)
out [[=============== file permissions ==============]]
local desired_permissions = init_desired_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
function check_upstream (errcnt)
out [[============= upstream repository =============
WARNING: Cannot retrieve repository data.
Github API access requires the luasec library.
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/latex3/luaotfload/releases]]
local luaotfload_repo = [[latex3/luaotfload]]
local user_agent = [[latex3/luaotfload integrity check]]
local shortbytes = 8
local function gh_shortrevision (rev)
return stringsub (rev, 1, shortbytes)
end
local function gh_encode_parameters (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 function gh_make_url (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 function gh_api_request (...)
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 function gh_api_checklimit (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.")
if ostype == "unix" then
out ("Try again later at %s.", osdate ("%F %T", reset))
else --- windows doesn’t C99
out ("Try again later at %s.",
osdate ("%Y-%m-d %H:%M:%S", reset))
end
end
return true
end
local function gh_tags ()
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 function gh_compare (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 function gh_news (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 function gh_catchup (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
function check_upstream (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 function print_envvar (var)
local val = osgetenv (var)
if val then
out ("%20s: %q", stringformat ("$%s", var), val)
return val
else
out ("%20s: <unset>", stringformat ("$%s", var))
end
end
local function print_path (var)
local val = osgetenv (var)
if val then
local paths = filesplitpath (val)
if paths then
local npaths = #paths
if npaths == 1 then
out ("%20s: %q", stringformat ("$%s", var), val)
elseif npaths > 1 then
out ("%20s: <%d items>", stringformat ("$%s", var), npaths)
for i = 1, npaths do
out (" +: %q", paths[i])
end
else
out ("%20s: <empty>")
end
end
else
out ("%20s: <unset>", stringformat ("$%s", var))
end
end
local function print_kpsevar (var)
var = "$" .. var
local val = kpseexpand_var (var)
if val and val ~= var then
out ("%20s: %q", var, val)
return val
else
out ("%20s: <empty or unset>", var)
end
end
local function print_kpsepath (var)
var = "$" .. var
local val = kpseexpand_path (var)
if val and val ~= "" then
local paths = filesplitpath (val)
if paths then
local npaths = #paths
if npaths == 1 then
out ("%20s: %q", var, paths[1])
elseif npaths > 1 then
out ("%20s: <%d items>", var, npaths)
for i = 1, npaths do
out (" +: %q", paths[i])
end
else
out ("%20s: <empty>")
end
end
else
out ("%20s: <empty or unset>", var)
end
end
--- this test first if a variable is set and then expands the
--- paths; this is necessitated by the fact that expand-path will
--- return the empty string both if the variable is unset and if
--- the directory does not exist
local function print_kpsepathvar (var)
local vvar = "$" .. var
local val = kpseexpand_var (vvar)
if val and val ~= vvar then
out ("%20s: %q", vvar, val)
print_kpsepath (var)
else
out ("%20s: <empty or unset>", var)
end
end
local function check_environment (errcnt)
out "============ environment settings ============="
out ("system: %s/%s", ostype, osname)
if ostype == "unix" and io.popen then
local chan = io.popen ("uname -a", "r")
if chan then
out ("info: %s", chan:read "*all")
chan:close ()
end
end
out "1) *shell environment*"
print_envvar "SHELL"
print_path "PATH"
print_path "OSFONTDIR"
print_envvar "USER"
if ostype == "windows" then
print_envvar "WINDIR"
print_envvar "CD"
print_path "TEMP"
elseif ostype == "unix" then
print_envvar "HOME"
print_envvar "PWD"
print_path "TMPDIR"
end
out "2) *kpathsea*"
print_kpsepathvar "OPENTYPEFONTS"
print_kpsepathvar "TTFONTS"
print_kpsepathvar "TEXMFCACHE"
print_kpsepathvar "TEXMFVAR"
--- the expansion of these can be quite large; as they aren’t
--- usually essential to luaotfload, we won’t dump every single
--- path
print_kpsevar "LUAINPUTS"
print_kpsevar "CLUAINPUTS"
return errcnt
end
local anamneses = {
"environment",
"files",
"index",
"repository",
"permissions"
}
local function diagnose (job)
local errcnt = 0
local asked = job.asked_diagnostics
if asked == "all" or asked == "thorough" then
asked = tabletohash (anamneses, true)
else
asked = lpegmatch (splitcomma, asked)
asked = tabletohash (asked, true)
end
if asked.index == true then
errcnt = check_index (errcnt)
asked.index = nil
end
if asked.environment == true then
errcnt = check_environment (errcnt)
asked.environment = nil
end
if asked.files == true then
errcnt = verify_files (errcnt)
asked.files = nil
end
if asked.permissions == true then
errcnt = check_permissions (errcnt)
asked.permissions = nil
end
if asked.repository == true then
local status = config.luaotfload.status
check_upstream (status.notes.revision)
asked.repository = nil
end
local rest = next (asked)
if rest ~= nil then --> something unknown
out ("Unknown diagnostic %q.", rest)
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://www.ctan.org/pkg/luaotfload
× https://github.com/latex3/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
return diagnose
-- vim:tw=71:sw=4:ts=4:expandtab