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