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