1*1031c584SApple OSS Distributions#!/usr/local/bin/recon 2*1031c584SApple OSS Distributions 3*1031c584SApple OSS Distributionslocal argparse = require 'argparse' 4*1031c584SApple OSS Distributionslocal darwin = require 'darwin' 5*1031c584SApple OSS Distributionslocal plist = require 'plist' 6*1031c584SApple OSS Distributionslocal kcdata = require 'kcdata' 7*1031c584SApple OSS Distributionslocal lfs = require 'lfs' 8*1031c584SApple OSS Distributionslocal sysctl = require 'sysctl' 9*1031c584SApple OSS Distributions 10*1031c584SApple OSS Distributionsrequire 'strict' 11*1031c584SApple OSS Distributions 12*1031c584SApple OSS Distributionslocal parser = argparse(){ 13*1031c584SApple OSS Distributions name = 'kernpost_report', 14*1031c584SApple OSS Distributions} 15*1031c584SApple OSS Distributions 16*1031c584SApple OSS Distributionslocal FMT_RAW <const> = 'raw' 17*1031c584SApple OSS Distributionslocal FMT_PLIST <const> = 'plist' 18*1031c584SApple OSS Distributionslocal FMT_BUNDLE <const> = 'resultbundle' 19*1031c584SApple OSS Distributions 20*1031c584SApple OSS Distributionslocal export = parser:command('export') 21*1031c584SApple OSS Distributionsexport:option{ 22*1031c584SApple OSS Distributions name = '-f --format', 23*1031c584SApple OSS Distributions description = 'the format to export the tests in', 24*1031c584SApple OSS Distributions choices = { FMT_RAW, FMT_PLIST, FMT_BUNDLE, }, 25*1031c584SApple OSS Distributions count = '?', 26*1031c584SApple OSS Distributions} 27*1031c584SApple OSS Distributionsexport:option{ 28*1031c584SApple OSS Distributions name = '-o --output', 29*1031c584SApple OSS Distributions description = 'where to write the report, must be a directory', 30*1031c584SApple OSS Distributions count = 1, 31*1031c584SApple OSS Distributions} 32*1031c584SApple OSS Distributions 33*1031c584SApple OSS Distributionslocal function write_file(path, data) 34*1031c584SApple OSS Distributions local f, err = io.open(path, 'w') 35*1031c584SApple OSS Distributions if not f then 36*1031c584SApple OSS Distributions io.stderr:write(path, ': failed to open for writing: ', err, '\n') 37*1031c584SApple OSS Distributions os.exit(1) 38*1031c584SApple OSS Distributions end 39*1031c584SApple OSS Distributions f:write(data) 40*1031c584SApple OSS Distributions f:close() 41*1031c584SApple OSS Distributionsend 42*1031c584SApple OSS Distributions 43*1031c584SApple OSS Distributionslocal function default_test_info(config) 44*1031c584SApple OSS Distributions return { 45*1031c584SApple OSS Distributions version = 2, 46*1031c584SApple OSS Distributions test_category = 'unittest', 47*1031c584SApple OSS Distributions Project = 'xnu', 48*1031c584SApple OSS Distributions ['boot-args'] = assert(config.boot_args), 49*1031c584SApple OSS Distributions osVersion = assert(config.osversion), 50*1031c584SApple OSS Distributions mach_timebase_info = config.mach_timebase_info, 51*1031c584SApple OSS Distributions } 52*1031c584SApple OSS Distributionsend 53*1031c584SApple OSS Distributions 54*1031c584SApple OSS Distributionslocal function current_timezone_offset() 55*1031c584SApple OSS Distributions local now = os.time() 56*1031c584SApple OSS Distributions local timezone = os.difftime(now, os.time(os.date("!*t", now))) 57*1031c584SApple OSS Distributions local h, m = math.modf(timezone / 3600) 58*1031c584SApple OSS Distributions return ("%+.2d:%.2d"):format(h, 60 * m) 59*1031c584SApple OSS Distributionsend 60*1031c584SApple OSS Distributionslocal timezone_offset = current_timezone_offset() 61*1031c584SApple OSS Distributions 62*1031c584SApple OSS Distributionslocal function convert_time(raw_time, tb) 63*1031c584SApple OSS Distributions local time_secs = raw_time * tb.numer / tb.denom / 1e9 64*1031c584SApple OSS Distributions local boottime_secs = darwin.mach_boottime_usec() / 1e6 65*1031c584SApple OSS Distributions local walltime_secs = math.modf(boottime_secs + time_secs) 66*1031c584SApple OSS Distributions local time_str = os.date('%Y-%m-%dT%H:%M:%S', walltime_secs) 67*1031c584SApple OSS Distributions local time_ms_str = time_str .. '.' .. tostring( 68*1031c584SApple OSS Distributions math.floor(walltime_secs / 1e6)) 69*1031c584SApple OSS Distributions return time_ms_str .. timezone_offset 70*1031c584SApple OSS Distributionsend 71*1031c584SApple OSS Distributions 72*1031c584SApple OSS Distributionslocal function write_test_result(config, test_data, dir) 73*1031c584SApple OSS Distributions local info_tbl = default_test_info(config) 74*1031c584SApple OSS Distributions 75*1031c584SApple OSS Distributions local name = test_data.test_name 76*1031c584SApple OSS Distributions local test_dir = dir .. '/test_' .. name 77*1031c584SApple OSS Distributions lfs.mkdir(test_dir) 78*1031c584SApple OSS Distributions 79*1031c584SApple OSS Distributions info_tbl.test_id = name 80*1031c584SApple OSS Distributions 81*1031c584SApple OSS Distributions local test_pass = test_data.retval ~= nil and 82*1031c584SApple OSS Distributions test_data.retval == test_data.expected_retval 83*1031c584SApple OSS Distributions info_tbl.result_code = test_pass and 200 or 400 84*1031c584SApple OSS Distributions 85*1031c584SApple OSS Distributions local tb = config.mach_timebase_info 86*1031c584SApple OSS Distributions info_tbl.result_started = convert_time(test_data.begin_time, tb) 87*1031c584SApple OSS Distributions info_tbl.beginTimeRaw = test_data.begin_time 88*1031c584SApple OSS Distributions 89*1031c584SApple OSS Distributions info_tbl.result_finished = convert_time(test_data.end_time, tb) 90*1031c584SApple OSS Distributions info_tbl.endTimeRaw = test_data.end_time 91*1031c584SApple OSS Distributions 92*1031c584SApple OSS Distributions local info_path <const> = test_dir .. '/Info.plist' 93*1031c584SApple OSS Distributions local info_plist, err = plist.encode(info_tbl) 94*1031c584SApple OSS Distributions if not info_plist then 95*1031c584SApple OSS Distributions io.stderr:write('error: failed to serialize test Info.plist: ', err, '\n') 96*1031c584SApple OSS Distributions os.exit(1) 97*1031c584SApple OSS Distributions end 98*1031c584SApple OSS Distributions write_file(info_path, info_plist) 99*1031c584SApple OSS Distributions 100*1031c584SApple OSS Distributions lfs.mkdir(test_dir .. '/Attachments') 101*1031c584SApple OSS Distributions lfs.mkdir(test_dir .. '/Diagnostics') 102*1031c584SApple OSS Distributions 103*1031c584SApple OSS Distributions local status_path = test_dir .. '/' .. (test_pass and 'PASS' or 'FAIL') .. 104*1031c584SApple OSS Distributions '.status' 105*1031c584SApple OSS Distributions write_file(status_path, '') 106*1031c584SApple OSS Distributionsend 107*1031c584SApple OSS Distributions 108*1031c584SApple OSS Distributionslocal function write_result_bundle(data, dir) 109*1031c584SApple OSS Distributions lfs.mkdir(dir) 110*1031c584SApple OSS Distributions local config = data.xnupost_testconfig 111*1031c584SApple OSS Distributions for _, test in ipairs(config.xnupost_test_config) do 112*1031c584SApple OSS Distributions write_test_result(config, test, dir) 113*1031c584SApple OSS Distributions end 114*1031c584SApple OSS Distributionsend 115*1031c584SApple OSS Distributions 116*1031c584SApple OSS Distributionsexport:action(function (args) 117*1031c584SApple OSS Distributions local dir = args.output 118*1031c584SApple OSS Distributions if lfs.attributes(dir, 'mode') ~= 'directory' then 119*1031c584SApple OSS Distributions io.stderr:write(dir, ': output path must be a directory\n') 120*1031c584SApple OSS Distributions os.exit(1) 121*1031c584SApple OSS Distributions end 122*1031c584SApple OSS Distributions 123*1031c584SApple OSS Distributions local raw_data, err = sysctl('debug.xnupost_get_tests') 124*1031c584SApple OSS Distributions if not raw_data then 125*1031c584SApple OSS Distributions io.stderr:write('error: failed to retrieve test data from kernel: ', err, 126*1031c584SApple OSS Distributions '\n') 127*1031c584SApple OSS Distributions os.exit(1) 128*1031c584SApple OSS Distributions end 129*1031c584SApple OSS Distributions 130*1031c584SApple OSS Distributions if args.format == FMT_RAW then 131*1031c584SApple OSS Distributions write_file(dir .. '/xnupost.kcdata', raw_data) 132*1031c584SApple OSS Distributions elseif args.format == FMT_PLIST then 133*1031c584SApple OSS Distributions local tbl_data 134*1031c584SApple OSS Distributions tbl_data, err = kcdata.decode(raw_data) 135*1031c584SApple OSS Distributions if not tbl_data then 136*1031c584SApple OSS Distributions io.stderr:write('error: failed to deserialize kernel data: ', err, '\n') 137*1031c584SApple OSS Distributions os.exit(1) 138*1031c584SApple OSS Distributions end 139*1031c584SApple OSS Distributions local data 140*1031c584SApple OSS Distributions data, err = plist.encode(tbl_data, 'xml') 141*1031c584SApple OSS Distributions if not data then 142*1031c584SApple OSS Distributions io.stderr:write('error: failed to serialize kernel data to plist: ', err, 143*1031c584SApple OSS Distributions '\n') 144*1031c584SApple OSS Distributions os.exit(1) 145*1031c584SApple OSS Distributions end 146*1031c584SApple OSS Distributions write_file(dir .. '/xnupost.plist', data) 147*1031c584SApple OSS Distributions elseif args.format == FMT_BUNDLE then 148*1031c584SApple OSS Distributions local tbl_data 149*1031c584SApple OSS Distributions tbl_data, err = kcdata.decode(raw_data) 150*1031c584SApple OSS Distributions if not tbl_data then 151*1031c584SApple OSS Distributions io.stderr:write('error: failed to deserialize kernel data: ', err, '\n') 152*1031c584SApple OSS Distributions os.exit(1) 153*1031c584SApple OSS Distributions end 154*1031c584SApple OSS Distributions write_result_bundle(tbl_data, dir .. '/xnupost') 155*1031c584SApple OSS Distributions end 156*1031c584SApple OSS Distributionsend) 157*1031c584SApple OSS Distributions 158*1031c584SApple OSS Distributionsparser:parse(arg) 159