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