1060a805bSEd Maste#!/usr/libexec/flua 2060a805bSEd Maste 3060a805bSEd Maste-- SPDX-License-Identifier: BSD-2-Clause-FreeBSD 4060a805bSEd Maste-- 5060a805bSEd Maste-- Copyright(c) 2020 The FreeBSD Foundation. 6060a805bSEd Maste-- 7060a805bSEd Maste-- Redistribution and use in source and binary forms, with or without 8060a805bSEd Maste-- modification, are permitted provided that the following conditions 9060a805bSEd Maste-- are met: 10060a805bSEd Maste-- 1. Redistributions of source code must retain the above copyright 11060a805bSEd Maste-- notice, this list of conditions and the following disclaimer. 12060a805bSEd Maste-- 2. Redistributions in binary form must reproduce the above copyright 13060a805bSEd Maste-- notice, this list of conditions and the following disclaimer in the 14060a805bSEd Maste-- documentation and/or other materials provided with the distribution. 15060a805bSEd Maste-- 16060a805bSEd Maste-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17060a805bSEd Maste-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18060a805bSEd Maste-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19060a805bSEd Maste-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20060a805bSEd Maste-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21060a805bSEd Maste-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22060a805bSEd Maste-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23060a805bSEd Maste-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24060a805bSEd Maste-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25060a805bSEd Maste-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26060a805bSEd Maste-- SUCH DAMAGE. 27060a805bSEd Maste 28060a805bSEd Maste-- $FreeBSD$ 29060a805bSEd Maste 30060a805bSEd Mastefunction main(args) 31060a805bSEd Maste if #args == 0 then usage() end 32060a805bSEd Maste local filename 33060a805bSEd Maste local printall, checkonly, pkgonly = 34060a805bSEd Maste #args == 1, false, false 35060a805bSEd Maste local dcount, dsize, fuid, fgid, fid = 36060a805bSEd Maste false, false, false, false, false 37060a805bSEd Maste local verbose = false 38060a805bSEd Maste local w_notagdirs = false 39060a805bSEd Maste 40060a805bSEd Maste local i = 1 41060a805bSEd Maste while i <= #args do 42060a805bSEd Maste if args[i] == '-h' then 43060a805bSEd Maste usage(true) 44060a805bSEd Maste elseif args[i] == '-a' then 45060a805bSEd Maste printall = true 46060a805bSEd Maste elseif args[i] == '-c' then 47060a805bSEd Maste printall = false 48060a805bSEd Maste checkonly = true 49060a805bSEd Maste elseif args[i] == '-p' then 50060a805bSEd Maste printall = false 51060a805bSEd Maste pkgonly = true 52060a805bSEd Maste while i < #args do 53060a805bSEd Maste i = i+1 54060a805bSEd Maste if args[i] == '-count' then 55060a805bSEd Maste dcount = true 56060a805bSEd Maste elseif args[i] == '-size' then 57060a805bSEd Maste dsize = true 58060a805bSEd Maste elseif args[i] == '-fsetuid' then 59060a805bSEd Maste fuid = true 60060a805bSEd Maste elseif args[i] == '-fsetgid' then 61060a805bSEd Maste fgid = true 62060a805bSEd Maste elseif args[i] == '-fsetid' then 63060a805bSEd Maste fid = true 64060a805bSEd Maste else 65060a805bSEd Maste i = i-1 66060a805bSEd Maste break 67060a805bSEd Maste end 68060a805bSEd Maste end 69060a805bSEd Maste elseif args[i] == '-v' then 70060a805bSEd Maste verbose = true 71060a805bSEd Maste elseif args[i] == '-Wcheck-notagdir' then 72060a805bSEd Maste w_notagdirs = true 73060a805bSEd Maste elseif args[i]:match('^%-') then 74060a805bSEd Maste io.stderr:write('Unknown argument '..args[i]..'.\n') 75060a805bSEd Maste usage() 76060a805bSEd Maste else 77060a805bSEd Maste filename = args[i] 78060a805bSEd Maste end 79060a805bSEd Maste i = i+1 80060a805bSEd Maste end 81060a805bSEd Maste 82060a805bSEd Maste if filename == nil then 83060a805bSEd Maste io.stderr:write('Missing filename.\n') 84060a805bSEd Maste usage() 85060a805bSEd Maste end 86060a805bSEd Maste 87060a805bSEd Maste local sess = Analysis_session(filename, verbose, w_notagdirs) 88060a805bSEd Maste 89*f93d92f4SEd Maste local errors 90060a805bSEd Maste if printall then 91060a805bSEd Maste io.write('--- PACKAGE REPORTS ---\n') 92060a805bSEd Maste io.write(sess.pkg_report_full()) 93060a805bSEd Maste io.write('--- LINTING REPORTS ---\n') 94*f93d92f4SEd Maste errors = print_lints(sess) 95060a805bSEd Maste elseif checkonly then 96*f93d92f4SEd Maste errors = print_lints(sess) 97060a805bSEd Maste elseif pkgonly then 98060a805bSEd Maste io.write(sess.pkg_report_simple(dcount, dsize, { 99060a805bSEd Maste fuid and sess.pkg_issetuid or nil, 100060a805bSEd Maste fgid and sess.pkg_issetgid or nil, 101060a805bSEd Maste fid and sess.pkg_issetid or nil 102060a805bSEd Maste })) 103060a805bSEd Maste else 104060a805bSEd Maste io.stderr:write('This text should not be displayed.') 105060a805bSEd Maste usage() 106060a805bSEd Maste end 107*f93d92f4SEd Maste 108*f93d92f4SEd Maste if errors then 109*f93d92f4SEd Maste return 1 110*f93d92f4SEd Maste end 111060a805bSEd Masteend 112060a805bSEd Maste 113060a805bSEd Maste--- @param man boolean 114060a805bSEd Mastefunction usage(man) 115060a805bSEd Maste local sn = 'Usage: '..arg[0].. ' [-h] [-a | -c | -p [-count] [-size] [-f...]] [-W...] metalog-path \n' 116060a805bSEd Maste if man then 117060a805bSEd Maste io.write('\n') 118060a805bSEd Maste io.write(sn) 119060a805bSEd Maste io.write( 120060a805bSEd Maste[[ 121060a805bSEd Maste 122060a805bSEd MasteThe script reads METALOG file created by pkgbase (make packages) and generates 123060a805bSEd Mastereports about the installed system and issues. It accepts an mtree file in a 124060a805bSEd Masteformat that's returned by `mtree -c | mtree -C` 125060a805bSEd Maste 126060a805bSEd Maste Options: 127060a805bSEd Maste -a prints all scan results. this is the default option if no option 128060a805bSEd Maste is provided. 129060a805bSEd Maste -c lints the file and gives warnings/errors, including duplication 130060a805bSEd Maste and conflicting metadata 131060a805bSEd Maste -Wcheck-notagdir entries with dir type and no tags will be also 132060a805bSEd Maste included the first time they appear 133060a805bSEd Maste -p list all package names found in the file as exactly specified by 134060a805bSEd Maste `tags=package=...` 135060a805bSEd Maste -count display the number of files of the package 136060a805bSEd Maste -size display the size of the package 137060a805bSEd Maste -fsetgid only include packages with setgid files 138060a805bSEd Maste -fsetuid only include packages with setuid files 139060a805bSEd Maste -fsetid only include packages with setgid or setuid files 140060a805bSEd Maste -v verbose mode 141060a805bSEd Maste -h help page 142060a805bSEd Maste 143060a805bSEd Maste]]) 144060a805bSEd Maste os.exit() 145060a805bSEd Maste else 146060a805bSEd Maste io.stderr:write(sn) 147060a805bSEd Maste os.exit(1) 148060a805bSEd Maste end 149060a805bSEd Masteend 150060a805bSEd Maste 151060a805bSEd Maste--- @param sess Analysis_session 152060a805bSEd Mastefunction print_lints(sess) 153060a805bSEd Maste local dupwarn, duperr = sess.dup_report() 154060a805bSEd Maste io.write(dupwarn) 155060a805bSEd Maste io.write(duperr) 156060a805bSEd Maste local inodewarn, inodeerr = sess.inode_report() 157060a805bSEd Maste io.write(inodewarn) 158060a805bSEd Maste io.write(inodeerr) 159*f93d92f4SEd Maste return #duperr > 0 or #inodeerr > 0 160060a805bSEd Masteend 161060a805bSEd Maste 162060a805bSEd Maste--- @param t table 163060a805bSEd Mastefunction sortedPairs(t) 164060a805bSEd Maste local sortedk = {} 165060a805bSEd Maste for k in next, t do sortedk[#sortedk+1] = k end 166060a805bSEd Maste table.sort(sortedk) 167060a805bSEd Maste local i = 0 168060a805bSEd Maste return function() 169060a805bSEd Maste i = i + 1 170060a805bSEd Maste return sortedk[i], t[sortedk[i]] 171060a805bSEd Maste end 172060a805bSEd Masteend 173060a805bSEd Maste 174060a805bSEd Maste--- @param t table <T, U> 175060a805bSEd Maste--- @param f function <U -> U> 176060a805bSEd Mastefunction table_map(t, f) 177060a805bSEd Maste local res = {} 178060a805bSEd Maste for k, v in pairs(t) do res[k] = f(v) end 179060a805bSEd Maste return res 180060a805bSEd Masteend 181060a805bSEd Maste 182060a805bSEd Maste--- @class MetalogRow 183060a805bSEd Maste-- a table contaning file's info, from a line content from METALOG file 184060a805bSEd Maste-- all fields in the table are strings 185060a805bSEd Maste-- sample output: 186060a805bSEd Maste-- { 187060a805bSEd Maste-- filename = ./usr/share/man/man3/inet6_rthdr_segments.3.gz 188060a805bSEd Maste-- lineno = 5 189060a805bSEd Maste-- attrs = { 190060a805bSEd Maste-- gname = 'wheel' 191060a805bSEd Maste-- uname = 'root' 192060a805bSEd Maste-- mode = '0444' 193060a805bSEd Maste-- size = '1166' 194060a805bSEd Maste-- time = nil 195060a805bSEd Maste-- type = 'file' 196060a805bSEd Maste-- tags = 'package=clibs,debug' 197060a805bSEd Maste-- } 198060a805bSEd Maste-- } 199060a805bSEd Maste--- @param line string 200060a805bSEd Mastefunction MetalogRow(line, lineno) 201060a805bSEd Maste local res, attrs = {}, {} 202060a805bSEd Maste local filename, rest = line:match('^(%S+) (.+)$') 203060a805bSEd Maste -- mtree file has space escaped as '\\040', not affecting splitting 204060a805bSEd Maste -- string by space 205060a805bSEd Maste for attrpair in rest:gmatch('[^ ]+') do 206060a805bSEd Maste local k, v = attrpair:match('^(.-)=(.+)') 207060a805bSEd Maste attrs[k] = v 208060a805bSEd Maste end 209060a805bSEd Maste res.filename = filename 210060a805bSEd Maste res.linenum = lineno 211060a805bSEd Maste res.attrs = attrs 212060a805bSEd Maste return res 213060a805bSEd Masteend 214060a805bSEd Maste 215060a805bSEd Maste-- check if an array of MetalogRows are equivalent. if not, the first field 216060a805bSEd Maste-- that's different is returned secondly 217060a805bSEd Maste--- @param rows MetalogRow[] 218060a805bSEd Maste--- @param ignore_name boolean 219060a805bSEd Maste--- @param ignore_tags boolean 220060a805bSEd Mastefunction metalogrows_all_equal(rows, ignore_name, ignore_tags) 221060a805bSEd Maste local __eq = function(l, o) 222060a805bSEd Maste if not ignore_name and l.filename ~= o.filename then 223060a805bSEd Maste return false, 'filename' 224060a805bSEd Maste end 225060a805bSEd Maste -- ignoring linenum in METALOG file as it's not relavant 226060a805bSEd Maste for k in pairs(l.attrs) do 227060a805bSEd Maste if ignore_tags and k == 'tags' then goto continue end 228060a805bSEd Maste if l.attrs[k] ~= o.attrs[k] and o.attrs[k] ~= nil then 229060a805bSEd Maste return false, k 230060a805bSEd Maste end 231060a805bSEd Maste ::continue:: 232060a805bSEd Maste end 233060a805bSEd Maste return true 234060a805bSEd Maste end 235060a805bSEd Maste for _, v in ipairs(rows) do 236060a805bSEd Maste local bol, offby = __eq(v, rows[1]) 237060a805bSEd Maste if not bol then return false, offby end 238060a805bSEd Maste end 239060a805bSEd Maste return true 240060a805bSEd Masteend 241060a805bSEd Maste 242060a805bSEd Maste--- @param tagstr string 243060a805bSEd Mastefunction pkgname_from_tag(tagstr) 244060a805bSEd Maste local ext, pkgname, pkgend = '', '', '' 245060a805bSEd Maste for seg in tagstr:gmatch('[^,]+') do 246060a805bSEd Maste if seg:match('package=') then 247060a805bSEd Maste pkgname = seg:sub(9) 248060a805bSEd Maste elseif seg == 'development' or seg == 'profile' 249060a805bSEd Maste or seg == 'debug' or seg == 'docs' then 250060a805bSEd Maste pkgend = seg 251060a805bSEd Maste else 252060a805bSEd Maste ext = ext == '' and seg or ext..'-'..seg 253060a805bSEd Maste end 254060a805bSEd Maste end 255060a805bSEd Maste pkgname = pkgname 256060a805bSEd Maste ..(ext == '' and '' or '-'..ext) 257060a805bSEd Maste ..(pkgend == '' and '' or '-'..pkgend) 258060a805bSEd Maste return pkgname 259060a805bSEd Masteend 260060a805bSEd Maste 261060a805bSEd Maste--- @class Analysis_session 262060a805bSEd Maste--- @param metalog string 263060a805bSEd Maste--- @param verbose boolean 264060a805bSEd Maste--- @param w_notagdirs boolean turn on to also check directories 265060a805bSEd Mastefunction Analysis_session(metalog, verbose, w_notagdirs) 266bca4d270SEd Maste local stage_root = {} 267060a805bSEd Maste local files = {} -- map<string, MetalogRow[]> 268060a805bSEd Maste -- set is map<elem, bool>. if bool is true then elem exists 269060a805bSEd Maste local pkgs = {} -- map<string, set<string>> 270060a805bSEd Maste ----- used to keep track of files not belonging to a pkg. not used so 271060a805bSEd Maste ----- it is commented with ----- 272060a805bSEd Maste -----local nopkg = {} -- set<string> 273060a805bSEd Maste --- @public 274060a805bSEd Maste local swarn = {} 275060a805bSEd Maste --- @public 276060a805bSEd Maste local serrs = {} 277060a805bSEd Maste 278060a805bSEd Maste -- returns number of files in package and size of package 279060a805bSEd Maste -- nil is returned upon errors 280060a805bSEd Maste --- @param pkgname string 281060a805bSEd Maste local function pkg_size(pkgname) 282060a805bSEd Maste local filecount, sz = 0, 0 283060a805bSEd Maste for filename in pairs(pkgs[pkgname]) do 284060a805bSEd Maste local rows = files[filename] 285060a805bSEd Maste -- normally, there should be only one row per filename 286060a805bSEd Maste -- if these rows are equal, there should be warning, but it 287060a805bSEd Maste -- does not affect size counting. if not, it is an error 288060a805bSEd Maste if #rows > 1 and not metalogrows_all_equal(rows) then 289060a805bSEd Maste return nil 290060a805bSEd Maste end 291060a805bSEd Maste local row = rows[1] 292060a805bSEd Maste if row.attrs.type == 'file' then 293060a805bSEd Maste sz = sz + tonumber(row.attrs.size) 294060a805bSEd Maste end 295060a805bSEd Maste filecount = filecount + 1 296060a805bSEd Maste end 297060a805bSEd Maste return filecount, sz 298060a805bSEd Maste end 299060a805bSEd Maste 300060a805bSEd Maste --- @param pkgname string 301060a805bSEd Maste --- @param mode number 302060a805bSEd Maste local function pkg_ismode(pkgname, mode) 303060a805bSEd Maste for filename in pairs(pkgs[pkgname]) do 304060a805bSEd Maste for _, row in ipairs(files[filename]) do 305060a805bSEd Maste if tonumber(row.attrs.mode, 8) & mode ~= 0 then 306060a805bSEd Maste return true 307060a805bSEd Maste end 308060a805bSEd Maste end 309060a805bSEd Maste end 310060a805bSEd Maste return false 311060a805bSEd Maste end 312060a805bSEd Maste 313060a805bSEd Maste --- @param pkgname string 314060a805bSEd Maste --- @public 315060a805bSEd Maste local function pkg_issetuid(pkgname) 316060a805bSEd Maste return pkg_ismode(pkgname, 2048) 317060a805bSEd Maste end 318060a805bSEd Maste 319060a805bSEd Maste --- @param pkgname string 320060a805bSEd Maste --- @public 321060a805bSEd Maste local function pkg_issetgid(pkgname) 322060a805bSEd Maste return pkg_ismode(pkgname, 1024) 323060a805bSEd Maste end 324060a805bSEd Maste 325060a805bSEd Maste --- @param pkgname string 326060a805bSEd Maste --- @public 327060a805bSEd Maste local function pkg_issetid(pkgname) 328060a805bSEd Maste return pkg_issetuid(pkgname) or pkg_issetgid(pkgname) 329060a805bSEd Maste end 330060a805bSEd Maste 331060a805bSEd Maste -- sample return: 332060a805bSEd Maste -- { [*string]: { count=1, size=2, issetuid=true, issetgid=true } } 333060a805bSEd Maste local function pkg_report_helper_table() 334060a805bSEd Maste local res = {} 335060a805bSEd Maste for pkgname in pairs(pkgs) do 336060a805bSEd Maste res[pkgname] = {} 337060a805bSEd Maste res[pkgname].count, 338060a805bSEd Maste res[pkgname].size = pkg_size(pkgname) 339060a805bSEd Maste res[pkgname].issetuid = pkg_issetuid(pkgname) 340060a805bSEd Maste res[pkgname].issetgid = pkg_issetgid(pkgname) 341060a805bSEd Maste end 342060a805bSEd Maste return res 343060a805bSEd Maste end 344060a805bSEd Maste 345060a805bSEd Maste -- returns a string describing package scan report 346060a805bSEd Maste --- @public 347060a805bSEd Maste local function pkg_report_full() 348060a805bSEd Maste local sb = {} 349060a805bSEd Maste for pkgname, v in sortedPairs(pkg_report_helper_table()) do 350060a805bSEd Maste sb[#sb+1] = 'Package '..pkgname..':' 351060a805bSEd Maste if v.issetuid or v.issetgid then 352060a805bSEd Maste sb[#sb+1] = ''..table.concat({ 353060a805bSEd Maste v.issetuid and ' setuid' or '', 354060a805bSEd Maste v.issetgid and ' setgid' or '' }, '') 355060a805bSEd Maste end 356060a805bSEd Maste sb[#sb+1] = '\n number of files: '..(v.count or '?') 357060a805bSEd Maste ..'\n total size: '..(v.size or '?') 358060a805bSEd Maste sb[#sb+1] = '\n' 359060a805bSEd Maste end 360060a805bSEd Maste return table.concat(sb, '') 361060a805bSEd Maste end 362060a805bSEd Maste 363060a805bSEd Maste --- @param have_count boolean 364060a805bSEd Maste --- @param have_size boolean 365060a805bSEd Maste --- @param filters function[] 366060a805bSEd Maste --- @public 367060a805bSEd Maste -- returns a string describing package size report. 368060a805bSEd Maste -- sample: "mypackage 2 2048"* if both booleans are true 369060a805bSEd Maste local function pkg_report_simple(have_count, have_size, filters) 370060a805bSEd Maste filters = filters or {} 371060a805bSEd Maste local sb = {} 372060a805bSEd Maste for pkgname, v in sortedPairs(pkg_report_helper_table()) do 373060a805bSEd Maste local pred = true 374060a805bSEd Maste -- doing a foldl to all the function results with (and) 375060a805bSEd Maste for _, f in pairs(filters) do pred = pred and f(pkgname) end 376060a805bSEd Maste if pred then 377060a805bSEd Maste sb[#sb+1] = pkgname..table.concat({ 378060a805bSEd Maste have_count and (' '..(v.count or '?')) or '', 379060a805bSEd Maste have_size and (' '..(v.size or '?')) or ''}, '') 380060a805bSEd Maste ..'\n' 381060a805bSEd Maste end 382060a805bSEd Maste end 383060a805bSEd Maste return table.concat(sb, '') 384060a805bSEd Maste end 385060a805bSEd Maste 386060a805bSEd Maste -- returns a string describing duplicate file warnings, 387060a805bSEd Maste -- returns a string describing duplicate file errors 388060a805bSEd Maste --- @public 389060a805bSEd Maste local function dup_report() 390060a805bSEd Maste local warn, errs = {}, {} 391060a805bSEd Maste for filename, rows in sortedPairs(files) do 392060a805bSEd Maste if #rows == 1 then goto continue end 393060a805bSEd Maste local iseq, offby = metalogrows_all_equal(rows) 394060a805bSEd Maste if iseq then -- repeated line, just a warning 395060a805bSEd Maste warn[#warn+1] = 'warning: '..filename 396b751fc75SEd Maste .. ' ' .. rows[1].attrs.type 397060a805bSEd Maste ..' repeated with same meta: line ' 398060a805bSEd Maste ..table.concat( 399060a805bSEd Maste table_map(rows, function(e) return e.linenum end), ',') 400060a805bSEd Maste warn[#warn+1] = '\n' 401060a805bSEd Maste elseif not metalogrows_all_equal(rows, false, true) then 402060a805bSEd Maste -- same filename (possibly different tags), different metadata, an error 403060a805bSEd Maste errs[#errs+1] = 'error: '..filename 404060a805bSEd Maste ..' exists in multiple locations and with different meta: line ' 405060a805bSEd Maste ..table.concat( 406060a805bSEd Maste table_map(rows, function(e) return e.linenum end), ',') 407060a805bSEd Maste ..'. off by "'..offby..'"' 408060a805bSEd Maste errs[#errs+1] = '\n' 409060a805bSEd Maste end 410060a805bSEd Maste ::continue:: 411060a805bSEd Maste end 412060a805bSEd Maste return table.concat(warn, ''), table.concat(errs, '') 413060a805bSEd Maste end 414060a805bSEd Maste 415060a805bSEd Maste -- returns a string describing warnings of found hard links 416060a805bSEd Maste -- returns a string describing errors of found hard links 417060a805bSEd Maste --- @public 418060a805bSEd Maste local function inode_report() 419060a805bSEd Maste -- obtain inodes of filenames 420060a805bSEd Maste local attributes = require('lfs').attributes 421060a805bSEd Maste local inm = {} -- map<number, string[]> 422060a805bSEd Maste local unstatables = {} -- string[] 423060a805bSEd Maste for filename in pairs(files) do 424060a805bSEd Maste -- i only took the first row of a filename, 425060a805bSEd Maste -- and skip links and folders 426060a805bSEd Maste if files[filename][1].attrs.type ~= 'file' then 427060a805bSEd Maste goto continue 428060a805bSEd Maste end 429bca4d270SEd Maste local fs = attributes(stage_root .. filename) 430060a805bSEd Maste if fs == nil then 431060a805bSEd Maste unstatables[#unstatables+1] = filename 432060a805bSEd Maste goto continue 433060a805bSEd Maste end 434060a805bSEd Maste local inode = fs.ino 435060a805bSEd Maste inm[inode] = inm[inode] or {} 436bca4d270SEd Maste table.insert(inm[inode], filename) 437060a805bSEd Maste ::continue:: 438060a805bSEd Maste end 439060a805bSEd Maste 440060a805bSEd Maste local warn, errs = {}, {} 441060a805bSEd Maste for _, filenames in pairs(inm) do 442060a805bSEd Maste if #filenames == 1 then goto continue end 443060a805bSEd Maste -- i only took the first row of a filename 444060a805bSEd Maste local rows = table_map(filenames, function(e) 445060a805bSEd Maste return files[e][1] 446060a805bSEd Maste end) 447060a805bSEd Maste local iseq, offby = metalogrows_all_equal(rows, true, true) 448060a805bSEd Maste if not iseq then 449060a805bSEd Maste errs[#errs+1] = 'error: ' 450060a805bSEd Maste ..'entries point to the same inode but have different meta: ' 451060a805bSEd Maste ..table.concat(filenames, ',')..' in line ' 452060a805bSEd Maste ..table.concat( 453060a805bSEd Maste table_map(rows, function(e) return e.linenum end), ',') 454060a805bSEd Maste ..'. off by "'..offby..'"' 455060a805bSEd Maste errs[#errs+1] = '\n' 456060a805bSEd Maste end 457060a805bSEd Maste ::continue:: 458060a805bSEd Maste end 459060a805bSEd Maste 460060a805bSEd Maste if #unstatables > 0 then 461060a805bSEd Maste warn[#warn+1] = verbose and 462060a805bSEd Maste 'note: skipped checking inodes: '..table.concat(unstatables, ',')..'\n' 463060a805bSEd Maste or 464060a805bSEd Maste 'note: skipped checking inodes for '..#unstatables..' entries\n' 465060a805bSEd Maste end 466060a805bSEd Maste 467060a805bSEd Maste return table.concat(warn, ''), table.concat(errs, '') 468060a805bSEd Maste end 469060a805bSEd Maste 470bca4d270SEd Maste -- The METALOG file is assumed to be at the top of the stage directory. 471bca4d270SEd Maste stage_root = string.gsub(metalog, '/[^/]*$', '/') 472bca4d270SEd Maste 473060a805bSEd Maste do 474060a805bSEd Maste local fp, errmsg, errcode = io.open(metalog, 'r') 475060a805bSEd Maste if fp == nil then 476060a805bSEd Maste io.stderr:write('cannot open '..metalog..': '..errmsg..': '..errcode..'\n') 477060a805bSEd Maste os.exit(1) 478060a805bSEd Maste end 479060a805bSEd Maste 480060a805bSEd Maste -- scan all lines and put file data into the dictionaries 481060a805bSEd Maste local firsttimes = {} -- set<string> 482060a805bSEd Maste local lineno = 0 483060a805bSEd Maste for line in fp:lines() do 484060a805bSEd Maste -----local isinpkg = false 485060a805bSEd Maste lineno = lineno + 1 486eec4f5c0SGordon Bergling -- skip lines beginning with # 487060a805bSEd Maste if line:match('^%s*#') then goto continue end 488060a805bSEd Maste -- skip blank lines 489060a805bSEd Maste if line:match('^%s*$') then goto continue end 490060a805bSEd Maste 491060a805bSEd Maste local data = MetalogRow(line, lineno) 492060a805bSEd Maste -- entries with dir and no tags... ignore for the first time 493060a805bSEd Maste if not w_notagdirs and 494060a805bSEd Maste data.attrs.tags == nil and data.attrs.type == 'dir' 495060a805bSEd Maste and not firsttimes[data.filename] then 496060a805bSEd Maste firsttimes[data.filename] = true 497060a805bSEd Maste goto continue 498060a805bSEd Maste end 499060a805bSEd Maste 500060a805bSEd Maste files[data.filename] = files[data.filename] or {} 501060a805bSEd Maste table.insert(files[data.filename], data) 502060a805bSEd Maste 503060a805bSEd Maste if data.attrs.tags ~= nil then 504060a805bSEd Maste pkgname = pkgname_from_tag(data.attrs.tags) 505060a805bSEd Maste pkgs[pkgname] = pkgs[pkgname] or {} 506060a805bSEd Maste pkgs[pkgname][data.filename] = true 507060a805bSEd Maste ------isinpkg = true 508060a805bSEd Maste end 509060a805bSEd Maste -----if not isinpkg then nopkg[data.filename] = true end 510060a805bSEd Maste ::continue:: 511060a805bSEd Maste end 512060a805bSEd Maste 513060a805bSEd Maste fp:close() 514060a805bSEd Maste end 515060a805bSEd Maste 516060a805bSEd Maste return { 517060a805bSEd Maste warn = swarn, 518060a805bSEd Maste errs = serrs, 519060a805bSEd Maste pkg_issetuid = pkg_issetuid, 520060a805bSEd Maste pkg_issetgid = pkg_issetgid, 521060a805bSEd Maste pkg_issetid = pkg_issetid, 522060a805bSEd Maste pkg_report_full = pkg_report_full, 523060a805bSEd Maste pkg_report_simple = pkg_report_simple, 524060a805bSEd Maste dup_report = dup_report, 525060a805bSEd Maste inode_report = inode_report 526060a805bSEd Maste } 527060a805bSEd Masteend 528060a805bSEd Maste 529*f93d92f4SEd Masteos.exit(main(arg)) 530