Merged PR 781723: Remove macOS sandbox (managed) code and logic

Remove macOS sandbox / kernel extension code and examples. Trying to only remove dead code here, some refactoring is still possible to improve code structure but I will leave it for future PRs.
This commit is contained in:
Marcelo Lynch 🧉 2024-05-10 21:44:15 +00:00
Родитель 4c5cc753e9
Коммит 58b367a6e8
83 изменённых файлов: 946 добавлений и 5553 удалений

Просмотреть файл

@ -112,11 +112,6 @@ This page lists flags that can be used to configure BuildXL.
| InjectCacheMisses | Sets a rate for artificial cache misses (pips may be re-run with this likelihood, when otherwise not necessary). Miss rate and options are specified as "[~]Rate[#Seed]". The '~' symbol negates the rate (it becomes an allowed hit rate). The 'Rate' must be a numeric value in the range [0.0, 1.0]. The optional 'Seed' is an integer value fully determining the random aspect of the miss rate (the same seed and miss rate will always pick the same set of pips). |
| InputChanges | Path to file containing a list of input changes, separated by new lines. The formats of an input change is '<path>|<change kind>' or '<path>', where change kinds are 'DataOrMetadataChanged', 'Removed', 'MembershipChanged', 'NewlyPresentAsFile', 'NewlyPresentAsDirectory'. |
| Interactive | When enabled indicates that {ShortProductName} is allowed to interact with the user either via console or popups. A common use case is to allow front ends like nuget to display authentication prompts in case the user is not authenticated. |
| KextNumberOfKextConnections | [macOS] Specifies the number of connections that drain the sandbox kernel extension file access reports on macOS systems - the sandbox kernel extension allocates one report queue per connection to balance the system load when reporting file accesses. |
| KextReportQueueSizeMb | [macOS] Specifies the size in MB of a sandbox kernel extension report queue. Use this cautiously, because it allocates the specified size in wired system memory multiplied by the number of kernel extension connections. |
| KextThrottleCpuUsageBlockThresholdPercent | [macOS] Causes throttling to be triggered whenever CPU usage is above this value. |
| KextThrottleCpuUsageWakeupThresholdPercent | [macOS] A blocked process can be awakened only when CPU usage drops below this value (defaults to /KextThrottleCpuUsageBlockThresholdPercent). |
| KextThrottleMinAvailableRamMB | [macOS] Causes throttling to be triggered whenever available RAM drops below this value. (takes precedence over CPU usage thresholds) |
| LaunchDebugger | Launches the debugger during boot (in the server process if applicable). |
| LimitPathSetsOnCacheLookup | Limits the number of path sets to be checked during cache lookup. Once the limit is reached, the pip is determined to have a cache miss. Defaults to off. The number of path sets can also be set. Defaults to 0 (off). |
| LoadGraph | Loads a cached build graph stored under the given fingerprint (40 digit hex string, no delimiters), path to cached graph directory, or canonical name. |
@ -152,7 +147,6 @@ This page lists flags that can be used to configure BuildXL.
| MaxIOMultiplier | Specifies maxIO in terms of a multiplier of the machine's processor count. Defaults to 0.25. |
| MaxLightProc | Specifies the maximum number of "light" processes that {ShortProductName} will launch at one time. Build specs can mark certain processes as "light" to indicate that they won't use much CPU time. Defaults to 0. |
| MaxMaterialize | Specifies the maximum number of materialize operations that {ShortProductName} will launch at one time. Defaults to twice the number of processors in the current machine. |
| MaxMemoryPressureLevel | [macOS] Causes scheduling to pause / cancel pips to free up system resources if specified maximum pressure level is overstepped. Allowed values are 'Normal' (Default), 'Warning' and 'Critical'. NOTE: Using 'Critical' can lead to system instablities and eventual kernel panics due to resource starvation! |
| MaxNumPipTelemetryBatches | Specifies the maximum number of batched messages to be sent to telemetry, default set to 10 messages. |
| MaxProc | Specifies the maximum number of processes that {ShortProductName} will launch at one time. Defaults to 25% more than the total number of processors in the current machine. |
| MaxProcMultiplier | Specifies maxProc in terms of a multiplier of the machine's processor count. Defaults to 1.25. |

Просмотреть файл

@ -38,52 +38,4 @@ This plugin enables C# and C++ target language support for building using BuildX
### Acquire the plugin
You can build it locally with this command: `bxl out\bin\debug\ide\*`. And find the resulting file will be dropped at: `out\bin\debug\ide\BuildXL.vs.vsix`. You can install it by simply running the vsix.
If you see a message like this: `The application which this project type is based on was not found. Please try this link for further information: http://go.microsoft.com/fwlink/?LinkID=299083&projecttype=DABA23A1-650F-4EAB-AC72-A2AF90E10E37` then you need to build and install the plugin. This is the plugin's GUID which is referenced from the generated proj files from `bxl -vs`.
## macOS Sandbox Kernel Extension
### Locating the kernel extension binaries
1. If you are using an APEX office enlistment, this will be a location like: `[enlistmentRoot]/build/NugetCache/BuildXL.osx-x64.*`
1. Within the BuildXL nuget package, locate the kernel extension. It is under `[BuildXLPackageRoot]/native/MacOS/BuildXLSandbox.kext`
### Loading the kernel extension
#### Option 1: Perform all steps manually
1. Create a new destination directory on your Mac and copy `BuildXLSandbox.kext/` (which is a directory) underneath the newly created destination. This destination directory can be underneath home (a.k.a. "~"), but it **cannot** be the home directory itself due to permissions issues. For example:
`mkdir -p /tmp`
`cp -r "$BuildXLPackageRoot/native/MacOS/BuildXLSandbox.kext" /tmp/`
1. Change ownership and apply permissions for the newly created directory:
`sudo chown -R root:wheel /tmp/BuildXLSandbox.kext`
`sudo chmod -R 555 /tmp/BuildXLSandbox.kext`
1. Start the KEXT.
`sudo kextutil /tmp/BuildXLSandbox.kext`
1. Verify that the KEXT is running. If you run
`kextstat | grep buildxl`
the output should look something like
` 132 0 0xffffff7f80cea000 0x68000 0x68000 com.microsoft.buildxl.sandbox (1.57.99) C1BBFFEF-A3AA-3457-A159-340CC5BA84E2 <5 4 3 2 1>`
#### Option 2: Run the `sandbox-load.sh` script
The `sandbox-load.sh` script (found next to the `bxl` executable) automates the steps outlined above in a bit more robust way.
```
sudo ./sandbox-load.sh --deploy-dir /tmp --kext native/MacOS/BuildXLSandbox.kext
```
This command copies the supplied folder to `/tmp/BuildXLSandbox.kext`, applies the required permissions to it and loads the extension from there. If `/tmp/BuildXLSandbox.kext` already exists, it deletes it. If the kernel extension is already running, it unloads the running extension before attempting to load the new one.
For more information about the `sandbox-load.sh` script execute `./sandbox-load.sh --help` or see its [man page](/BuildXL/Reference-Guide/Man-Pages/sandbox%2Dload.sh.1).
#### Option 3: Pass the `--load-kext` switch to `bxl.sh`
If `--load-kext` is passed to the `bxl.sh` script (found next to the `bxl` executable), before running BuildXL the script will load the kernel extension that is pre-packaged with that BuildXL deployment.
```
./bxl.sh --load-kext [other-bxl-args-for-running-buildxl]
```
This script simply invokes the previously described `sandbox-load.sh` script using `/tmp` as the deployment destination directory.
For more information about the `bxl.sh` script execute `./bxl.sh --help` or see its [man page](/BuildXL/Reference-Guide/Man-Pages/bxl.sh.1).
### *Important notes*
- **Note 1:** If this is the first time you've attempted to load the BuildXL KEXT, you'll need to allow it in System Preferences -> Security & Privacy. You should see a notification about loading software from Microsoft. <br>
- **Note 2:** The allow button can only be hit by a mouse directly connected to the Mac. Remoting in will not work.
- **Note 3:** You may need to restart the KEXT each time you restart your machine
For further information check Apple TechNotes:
- https://developer.apple.com/library/archive/technotes/tn2459/_index.html
- https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/KEXTConcept/KEXTConceptKEXT/kext_tutorial.html#//apple_ref/doc/uid/20002365-BABJHCJA
If you see a message like this: `The application which this project type is based on was not found. Please try this link for further information: http://go.microsoft.com/fwlink/?LinkID=299083&projecttype=DABA23A1-650F-4EAB-AC72-A2AF90E10E37` then you need to build and install the plugin. This is the plugin's GUID which is referenced from the generated proj files from `bxl -vs`.

Просмотреть файл

@ -1,328 +0,0 @@
#!/bin/bash
set -o nounset
set -o errexit
readonly MY_DIR=$(cd `dirname ${BASH_SOURCE[0]}` && pwd)
source ${MY_DIR}/env.sh
declare arg_Unconsumed=()
declare arg_BuildScript=""
declare arg_CacheRoot=""
declare arg_CacheName=""
declare arg_CacheScenario=""
declare arg_RemoteTelemetry=""
declare arg_BuildxlBin=""
function parseArgs {
while [[ $# -gt 0 ]]; do
cmd="$1"
case $cmd in
--build-script)
arg_BuildScript="$2"
shift
shift
;;
--cache-root)
arg_CacheRoot="$2"
shift
shift
;;
--cache-name)
arg_CacheName="$2"
shift
shift
;;
--cache-scenario)
arg_CacheScenario="$2"
shift
shift
;;
--remote-telemetry)
arg_RemoteTelemetry="1"
shift
;;
--buildxl-bin)
arg_BuildxlBin="$2"
shift
shift
;;
*)
arg_Unconsumed+=("$1")
shift
;;
esac
done
}
function validateAndInit {
# check --buildxl-bin
if [[ ! -d "$arg_BuildxlBin" ]]; then
print_error "--buildxl-bin not a directory: '${arg_BuildxlBin}'"
return 1
fi
if [[ ! -f "${arg_BuildxlBin}/ContentStoreApp" ]]; then
print_error "$arg_BuildxlBin/ContentStoreApp not found"
return 1
fi
# check --cache-root
arg_CacheRoot="${arg_CacheRoot:-${MY_DIR}/out/casaas}"
if [[ ! -d "${arg_CacheRoot}" ]]; then
mkdir -p "${arg_CacheRoot}"
fi
# check --build-script
arg_BuildScript="${arg_BuildScript:-${MY_DIR}/build.sh}"
if [[ ! -f "${arg_BuildScript}" ]]; then
print_error "--build-script is not a file: '${arg_BuildScript}'"
return 1
fi
# set readonly vars
readonly BUILDXL_BIN="$(cd "${arg_BuildxlBin}" && pwd)"
readonly CACHE_ROOT="$(cd "${arg_CacheRoot}" && pwd)"
readonly CACHE_STDOUT="${CACHE_ROOT}/casaas.stdout"
readonly CACHE_NAME="${arg_CacheName:-bxl-selfhost-example-casaas}"
readonly CACHE_SCENARIO="${arg_CacheScenario:-scenario-$CACHE_NAME}"
readonly REMOTE_TELEMETRY="${arg_RemoteTelemetry:-}"
readonly BUILD_SCRIPT="${arg_BuildScript:-${MY_DIR}/build.sh}"
}
function printRunningCasaasPid {
ps -ef | grep ContentStoreApp | grep -v grep | awk '{print $2}'
}
function killRunningContentStoreApp {
local runningCasaasPid=$(printRunningCasaasPid)
if [[ -n $runningCasaasPid ]]; then
print_info "ContentStoreApp process running: ${runningCasaasPid}. Sending TERM..."
kill -s TERM $runningCasaasPid
sleep 1
local checkAgainPid=$(printRunningCasaasPid)
if [[ -n $checkAgainPid ]]; then
print_error "Cound not kill ContentStoreApp process"
return 1
fi
fi
}
function createContentStoreAppSettingsFile {
local file=$(mktemp -t casaas-settings-json)
cat > $file <<EOF
{
"IsDistributedContentEnabled": "true",
"ConnectionSecretNamesMap": {
".*": {
"RedisContentSecretName": "CloudStoreRedisConnectionString",
"RedisMachineLocationsSecretName": "CloudStoreRedisConnectionString"
}
},
"KeySpacePrefix": "PD",
"ContentHashBumpTimeMinutes": 1500,
"IsBandwidthCheckEnabled": true,
"IsDistributedEvictionEnabled": true,
"IsPinBetterEnabled": true,
"PinRisk": 1.0E-8,
"FileRisk": 0.02,
"MachineRisk": 0.08,
"IsPinCachingEnabled": true,
"IsTouchEnabled": true,
"IsRepairHandlingEnabled": true,
"UseLegacyQuotaKeeperImplementation ": false,
"PinRisk ": null,
"FileRisk ": null,
"MachineRisk ": null,
"IsPinCachingEnabled ": false,
"IsTouchEnabled ": false,
"IsContentLocationDatabaseEnabled ": false,
"GlobalRedisSecretName ": "cbcache-test-redis-{StampId:LA}",
"SecondaryGlobalRedisSecretName ": "cbcache-test-redis-secondary-{StampId:LA}",
"EventHubSecretName ": "cbcache-test-eventhub-{StampId:LA}",
"AzureStorageSecretName ": "cbcacheteststorage{StampId:LA}",
"MaxEventProcessingConcurrency ": 64,
"UseIncrementalCheckpointing ": true,
"UseDistributedCentralStorage ": true,
"LocationEntryExpiryMinutes ": 120,
"IsReconciliationEnabled ": true,
"ContentLocationReadMode ": "LocalLocationStore",
"ContentLocationWriteMode ": "Both",
"UseMdmCounters ": true,
"UseTrustedHash ": true,
"TrustedHashFileSizeBoundary ": 100000,
"IsMachineReputationEnabled ": true,
"ParallelHashingFileSizeBoundary ": 52428801,
"EmptyFileHashShortcutEnabled ": true,
"BlobExpiryTimeMinutes ": 30,
"MaxBlobCapacity ": 3221225472,
"IsGrpcCopierEnabled ": true
}
EOF
echo $file
}
function createCacheConfigJson {
local cacheConfigFile=$(mktemp -t cache-config-json)
cat > $cacheConfigFile <<EOF
{
"LocalCache": {
"CacheId": "SelfhostCS2L1",
"Assembly": "BuildXL.Cache.MemoizationStoreAdapter",
"CacheLogPath": "[BuildXLSelectedLogPath]",
"Type": "BuildXL.Cache.MemoizationStoreAdapter.MemoizationStoreCacheFactory",
"CacheRootPath": "${CACHE_ROOT}/cache",
"UseStreamCAS": false,
"EnableContentServer": true,
"EmptyFileHashShortcutEnabled": false,
"CheckLocalFiles": false,
"CacheName": "${CACHE_NAME}",
"GrpcPort": 7089,
"Scenario": "${CACHE_SCENARIO}"
},
"RemoteCache": {
"Type": "BuildXL.Cache.BuildCacheAdapter.DistributedBuildCacheFactory",
"Assembly": "BuildXL.Cache.BuildCacheAdapter",
"CacheId": "RemoteCache",
"CacheLogPath": "[DominoSelectedLogPath].L3.log",
"CacheServiceFingerprintEndpoint": "https://artifactsu0.artifacts.visualstudio.com/DefaultCollection",
"CacheServiceContentEndpoint": "https://artifactsu0.vsblob.visualstudio.com/DefaultCollection",
"UseBlobContentHashLists": true,
"CacheKeyBumpTimeMins": 120,
"CacheNamespace": "${CACHE_NAME}",
"CacheName": "${CACHE_NAME}",
"ConnectionRetryCount": 5,
"ConnectionRetryIntervalSeconds": 5,
"GrpcPort": 7089,
"SealUnbackedContentHashLists": false,
"ConnectionsPerSession": 46,
"DisableContent": true
},
"Type": "BuildXL.Cache.VerticalAggregator.VerticalCacheAggregatorFactory",
"Assembly": "BuildXL.Cache.VerticalAggregator",
"RemoteContentIsReadOnly": true
}
EOF
echo $cacheConfigFile
}
function startContentStoreApp { # (vstsPAT, redisCS)
local vstsPAT="$1"
local redisCS="$2"
local kustoCS=""
local settingsFile=$(createContentStoreAppSettingsFile)
print_info "Generated ContentStoreApp settings file: ${settingsFile}"
local casaasArgs=(
DistributedService
/dataRootPath:${CACHE_ROOT}/dataRootPath
/grpcPort:7089
/cacheName:${CACHE_NAME}
/cachePath:${CACHE_ROOT}/cache
/ringId:ring-${CACHE_NAME}
/stampId:stamp-${CACHE_NAME}
/scenario:${CACHE_SCENARIO}
/useDistributedGrpc:true
/settingsPath:${settingsFile}
/logAutoFlush
/logdirectorypath:${CACHE_ROOT}/logs)
if [[ -n ${REMOTE_TELEMETRY:-} ]]; then
casaasArgs+=(/remoteTelemetry)
kustoCS=$(retrieveSecret KustoConnectionString) || return 1
fi
pushd "${CACHE_ROOT}" > /dev/null
env \
KustoConnectionString="${kustoCS:-}" \
VSTSPERSONALACCESSTOKEN="${vstsPAT}" \
CloudStoreRedisConnectionString="${redisCS}" \
${BUILDXL_BIN}/ContentStoreApp "${casaasArgs[@]}" 2>&1 > "${CACHE_STDOUT}" &
popd > /dev/null
}
function runBuildXL { # (redisCS, vstsPAT)
local redisCS="$1"; shift
local cacheConfigFile=$(createCacheConfigJson)
print_info "Generated BuildXL cache config file: ${cacheConfigFile}"
pushd "${CACHE_ROOT}" > /dev/null
env \
VSTSPERSONALACCESSTOKEN="${vstsPAT}" \
CloudStoreRedisConnectionString="${redisCS}" \
${BUILD_SCRIPT} --buildxl-bin "${BUILDXL_BIN}" --cache-config-file "${cacheConfigFile}" ${arg_Unconsumed[@]:-}
popd > /dev/null
}
function checkEnvVarExists { # (varName)
local varName="$1"
print_info "Checking env var '${varName}'"
if [[ -z ${!varName:-} ]]; then
print_error "Env var '${varName}' not defined"
return 1
fi
}
function retrieveSecret {
local secretName="$1"
if [[ -n ${!secretName:-} ]]; then
print_info "Secret '${secretName}' found in the environment" >&2
echo ${!secretName}
else
print_info "Attempting to fetch secret '${secretName}' as a password in 'login' keychain for account '${USER}'" >&2
security find-generic-password -a "${USER}" -s "${secretName}" -w && (
print_info "Secret '${secretName}' successfully retrieved from keychain" >&2
) || (
print_error "Failed to fetch '${secretName}' from login keychain" >&2
return 1
)
fi
}
parseArgs "$@"
validateAndInit
# retrieve mandatory secrets
vstsPAT=$(retrieveSecret VSTSPERSONALACCESSTOKEN) || exit 1
redisCS=$(retrieveSecret CloudStoreRedisConnectionString) || exit 1
# kill any currently running ContentStoreApp process
killRunningContentStoreApp
# start ContentStoreApp and remember its PID
startContentStoreApp "$vstsPAT" "$redisCS"
# verify that ContentStoreApp process is running and that its PID matches what we have
sleep 2
readonly casaasPid=$(printRunningCasaasPid)
if [[ -n $casaasPid ]]; then
print_info "ContentStoreApp(${casaasPid}) process started successfully:"
head "${CACHE_STDOUT}"
else
print_error "Could not start ContentStoreApp"
head "${CACHE_STDOUT}"
exit 1
fi
# run the build
runBuildXL "$redisCS" "$vstsPAT" || print_error "Build failed."
# in any case, send SIGTERM to ContentStoreApp
print_info "Shutting down ContentStoreApp..."
kill -s TERM $casaasPid
# wait for a while until ContentStoreApp exits
for i in `seq 1 20`; do
if [[ -z $(printRunningCasaasPid) ]]; then
print_info "Successfully shut down ContentStoreApp. ContentStoreApp stdout: '${CACHE_STDOUT}'"
exit 0
else
print_warning "Still running, sleeping for 1 second..."
sleep 1
fi
done
print_error "Failed to shut down ContentStoreApp"
exit 1

Просмотреть файл

@ -1,15 +0,0 @@
#!/bin/bash
readonly MY_DIR=$(cd `dirname ${BASH_SOURCE[0]}` && pwd)
source "${MY_DIR}/env.sh"
/bin/bash "${MacOsScriptsDir}/bxl.sh" \
--config "$MY_DIR/config.dsc" \
--symlink-sdks-into "$MY_DIR/sdk" \
--buildxl-bin "$BUILDXL_BIN" \
/useHardLinks+ \
/sandboxKind:macOsKext \
/disableProcessRetryOnResourceExhaustion+ \
/exp:LazySODeletion+ \
"$@"

Просмотреть файл

@ -1,5 +0,0 @@
#!/bin/bash
export MacOsScriptsDir="$(cd `dirname ${BASH_SOURCE[0]}` && pwd)/../../Public/Src/Sandbox/MacOs/scripts"
source "${MacOsScriptsDir}/env.sh"

Просмотреть файл

@ -1,60 +0,0 @@
#!/bin/bash
readonly MY_DIR=$(cd `dirname ${BASH_SOURCE[0]}` && pwd)
readonly HELLO_WORLD_MSG="HelloWorldFromBuildXLOnMac"
readonly bxlOutDir="$MY_DIR/out"
readonly bxlObjDir="$bxlOutDir/objects"
readonly bxlLogDir="$bxlOutDir/logs"
readonly bxlLogFile="$bxlLogDir/BuildXL.log"
source "$MY_DIR/env.sh"
# check if SIP is disabled
readonly sipStatus="$(csrutil status)"
print_info "$sipStatus"
if [[ -z $(echo "$sipStatus" | grep -o "disabled") ]]; then
print_warning "SIP is not disabled"
fi
# check Kext is running
declare additionalBuildArgs=""
print_info "Checking if Bxl Kernel Extension (Kext) is loaded"
readonly bxlKext=$(kextstat | grep com.microsoft.buildxl)
if [[ -z $bxlKext ]]; then
print_warning "Kext is not running, adding '--load-kext' build arguments"
additionalBuildArgs="--load-kext"
fi
print_info $(ln -sfv src-file.txt "$MY_DIR/test/TestSymlink/symlink-to-src-file.txt")
# run Bxl
chmod +x "$MY_DIR/build.sh"
"$MY_DIR/build.sh" \
/logsDirectory:"$bxlLogDir" \
/o:"$bxlObjDir" \
/p:"HELLO_WORLD_MSG=$HELLO_WORLD_MSG" \
/nowarn:0802 $bxlExtraArgs \
/sandboxKind:macOsKext \
/kextThrottleCpuUsageBlockThresholdPercent:55 \
/kextThrottleCpuUsageWakeupThresholdPercent:50 \
/kextThrottleMinAvailableRamMB:1024 \
${additionalBuildArgs} \
"$@"
bxlExitCode=$?
# check Bxl succeeded
if [[ $bxlExitCode != 0 ]]; then
exit $bxlExitCode
fi
# check that Bxl.log file exists
print_info "Checking existence of Bxl log file"
echo " found: $bxlLogFile"
if [[ ! -f $bxlLogFile ]]; then
print_error "Bxl log file ($bxlLogFile) not found"
exit 1
fi
echo "${tputBold}${tputGreen}Build Validation Succeeded${tputReset}"

Просмотреть файл

@ -1,162 +0,0 @@
#!/bin/bash
readonly MY_DIR=$(cd `dirname ${BASH_SOURCE[0]}` && pwd)
readonly ARGS="$@"
function run_build {
/bin/bash $MY_DIR/validate-build-kext.sh /logsDirectory:"$bxlLogDir" /o:"$bxlObjDir" $ARGS "$@"
}
function check_graph_reloaded {
grep -q "Reloading pip graph from previous build." $bxlLogFile
}
function check_fully_cached {
grep -q "Process pip cache hits: 100%" $bxlLogFile
}
readonly GRAPH_RELOADED=0
readonly GRAPH_NOT_RELOADED=1
readonly FULLY_CACHED=0
readonly NOT_FULLY_CACHED=1
# this is the magic timestamp ("2003-03-03 3:03:03") translated to UTC Epoch seconds
# readonly magicTimestamp=$(date -j -u -f "%Y-%m-%d %H:%M:%S" "2003-03-03 3:03:03" +%s)
readonly magicXattrName="com.microsoft.buildxl:shared_opaque_output"
function run_build_and_check_stuff {
local expectGraphReloadedStatus=$1
shift
local expectFullyCachedStatus=$1
shift
local rest="$@"
# run Bxl build
run_build $rest
local bxlExit=$?
if [[ $bxlExit != 0 ]]; then
print_error "Bxl failed with code $bxlExit"
return 1
fi
# check graph reloaded
check_graph_reloaded
local status=$?
if [[ $status != $expectGraphReloadedStatus ]]; then
print_error "Graph validation failed :: status = $status, expected = $expectGraphReloadedStatus ($GRAPH_RELOADED = graph reloaded, $GRAPH_NOT_RELOADED = graph not reloaded)"
return 2
else
print_info $(if [[ $expectGraphReloadedStatus == $GRAPH_RELOADED ]]; then echo "Verified graph reloaded"; else echo "Verified graph NOT reloaded"; fi)
fi
# check fully cached
check_fully_cached
status=$?
if [[ $status != $expectFullyCachedStatus ]]; then
print_error "Cache validation failed :: status = $status, expected = $expectFullyCachedStatus ($FULLY_CACHED = fully cached, $NOT_FULLY_CACHED = not fully cached)"
return 3
else
print_info $(if [[ $expectFullyCachedStatus == $FULLY_CACHED ]]; then echo "Verified build fully cached"; else echo "Verified build NOT fully cached"; fi)
fi
# check that all files in all shared opaque directories (whose name is 'sod*') have the magic xattr
local sodFilesFile="$MY_DIR/sod-files.txt"
find "$MY_DIR/out/objects" -type d -name 'sod*' -exec find {} -type f -o -type l \; > "$sodFilesFile"
for f in `cat $sodFilesFile`; do
xattr -s "$f" | grep -q $magicXattrName || {
print_error "File '$f' does not have the magic xattr '$magicXattrName'"
return 4
}
done
rm -f "$sodFilesFile"
}
function print_header {
echo " ==============================================================================="
echo " === $@"
echo " ==============================================================================="
}
# ======================= script entry point
source "$MY_DIR/env.sh"
readonly bxlOutDir="$MY_DIR/out"
readonly bxlObjDir="$bxlOutDir/objects.noindex"
readonly bxlLogDir="$bxlOutDir/logs"
readonly bxlLogFile="$bxlLogDir/BuildXL.log"
readonly unusedFilePath=$(find $MY_DIR/test -name "unused-file.txt")
if [[ ! -f $unusedFilePath ]]; then
print_error "Cannot find 'unused-file.txt'; goal: modify it, run a second build, and assert the second build still gets 100% cache hit rate"
exit 1
fi
# start with clean Out dir
ls -1 "$bxlOutDir" | grep -v -i casaas | xargs rm -rf
# 1st run
print_header "1st run: clean build"
run_build_and_check_stuff $GRAPH_NOT_RELOADED $NOT_FULLY_CACHED
if [[ $? != 0 ]]; then
print_error "1st build failed"
exit 1
fi
# 2nd run: clean Obj dir, modify "unused file", and run build again
echo
print_info "Deleting $bxlObjDir"
rm -rf "$bxlObjDir"*
print_info "$(mv -v $unusedFilePath $unusedFilePath.bak)"
print_header "2nd run: fully cached but no graph reloaded (because content of a globbed folder changed)."
run_build_and_check_stuff $GRAPH_NOT_RELOADED $FULLY_CACHED
declare status=$?
# revert "unused" file
print_info "$(mv -v $unusedFilePath.bak $unusedFilePath)"
# check 2nd build succeeded
if [[ $status != 0 ]]; then
print_error "2nd build failed"
exit 2
fi
# 3nd run
echo
print_header "3rd run: fully cached and graph reloaded (because nothing changed since last run)."
run_build_and_check_stuff $GRAPH_RELOADED $FULLY_CACHED
if [[ $? != 0 ]]; then
print_error "3rd build failed"
exit 2
fi
echo "${tputBold}${tputGreen}Build Validation Succeeded${tputReset}"
# 4th run: check that eager scrubbing deletes symlinks within shared opaque directories
echo
print_header "4th run: and check that symlinks are eagerly deleted when running with /exp:LazySODeletion- /phase:Schedule"
run_build /phase:Schedule /exp:LazySODeletion-
if [[ $? != 0 ]]; then
print_error "4th build failed"
exit 2
fi
declare producedSymlinkFileName="module.config.dsc"
declare scrubbedSymlinkFile=$(grep -o "Scrubber deletes file '.*/$producedSymlinkFileName'" $bxlLogFile | grep -o "'.*'")
if [[ -z $scrubbedSymlinkFile ]]; then
print_error "Expected produced symlink to $producedSymlinkFileName to have been deleted"
exit 1
fi
print_info "Symlink was scrubbed: $scrubbedSymlinkFile"
if [[ -f $scrubbedSymlinkFile ]]; then
print_error "File '$scrubbedSymlinkFile' exists on disk"
exit 1
fi
declare foundSymlinkFiles=$(find $bxlOutDir -type f -name $producedSymlinkFileName)
if [[ -n $foundSymlinkFiles ]]; then
print_error "File '$producedSymlinkFileName' found in object folder:"
echo $foundSymlinkFiles
exit 1
fi

2
Examples/SandboxedBuildMacOS/.gitignore поставляемый
Просмотреть файл

@ -1,2 +0,0 @@
*~
Out/

Просмотреть файл

@ -1,62 +0,0 @@
# Introduction
A simple "HelloWorld"-type clang C++ build for validating the sandbox functionality with BuildXL on the Mac. Note: The validaton will fail if executed with sandboxing disabled.
# Getting Started
* Disable SIP on your mac
* Set `BUILDXL_BIN` environment variable to point to a BuildXL .NET Core deployment (binaries), e.g.,
```bash
unzip BuildXL.osx-x64.0.1.-20190101.1.nupkg -d buildxl_bin
export BUILDXL_BIN=$(cd buildxl_bin && pwd)
```
* Depending on the sandbox kind you want to validate, either load the sandbox kernel extension or start the sandbox daemon process before running. Pass `--load-kext` to load the kernel extension or `--install-daemon` to install a launchd daemon when calling the validation script.
* Simply run `./validate-build.sh /sandboxKind:SANDBOX_TYPE --REQ` to run the example with some validation, e.g., `./validate-build.sh /sandboxKind:MacOsDetours --install-daemon`
```bash
Supported sandbox kinds and requirements:
SANDBOX_TYPE REQ
MacOsKext --load-kext
MacOsEndpointSecurity --install-daemon
MacOsDetours --install-daemon
```
* The build logs all observed file access from the ES sandbox into the main BuildXL log file (check `./ESSandboxBuildMacOS/Out/BuildXLCurrentLog/BuildXL.log`), this can be used to verify correctness of the sandbox and expected observed file accesses.
* If everything goes well, the output should look something like:
```
===============================================================================
=== 1st run: clean build
===============================================================================
[info] System Integrity Protection status: unknown (Custom Configuration).
Configuration:
Apple Internal: disabled
Kext Signing: disabled
Filesystem Protections: disabled
Debugging Restrictions: disabled
DTrace Restrictions: disabled
NVRAM Protections: disabled
BaseSystem Verification: enabled
This is an unsupported configuration, likely to break in the future and leave your machine in an unknown state.
[info] Checking BuildXL bin folder
[info] BUILDXL_BIN set to /Users/userName/work/buildxl_bin
[info] Installing sandbox daemon from: '/Users/userName/work/buildxl_bin/native/MacOS/BuildXLSandboxDaemon'
29588 0 com.microsoft.buildxl.sandbox
[info] Symlinking sdk folder from BuildXL deployment: .../Examples/DotNetCoreBuild/sdk/Sdk.Transformers -> $BUILDXL_BIN/Sdk/Sdk.Transformers
[info] Running bxl: '/Users/userName/work/buildxl_bin/bxl' /enableIncrementalFrontEnd- /useHardLinks- /p:BUILDXL_BIN=/Users/userName/work/buildxl_bin /p:DOTNET_EXE=/usr/local/bin/dotnet /p:MONO_EXE=/usr/local/bin/mono /useHardLinks+ /sandboxKind:macOSEndpointSecurity /disableProcessRetryOnResourceExhaustion+ /exp:LazySODeletion+ /logObservedFileAccesses+
Microsoft (R) Build Accelerator. Build: 0.1.0-20211211.0, Version: [refs/heads/master:a9525cd65e2776d0f49bf54a1ea35a645896839b]
Copyright (C) Microsoft Corporation. All rights reserved.
[0:05] 100.00% Processes:[2 done (0 hit), 0 executing, 0 waiting]
[0:05] -- Cache savings: 0.000% of 2 included processes. 0 excluded via filtering.
Build Succeeded
Log Directory: /Users/krisso/Work/BuildXL.Internal/Examples/ESSandboxBuildMacOS/Out/Logs
BuildXL Succeeded
[info] Asserting build produced observable outputs...
```

Просмотреть файл

@ -1,22 +0,0 @@
#!/bin/bash
readonly MY_DIR=$(cd `dirname ${BASH_SOURCE[0]}` && pwd)
source "${MY_DIR}/env.sh"
# check if SIP is disabled, fail if not.
readonly sipStatus="$(csrutil status)"
print_info "$sipStatus"
if [[ -z $(echo "$sipStatus" | grep -o "disabled") ]]; then
print_error "SIP is not disabled"
exit 1
fi
/bin/bash "${MacOsScriptsDir}/bxl.sh" \
--config "$MY_DIR/config.dsc" \
--symlink-sdks-into "$MY_DIR/sdk" \
--buildxl-bin "$BUILDXL_BIN" \
/useHardLinks+ \
/disableProcessRetryOnResourceExhaustion+ \
/exp:LazySODeletion+ \
/logObservedFileAccesses+ \
"$@"

Просмотреть файл

@ -1,44 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
config({
modules: [
d`sdk`,
d`src`,
].mapMany(dir => [...globR(dir, "module.config.dsc"), ...globR(dir, "package.config.dsc")]),
mounts: Context.getCurrentHost().os === "macOS" ? [
{
name: a`usrbin`,
path: p`/usr/bin`,
trackSourceFileChanges: true,
isWritable: false,
isReadable: true,
isScrubbable: false,
},
{
name: a`usrlib`,
path: p`/usr/lib`,
trackSourceFileChanges: true,
isWritable: false,
isReadable: true,
isScrubbable: false,
},
{
name: a`usrinclude`,
path: p`/usr/include`,
trackSourceFileChanges: true,
isWritable: false,
isReadable: true,
isScrubbable: false,
},
{
name: a`library`,
path: p`/Library`,
trackSourceFileChanges: true,
isWritable: false,
isReadable: true,
isScrubbable: false,
},
] : []
});

Просмотреть файл

@ -1,5 +0,0 @@
#!/bin/bash
export MacOsScriptsDir="$(cd `dirname ${BASH_SOURCE[0]}` && pwd)/../../Public/Src/Sandbox/MacOs/scripts"
source "${MacOsScriptsDir}/env.sh"

2
Examples/SandboxedBuildMacOS/sdk/.gitignore поставляемый
Просмотреть файл

@ -1,2 +0,0 @@
Sdk.Prelude
Sdk.Transformers

Просмотреть файл

@ -1,87 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import {Artifact, Cmd, Transformer} from "Sdk.Transformers";
@@public
export const isMacOS = Context.getCurrentHost().os === "macOS";
@@public
export const untrackedSystemScopes = [
d`/usr`,
d`/private`,
d`/dev`,
d`/etc`,
d`/Library`,
d`/System/Library`,
d`/AppleInternal`,
d`/var`,
d`/bin`,
...(Environment.hasVariable("HOME") ? [
d`${Environment.getDirectoryValue("HOME")}/Library`
] : [])
];
@@public
export const toolDefinition = <Transformer.ToolDefinition>{
exe: f`/bin/bash`,
runtimeDependencies: [
f`/bin/sh`
],
untrackedDirectoryScopes: untrackedSystemScopes
};
@@public
export function runBashCommand(hint: string, bashArguments: Argument[], printDebug?: boolean): Transformer.ExecuteResult {
if (!isMacOS) return undefined;
const outDir = Context.getNewOutputDirectory(hint);
const outFile = outDir.combine(hint + "-stdout.txt");
const exeArgs = <Transformer.ExecuteArguments>{
tool: toolDefinition,
workingDirectory: outDir,
arguments: [
Cmd.argument("-c"),
Cmd.rawArgument('"'),
...bashArguments,
Cmd.rawArgument('"')
],
consoleOutput: p`${outFile}`,
unsafe: {
passThroughEnvironmentVariables: [
"PATH",
"HOME",
"USER"
]
},
description: `bash-${hint}`
};
const result = Transformer.execute(exeArgs);
if (printDebug) {
Debug.writeLine(` *** ${Debug.dumpData(exeArgs.tool.exe)} ${Debug.dumpArgs(exeArgs.arguments)}`);
}
return result;
}
@@public
export function runInBashSubshells(hint: string, numRepeats: number, programCmdLine: ArgumentValue[], printDebug?: boolean): Transformer.ExecuteResult {
const bashArguments = [
Cmd.rawArgument('('), // () creates a subshell, i.e., child process
...join(
Cmd.rawArgument(") ; ("),
programCmdLine.map(Cmd.argument),
numRepeats),
Cmd.rawArgument(')')
];
return runBashCommand(hint, bashArguments);
}
function join<T>(sep: T, arr: T[], nTimes: number): T[] {
Contract.requires(nTimes >= 1);
return nTimes === 1 ? arr : [
...join(sep, arr, nTimes - 1),
sep,
...arr
];
}

Просмотреть файл

@ -1,6 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
module({
name: "Bash"
});

Просмотреть файл

@ -1,58 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import {Artifact, Cmd, Transformer} from "Sdk.Transformers";
import * as Bash from "Bash";
@@public
export const cmdTool: Transformer.ToolDefinition = Context.isWindowsOS()
? getCmdToolDefinition()
: Bash.toolDefinition;
function getCmdToolDefinition(): Transformer.ToolDefinition {
return {
exe: f`${Environment.getPathValue("ComSpec")}`,
dependsOnWindowsDirectories: true,
untrackedDirectoryScopes: [
d`${Environment.getPathValue("SystemRoot")}`
]
};
}
@@public
export function echoViaShellExecute(message: string, printDebug?: boolean): DerivedFile {
const wd = Context.getNewOutputDirectory("cmd");
const outFile = p`${wd}/stdout.txt`;
const exeArgs = <Transformer.ExecuteArguments>{
tool: cmdTool,
workingDirectory: wd,
arguments: Context.isWindowsOS()
? getExecuteArgumentsForCmd(message, outFile)
: getExecuteArgumentsForBash(message, outFile),
implicitOutputs: [
outFile
],
};
if (printDebug) {
Debug.writeLine(` *** ${Debug.dumpData(exeArgs.tool.exe)} ${Debug.dumpArgs(exeArgs.arguments)}`);
}
return Transformer.execute(exeArgs).getOutputFile(outFile);
}
function getExecuteArgumentsForCmd(message: string, outFile: Path): Argument[] {
return [
Cmd.argument("/d"),
Cmd.argument("/c"),
Cmd.argument("echo"),
Cmd.argument(message),
Cmd.rawArgument(" > "),
Cmd.argument(Artifact.output(outFile))
];
}
function getExecuteArgumentsForBash(message: string, outFile: Path): Argument[] {
return [
Cmd.argument("-c"),
Cmd.argument(`echo ${message} > ${Debug.dumpData(outFile)}`)
];
}

Просмотреть файл

@ -1,6 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
module({
name: "Echo"
});

Просмотреть файл

@ -1,65 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import * as Echo from "Echo";
import * as Bash from "Bash";
import {Cmd, Artifact, Transformer} from "Sdk.Transformers";
@@public
export const foo = printFromBuildXL("HelloWorld");
const helloWorldMsgVarName = "HELLO_WORLD_MSG";
@@public
export const writeFilePipOutput = (() => {
const outDir = Context.getNewOutputDirectory("write-pip");
const outFile = Transformer.writeAllText({
outputPath: p`${outDir}/write-pip-out-file.txt`,
text: "Produced by a WriteFile pip"
});
Debug.writeLine(` *** Using WriteFile pip to write to ${outFile}`);
return outFile;
})();
@@public
export const boo = printToFileViaPip(Environment.hasVariable(helloWorldMsgVarName)
? Environment.getStringValue(helloWorldMsgVarName)
: "HelloWorldViaPip");
@@public
export const cp = (() => {
if (!Bash.isMacOS) return undefined;
const outDir = Context.getNewOutputDirectory("dest");
const outFile = outDir.combine("newfile.txt");
const args = <Transformer.ExecuteArguments>{
tool: <Transformer.ToolDefinition>{
exe: f`/bin/cp`,
runtimeDependencies: [
f`/bin/sh`
]
},
workingDirectory: outDir,
arguments: [
Cmd.argument(Artifact.input(f`HelloWorld.dsc`)),
Cmd.argument(Artifact.output(outFile))
],
unsafe: {
untrackedScopes: Bash.untrackedSystemScopes
}
};
const result = Transformer.execute(args);
const derivedFile = result.getOutputFile(outFile);
Debug.writeLine(` *** ${Debug.dumpData(args.tool.exe)} ${Debug.dumpArgs(args.arguments)}`);
return derivedFile;
})();
function printFromBuildXL(message: string): number {
Debug.writeLine(` *** ${message} from BuildXL *** `);
return 42;
}
function printToFileViaPip(message: string): DerivedFile {
const outFile = Echo.echoViaShellExecute(message, /* printDebug: */ true);
return outFile;
}

Просмотреть файл

@ -1,10 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#include <stdio.h>
int main(int argc, char** argv)
{
printf("Hello World!\n");
return 0;
}

Просмотреть файл

@ -1,6 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
module({
name: "HelloWorld"
});

Просмотреть файл

@ -1,63 +0,0 @@
#!/bin/bash
readonly MY_DIR=$(cd `dirname ${BASH_SOURCE[0]}` && pwd)
readonly ARGS="$@"
function run_build {
/bin/bash $MY_DIR/build.sh /logsDirectory:"$bxlLogDir" /o:"$bxlObjDir" $ARGS "$@"
}
function check_fully_cached {
print_info "Asserting build was fully cached..."
grep -q "Process pip cache hits: 100%" $bxlLogFile
check_success $?
}
function check_success {
if [[ $@ != 0 ]]; then
print_error "Assertion failed."
exit 2
fi
}
function check_observed_writes {
print_info "Asserting build produced observable outputs..."
grep -q 'MAC_VNODE_CREATE\|VNODE_WRITE' $bxlLogFile
check_success $?
}
function print_header {
echo " ==============================================================================="
echo " === $@"
echo " ==============================================================================="
}
# ======================= script entry point
source "$MY_DIR/env.sh"
readonly bxlOutDir="$MY_DIR/out"
readonly bxlObjDir="$bxlOutDir/objects.noindex"
readonly bxlLogDir="$bxlOutDir/logs"
readonly bxlLogFile="$bxlLogDir/BuildXL.log"
# start with clean Out dir, presrving log files
find $bxlOutDir/ ! -name "*1st.log" ! -name "*2nd.log" -type f -delete
# 1st run
print_header "1st run: clean build"
run_build
check_success $?
check_observed_writes
mv $bxlLogDir/BuildXL.log $bxlLogDir/BuildXL-$((1 + $RANDOM % 50000))-1st.log
# 2nd run: clean Obj dir, and run build again
echo
print_info "Deleting $bxlObjDir"
rm -rf "$bxlObjDir"*
print_header "2nd run: fully cached"
run_build
check_success $?
check_fully_cached
mv $bxlLogDir/BuildXL.log $bxlLogDir/BuildXL-$((1 + $RANDOM % 50000))-2nd.log

Просмотреть файл

@ -27,9 +27,6 @@ using HelpLevel = BuildXL.Utilities.Configuration.HelpLevel;
using Strings = bxl.Strings;
using System.Text.RegularExpressions;
using BuildXL.Processes;
#if PLATFORM_OSX
using static BuildXL.Interop.Unix.Memory;
#endif
#pragma warning disable SA1649 // File name must match first type name
@ -532,48 +529,6 @@ namespace BuildXL
ContentHashingUtilities.SetDefaultHashType(hashType);
cacheConfiguration.UseDedupStore = hashType.IsValidDedup();
}),
#if PLATFORM_OSX
OptionHandlerFactory.CreateOption(
"numberOfKextConnections", // TODO: deprecate and remove
opt =>
{
Console.WriteLine("*** WARNING: deprecated switch /numberOfKextConnections; don't use it as it has no effect any longer");
}),
OptionHandlerFactory.CreateOption(
"reportQueueSizeMb", // TODO: deprecate and remove
opt =>
{
Console.WriteLine("*** WARNING: deprecated switch /reportQueueSizeMb; please use /kextReportQueueSizeMb instead");
sandboxConfiguration.KextReportQueueSizeMb = CommandLineUtilities.ParseUInt32Option(opt, 16, 2048);
}),
OptionHandlerFactory.CreateBoolOption(
"kextEnableReportBatching",
sign => sandboxConfiguration.KextEnableReportBatching = sign),
OptionHandlerFactory.CreateBoolOption(
"measureProcessCpuTimes",
sign => sandboxConfiguration.MeasureProcessCpuTimes = sign),
OptionHandlerFactory.CreateOption(
"kextNumberOfConnections",
opt =>
{
Console.WriteLine("*** WARNING: deprecated switch /kextNumberOfKextConnections; don't use it as it has no effect any longer");
}),
OptionHandlerFactory.CreateOption(
"kextReportQueueSizeMb",
opt => sandboxConfiguration.KextReportQueueSizeMb = CommandLineUtilities.ParseUInt32Option(opt, 16, 2048)),
OptionHandlerFactory.CreateOption(
"kextThrottleCpuUsageBlockThresholdPercent",
opt => sandboxConfiguration.KextThrottleCpuUsageBlockThresholdPercent = CommandLineUtilities.ParseUInt32Option(opt, 0, 100)),
OptionHandlerFactory.CreateOption(
"kextThrottleCpuUsageWakeupThresholdPercent",
opt => sandboxConfiguration.KextThrottleCpuUsageWakeupThresholdPercent = CommandLineUtilities.ParseUInt32Option(opt, 0, 100)),
OptionHandlerFactory.CreateOption(
"kextThrottleMinAvailableRamMB",
opt => sandboxConfiguration.KextThrottleMinAvailableRamMB = CommandLineUtilities.ParseUInt32Option(opt, 0, uint.MaxValue)),
OptionHandlerFactory.CreateOption(
"maxMemoryPressureLevel",
opt => schedulingConfiguration.MaximumAllowedMemoryPressureLevel = CommandLineUtilities.ParseEnumOption<PressureLevel>(opt)),
#endif
OptionHandlerFactory.CreateOption2(
"help",
"?",
@ -981,13 +936,6 @@ namespace BuildXL
opt =>
{
var parsedOption = CommandLineUtilities.ParseEnumOption<SandboxKind>(opt);
#if PLATFORM_OSX
var isEndpointSecurityOrHybridSandboxKind = (parsedOption == SandboxKind.MacOsEndpointSecurity || parsedOption == SandboxKind.MacOsHybrid);
if (isEndpointSecurityOrHybridSandboxKind && !OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport)
{
parsedOption = SandboxKind.MacOsKext;
}
#endif
sandboxConfiguration.UnsafeSandboxConfigurationMutable.SandboxKind = parsedOption;
if ((parsedOption.ToString().StartsWith("Win") && OperatingSystemHelper.IsUnixOS) ||
(parsedOption.ToString().StartsWith("Mac") && !OperatingSystemHelper.IsUnixOS))

Просмотреть файл

@ -1122,40 +1122,6 @@ namespace BuildXL
Strings.HelpText_DisplayHelp_AllowInternalDetoursErrorNotificationFile,
HelpLevel.Verbose);
#if PLATFORM_OSX
hw.WriteOption(
"/kextNumberOfKextConnections:<number>",
Strings.HelpText_DisplayHelp_KextNumberOfKextConnections,
HelpLevel.Verbose);
hw.WriteOption(
"/kextReportQueueSizeMb:<number>",
Strings.HelpText_DisplayHelp_KextReportQueueSizeMb,
HelpLevel.Verbose);
hw.WriteOption(
"/kextThrottleCpuUsageBlockThresholdPercent:<number>",
Strings.HelpText_DisplayHelp_KextThrottleCpuUsageBlockThresholdPercent,
HelpLevel.Verbose);
hw.WriteOption(
"/kextThrottleCpuUsageWakeupThresholdPercent:<number>",
Strings.HelpText_DisplayHelp_KextThrottleCpuUsageWakeupThresholdPercent,
HelpLevel.Verbose);
hw.WriteOption(
"/KextThrottleMinAvailableRamMB:<number>",
Strings.HelpText_DisplayHelp_KextThrottleMinAvailableRamMB,
HelpLevel.Verbose);
hw.WriteOption(
"/maxMemoryPressureLevel:<option>",
Strings.HelpText_DisplayHelp_MaxMemoryPressureLevel,
HelpLevel.Verbose);
#endif
hw.WriteOption(
"/unsafe_ExistingDirectoryProbesAsEnumerations[+|-]",
Strings.HelpText_DisplayHelp_ExistingDirectoryProbesAsEnumerations,

Просмотреть файл

@ -844,24 +844,6 @@ Example: ad2d42d2ec5d2ca0c0b7ad65402d07c7ef40b91e</value>
<data name="HelpText_DisplayHelp_AllowInternalDetoursErrorNotificationFile" xml:space="preserve">
<value>When enabled, the detoured processes will store internal errors in a special file. If this file contains data at the end of pip execution, the pip (build) will fail. Defaults to on.</value>
</data>
<data name="HelpText_DisplayHelp_KextNumberOfKextConnections" xml:space="preserve">
<value>[macOS] Specifies the number of connections that drain the sandbox kernel extension file access reports on macOS systems - the sandbox kernel extension allocates one report queue per connection to balance the system load when reporting file accesses.</value>
</data>
<data name="HelpText_DisplayHelp_KextReportQueueSizeMb" xml:space="preserve">
<value>[macOS] Specifies the size in MB of a sandbox kernel extension report queue. Use this cautiously, because it allocates the specified size in wired system memory multiplied by the number of kernel extension connections.</value>
</data>
<data name="HelpText_DisplayHelp_KextThrottleCpuUsageBlockThresholdPercent" xml:space="preserve">
<value>[macOS] Causes throttling to be triggered whenever CPU usage is above this value.</value>
</data>
<data name="HelpText_DisplayHelp_KextThrottleCpuUsageWakeupThresholdPercent" xml:space="preserve">
<value>[macOS] A blocked process can be awakened only when CPU usage drops below this value (defaults to /KextThrottleCpuUsageBlockThresholdPercent).</value>
</data>
<data name="HelpText_DisplayHelp_KextThrottleMinAvailableRamMB" xml:space="preserve">
<value>[macOS] Causes throttling to be triggered whenever available RAM drops below this value. (takes precedence over CPU usage thresholds)</value>
</data>
<data name="HelpText_DisplayHelp_MaxMemoryPressureLevel" xml:space="preserve">
<value>[macOS] Causes scheduling to pause / cancel pips to free up system resources if specified maximum pressure level is overstepped. Allowed values are 'Normal' (Default), 'Warning' and 'Critical'. NOTE: Using 'Critical' can lead to system instablities and eventual kernel panics due to resource starvation!</value>
</data>
<data name="HelpText_DisplayHelp_LogMemory" xml:space="preserve">
<value>Collects actual memory usage when collection performance counters. This has a negaitve performance impact and should only be used when analyzing memory consumption. Defaults to off.</value>
</data>

Просмотреть файл

@ -36,16 +36,8 @@ namespace BuildXL.Demo
// Enumerates all files and read them
string pathToProcess;
string arguments;
if (OperatingSystemHelper.IsUnixOS)
{
pathToProcess = "/usr/bin/find";
arguments = ". -type f -exec /bin/cat {} \\;";
}
else
{
pathToProcess = Environment.GetEnvironmentVariable("COMSPEC");
arguments = "/C for /r %f in (*) do type %~ff";
}
pathToProcess = Environment.GetEnvironmentVariable("COMSPEC");
arguments = "/C for /r %f in (*) do type %~ff";
var info = new SandboxedProcessInfo(
m_pathTable,
@ -58,8 +50,7 @@ namespace BuildXL.Demo
Arguments = arguments,
WorkingDirectory = directoryToEnumerateAsString,
PipSemiStableHash = 0,
PipDescription = "EnumerateWithBlockedDirectories",
SandboxConnection = OperatingSystemHelper.IsUnixOS ? new SandboxConnectionKext() : null
PipDescription = "EnumerateWithBlockedDirectories"
};
var process = SandboxedProcessFactory.StartAsync(info, forceSandboxing: true).GetAwaiter().GetResult();

Просмотреть файл

@ -63,8 +63,7 @@ var info =
Arguments = arguments,
WorkingDirectory = workingDirectory,
PipSemiStableHash = 0,
PipDescription = "Simple sandbox demo",
SandboxConnection = OperatingSystemHelper.IsUnixOS ? new SandboxConnectionKext() : null
PipDescription = "Simple sandbox demo"
};
```
@ -85,40 +84,6 @@ We are creating a manifest that configures the sandbox so:
As a result of this configuration, all file accesses are allowed and reported. Each file access carries structured information that includes the type of operation, disposition, attributes, etc. In this simple demo we are just printing out the path of each access.
This demo works on macOS as well, but only supports absolute paths in the arguments.
```
~/BuildXL$ <bxl_repo_root>/out/bin/Demos/Debug/osx-x64/ReportAccesses /bin/echo
Process '/bin/echo' ran under BuildXL sandbox with arguments '' and returned with exit code '0'. Sandbox reports 48 file accesses:
/bin/echo
/usr/lib/dyld
/private/var/db/dyld/dyld_shared_cache_x86_64h
/usr/lib/libSystem.B.dylib
/usr/lib/system/libcache.dylib
/usr/lib/system/libcommonCrypto.dylib
/usr/lib/system/libcompiler_rt.dylib
/usr/lib/system/libcopyfile.dylib
...
...
...
...
/usr/lib/system/libsystem_notify.dylib
/usr/lib/system/libsystem_sandbox.dylib
/dev/dtracehelper
/usr/lib/system/libsystem_secinit.dylib
/usr/lib/system/libsystem_kernel.dylib
/usr/lib/system/libsystem_platform.dylib
/AppleInternal
/usr/lib/system/libsystem_pthread.dylib
/usr/lib/system/libsystem_symptoms.dylib
/usr/lib/system/libsystem_trace.dylib
/usr/lib/system/libunwind.dylib
/usr/lib/system/libxpc.dylib
/usr/lib/libobjc.A.dylib
/usr/lib/libc++abi.dylib
/usr/lib/libc++.1.dylib
```
## Blocking accesses (`Public/Src/Demos/BlockAccesses`)
The next demo shows how to use BuildXL sandbox to actually block accesses with certain characteristics. Given a directory provided by the user, a process is launched under the sandbox which tries to enumerate the given directory recursively and perform a read on every file found. However, a collection of directories to block can also be provided: the sandbox will make sure that any access that falls under these directories will be blocked, preventing the tool from accessing those files.
@ -215,41 +180,6 @@ var allAccesses = result
`SandboxedProcessResult.FileAccesses` contains all the reported accesses. So we just iterate over them and print some of the details.
This demo works on mac as well (with the same directory structure as before)
```
~/BuildXL$ <bxl_repo_root>/out/bin/Demos/Debug/BlockAccesses ~/test/ ~/test/obj/ ~/test/bin/
Enumerated the directory '/Users/BuildXLUser/test/'. The following accesses were reported:
Allowed -> [Read] /usr/bin/find
Allowed -> [Read] /usr/lib/dyld
Allowed -> [Probe] /usr/bin/find
Allowed -> [Probe] /private/var/db/dyld/dyld_shared_cache_x86_64h
Allowed -> [Probe] /usr/lib/libSystem.B.dylib
...
...
...
...
Allowed -> [Probe] /usr/lib/libc++.1.dylib
Allowed -> [Probe] /AppleInternal/XBS/.isChrooted
Allowed -> [Read] find
Allowed -> [Read] /bin/cat
Allowed -> [Probe] /bin/cat
Allowed -> /bin/cat
Allowed -> /usr/bin/find
Allowed -> [Probe] /Users/BuildXLUser/test
Allowed -> [Enumerate] /Users/BuildXLUser/test
Allowed -> [Enumerate] /Users/BuildXLUser/test/obj
Allowed -> [Probe] /Users/BuildXLUser/test/obj
Allowed -> [Probe] /Users/BuildXLUser/test/bin
Allowed -> [Probe] /Users/BuildXLUser/test/source
Denied -> [Read] /Users/BuildXLUser/test/obj/t1.obj
Allowed -> [Enumerate] /Users/BuildXLUser/test/bin
Denied -> [Read] /Users/BuildXLUser/test/obj/src2.txt
Denied -> [Read] /Users/BuildXLUser/test/bin/t1
Allowed -> [Enumerate] /Users/BuildXLUser/test/source
Allowed -> [Read] /Users/BuildXLUser/test/source/t1.txt
```
## Retrieving the process list (`Public/Src/Demos/ReportProcesses`)
The last demo shows how the sandbox can be used to retrieve the list of processes spawned by a process that was run under the sandbox. All child processes that are created during the execution of the main process is reported, together with structured information that contains IO and CPU counters, elapsed times, etc.
@ -300,18 +230,4 @@ All the processes (main and children) are reported in ``SandboxedProcessResult.P
```cs
/// Public/Src/Demos/ReportProcesses/Program.cs
Console.WriteLine($"{reportedProcess.Path} [ran {(reportedProcess.ExitTime - reportedProcess.CreationTime).TotalMilliseconds}ms]");
```
Here is the process list reported on Mac
```
~/BuildXL$ ./out/bin/Demos/debug/osx-x64/ReportProcesses /usr/bin/git fetch
Process '/usr/bin/git' ran under the sandbox. These processes were launched in the sandbox:
/usr/bin/git [ran 0ms]
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git-remote-http [ran 0ms]
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git [ran 0ms]
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git [ran 0ms]
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git [ran 0ms]
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git [ran 0ms]
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git [ran 0.001ms]
```

Просмотреть файл

@ -43,8 +43,7 @@ namespace BuildXL.Demo
Arguments = arguments,
WorkingDirectory = Directory.GetCurrentDirectory(),
PipSemiStableHash = 0,
PipDescription = "Simple sandbox demo",
SandboxConnection = OperatingSystemHelper.IsUnixOS ? new SandboxConnectionKext() : null
PipDescription = "Simple sandbox demo"
};
var process = SandboxedProcessFactory.StartAsync(info, forceSandboxing: true).GetAwaiter().GetResult();

Просмотреть файл

@ -54,8 +54,7 @@ namespace BuildXL.Demo
Arguments = arguments,
WorkingDirectory = Directory.GetCurrentDirectory(),
PipSemiStableHash = 0,
PipDescription = "Process list demo",
SandboxConnection = OperatingSystemHelper.IsUnixOS ? new SandboxConnectionKext() : null
PipDescription = "Process list demo"
};
var process = SandboxedProcessFactory.StartAsync(info, forceSandboxing: true).GetAwaiter().GetResult();

Просмотреть файл

@ -705,15 +705,6 @@ namespace NugetPackages {
];
// End cache packages
// Currently we deploy tools as self-contained .NET Core binaries for macOS only!
const toolsSandBoxExec = pack({
id: `${packageNamePrefix}.Tools.SandboxExec.osx-x64`,
deployment: Tools.SandboxExec.withQualifier({
targetFramework: defaultTargetFramework,
targetRuntime: "osx-x64"
}).deployment
});
// Currently we deploy tools as self-contained .NET Core binaries for macOS only!
const toolsAdoBuildRunner = pack({
id: `${packageNamePrefix}.Tools.AdoBuildRunner.osx-x64`,
@ -746,8 +737,6 @@ namespace NugetPackages {
processes,
engineCache,
sdks,
// macOS specific packages
toolsSandBoxExec,
osxX64,
toolsAdoBuildRunner,
]),

Просмотреть файл

@ -18,23 +18,7 @@ namespace Tools {
return r`${qualifier.targetFramework}/${qualifier.configuration}/tools/${toolName}/${qualifier.targetRuntime}`;
}
}
namespace SandboxExec {
export declare const qualifier: BuildXLSdk.NetCoreAppQualifier;
export const deployment : Deployment.Definition = {
contents: [
importFrom("BuildXL.Tools").SandboxExec.exe
]
};
const deployed = BuildXLSdk.DeploymentHelpers.deploy({
definition: deployment,
targetLocation: Helpers.getTargetLocation("SandboxExec"),
});
}
namespace AdoBuildRunner {
export declare const qualifier: BuildXLSdk.NetCoreAppQualifier;

Просмотреть файл

@ -2029,7 +2029,6 @@ namespace BuildXL.ProcessPipExecutor
retryInfo = RetryInfo.GetDefault(RetryReason.AzureWatsonExitCode);
}
else if (MonitorFileAccesses
&& m_sandboxConfig.UnsafeSandboxConfiguration.SandboxKind != SandboxKind.MacOsKextIgnoreFileAccesses
&& status == SandboxedProcessPipExecutionStatus.Succeeded
&& m_pip.PipType == PipType.Process
&& unobservedOutputs != null)

Просмотреть файл

@ -16,7 +16,7 @@ namespace BuildXL.Processes
public interface ISandboxConnection : IDisposable
{
/// <summary>
/// The sandbox kind used by the backing SandboxConnection, e.g. MacOsKext
/// The sandbox kind used by the backing SandboxConnection
/// </summary>
SandboxKind Kind { get; }

Просмотреть файл

@ -1,247 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.ContractsLight;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using BuildXL.Interop.Unix;
using BuildXL.Native.Processes;
using BuildXL.Utilities.Core;
using BuildXL.Utilities.Instrumentation.Common;
namespace BuildXL.Processes
{
/// <summary>
/// A class that manages the connection to the generic macOS sandboxing implementation
/// </summary>
public sealed class SandboxConnection : ISandboxConnection
{
/// <inheritdoc />
public SandboxKind Kind { get; }
/// <inheritdoc />
public ulong MinReportQueueEnqueueTime => Volatile.Read(ref m_reportQueueLastEnqueueTime);
/// <inheritdoc />
public bool IsInTestMode { get; }
/// <nodoc />
public static bool IsInDebugMode { get; } =
#if DEBUG
true;
#else
false;
#endif
private readonly ConcurrentDictionary<long, SandboxedProcessUnix> m_pipProcesses = new ConcurrentDictionary<long, SandboxedProcessUnix>();
// TODO: remove at some later point
private Sandbox.KextConnectionInfo m_fakeKextConnectionInfo = new Sandbox.KextConnectionInfo();
private readonly Sandbox.SandboxConnectionInfo m_sandboxConnectionInfo;
/// <summary>
/// Enqueue time of the last received report (or 0 if no reports have been received)
/// </summary>
private ulong m_reportQueueLastEnqueueTime;
/// <summary>
/// The time (in ticks) when the last report was received.
/// </summary>
private long m_lastReportReceivedTimestampTicks = DateTime.UtcNow.Ticks;
private long LastReportReceivedTimestampTicks => Volatile.Read(ref m_lastReportReceivedTimestampTicks);
private Sandbox.AccessReportCallback m_AccessReportCallback;
static private Sandbox.Configuration ConfigurationForSandboxKind(SandboxKind kind)
{
switch (kind)
{
case SandboxKind.MacOsEndpointSecurity:
return Sandbox.Configuration.EndpointSecuritySandboxType;
case SandboxKind.MacOsDetours:
return Sandbox.Configuration.DetoursSandboxType;
case SandboxKind.MacOsHybrid:
return Sandbox.Configuration.HybridSandboxType;
case SandboxKind.MacOsKext:
return Sandbox.Configuration.KextType;
default:
throw new BuildXLException($"Could not find mapping for sandbox configration with sandbox kind: {kind}", ExceptionRootCause.FailFast);
}
}
/// <inheritdoc />
public TimeSpan CurrentDrought => DateTime.UtcNow.Subtract(new DateTime(ticks: LastReportReceivedTimestampTicks));
/// <summary>
/// Initializes the ES sandbox
/// </summary>
public SandboxConnection(SandboxKind kind, bool isInTestMode = false)
{
Kind = kind;
m_reportQueueLastEnqueueTime = 0;
m_sandboxConnectionInfo = new Sandbox.SandboxConnectionInfo()
{
Config = ConfigurationForSandboxKind(kind),
Error = Sandbox.SandboxSuccess
};
IsInTestMode = isInTestMode;
var process = System.Diagnostics.Process.GetCurrentProcess();
Sandbox.InitializeSandbox(ref m_sandboxConnectionInfo, process.Id);
if (m_sandboxConnectionInfo.Error != Sandbox.SandboxSuccess)
{
throw new BuildXLException($@"Unable to initialize generic sandbox, please check the sources for error code: {m_sandboxConnectionInfo.Error})");
}
ProcessUtilities.SetNativeConfiguration(IsInDebugMode);
m_AccessReportCallback = (Sandbox.AccessReport report, int code) =>
{
if (code != Sandbox.ReportQueueSuccessCode)
{
var message = "Sandbox event delivery failed with error: " + code;
throw new BuildXLException(message, ExceptionRootCause.MissingRuntimeDependency);
}
// Stamp the access report with a dequeue timestamp
report.Statistics.DequeueTime = Sandbox.GetMachAbsoluteTime();
// Update last received timestamp
Volatile.Write(ref m_lastReportReceivedTimestampTicks, DateTime.UtcNow.Ticks);
// Remember the latest enqueue time
Volatile.Write(ref m_reportQueueLastEnqueueTime, report.Statistics.EnqueueTime);
// The only way it can happen that no process is found for 'report.PipId' is when that pip is
// explicitly terminated (e.g., because it timed out or Ctrl-c was pressed)
if (m_pipProcesses.TryGetValue(report.PipId, out var process))
{
// if the process is found, its ProcessId must match the RootPid of the report.
if (process.ProcessId != report.RootPid)
{
throw new BuildXLException("The process id from the lookup did not match the file access report process id", ExceptionRootCause.FailFast);
}
else
{
process.PostAccessReport(report);
}
}
};
Sandbox.ObserverFileAccessReports(ref m_sandboxConnectionInfo, m_AccessReportCallback, Marshal.SizeOf<Sandbox.AccessReport>());
}
/// <summary>
/// Disposes the sandbox connection and release the resources in the interop layer, when running tests this can be skipped
/// </summary>
public void Dispose()
{
if (!IsInTestMode)
{
ReleaseResources();
}
}
/// <summary>
/// Releases all resources and cleans up the interop instance too
/// </summary>
public void ReleaseResources()
{
Sandbox.DeinitializeSandbox();
m_AccessReportCallback = null;
}
/// <inheritdoc />
public bool NotifyUsage(uint cpuUsage, uint availableRamMB)
{
// TODO: Will we need this?
return true;
}
/// <inheritdoc />
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName)
{
return Enumerable.Empty<(string, string)>();
}
/// <inheritdoc />
public void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process, Task reportCompletion) {}
/// <inheritdoc />
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process)
{
Contract.Requires(process.Started);
Contract.Requires(process.PipId != 0);
if (!m_pipProcesses.TryAdd(fam.PipId, process))
{
throw new BuildXLException($"Process with PidId {process.PipId} already exists");
}
var setup = new FileAccessSetup()
{
DllNameX64 = string.Empty,
DllNameX86 = string.Empty,
ReportPath = process.ExecutableAbsolutePath, // piggybacking on ReportPath to pass full executable path
};
using (var wrapper = Pools.MemoryStreamPool.GetInstance())
{
var debugFlags = true;
ArraySegment<byte> manifestBytes = fam.GetPayloadBytes(
loggingContext,
setup,
wrapper.Instance,
timeoutMins: 10, // don't care because on Mac we don't kill the process from the sandbox once it times out
debugFlagsMatch: ref debugFlags);
Contract.Assert(manifestBytes.Offset == 0);
var result = Sandbox.SendPipStarted(
processId: process.ProcessId,
pipId: process.PipId,
famBytes: manifestBytes.Array,
famBytesLength: manifestBytes.Count,
type: Sandbox.ConnectionType.EndpointSecurity,
info: ref m_fakeKextConnectionInfo);
return result;
}
}
/// <inheritdoc />
public void NotifyPipProcessTerminated(long pipId, int processId)
{
Sandbox.SendPipProcessTerminated(pipId, processId, type: Sandbox.ConnectionType.EndpointSecurity, info: ref m_fakeKextConnectionInfo);
}
/// <inheritdoc />
public void NotifyRootProcessExited(long pipId, SandboxedProcessUnix process)
{
// this implementation keeps track of what the root process is an when it exits,
// so it doesn't need to be notified about it separately
}
/// <inheritdoc />
public bool NotifyPipFinished(long pipId, SandboxedProcessUnix process)
{
if (m_pipProcesses.TryRemove(pipId, out var proc))
{
Contract.Assert(process == proc);
return true;
}
else
{
return false;
}
}
}
}

Просмотреть файл

@ -1,328 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.ContractsLight;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BuildXL.Interop.Unix;
using BuildXL.Native.Processes;
using BuildXL.Utilities.Core;
using BuildXL.Utilities.Instrumentation.Common;
namespace BuildXL.Processes
{
/// <summary>
/// A class that manages the connection to the macOS kernel extension and provides utilities to communicate with the sandbox kernel extension
/// </summary>
public sealed class SandboxConnectionKext : ISandboxConnection
{
/// <inheritdoc />
public SandboxKind Kind => SandboxKind.MacOsKext;
/// <summary>
/// Configuration for <see cref="SandboxConnectionKext"/>.
/// </summary>
public sealed class Config
{
/// <summary>
/// Whether to measure user/system CPU times of sandboxed processes.
/// </summary>
public bool MeasureCpuTimes;
/// <summary>
/// Configuration for the kernel extension.
/// </summary>
public Sandbox.KextConfig? KextConfig;
/// <summary>
/// Callback to invoke in the case of an irrecoverable kernel extension error.
/// </summary>
public Sandbox.ManagedFailureCallback FailureCallback;
}
/// <inheritdoc />
public ulong MinReportQueueEnqueueTime => Volatile.Read(ref m_reportQueueLastEnqueueTime);
/// <inheritdoc />
public bool IsInTestMode { get; }
private const string KextInstallHelperFormat =
@"
Use the the following command to load/reload the sandbox kernel extension and fix this issue:
----> sudo /bin/bash '{0}/bxl.sh' --load-kext <----
";
private static readonly string s_buildXLBin = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetLocation());
private string KextInstallHelper { get; } = string.Format(KextInstallHelperFormat, s_buildXLBin);
/// <summary>
/// <see cref="Native.Processes.Unix.KextInfo.RequiredKextVersionNumber"/>
/// </summary>
public const string RequiredKextVersionNumber = Native.Processes.Unix.KextInfo.RequiredKextVersionNumber;
/// <summary>
/// See TN2420 (https://developer.apple.com/library/archive/technotes/tn2420/_index.html) on how versioning numbers are formatted in the Apple ecosystem
/// </summary>
private const int MaxVersionNumberLength = 17;
private readonly ConcurrentDictionary<long, SandboxedProcessUnix> m_pipProcesses = new ConcurrentDictionary<long, SandboxedProcessUnix>();
private Sandbox.KextConnectionInfo m_kextConnectionInfo;
private readonly Sandbox.KextSharedMemoryInfo m_sharedMemoryInfo;
private readonly Sandbox.ManagedFailureCallback m_failureCallback;
private readonly Thread m_workerThread;
/// <summary>
/// Enqueue time of the last received report (or 0 if no reports have been received)
/// </summary>
private ulong m_reportQueueLastEnqueueTime;
/// <summary>
/// The time (in ticks) when the last report was received.
/// </summary>
private long m_lastReportReceivedTimestampTicks = DateTime.UtcNow.Ticks;
private long LastReportReceivedTimestampTicks => Volatile.Read(ref m_lastReportReceivedTimestampTicks);
/// <inheritdoc />
public TimeSpan CurrentDrought => DateTime.UtcNow.Subtract(new DateTime(ticks: LastReportReceivedTimestampTicks));
/// <summary>
/// Initializes the sandbox kernel extension connection, setting up the kernel extension connection and workers that drain the
/// kernel event queue and report file accesses
/// </summary>
public SandboxConnectionKext(Config config = null, bool skipDisposingForTests = false)
{
m_reportQueueLastEnqueueTime = 0;
m_kextConnectionInfo = new Sandbox.KextConnectionInfo() { Error = Sandbox.SandboxSuccess };
m_sharedMemoryInfo = new Sandbox.KextSharedMemoryInfo() { Error = Sandbox.SandboxSuccess };
IsInTestMode = skipDisposingForTests;
// initialize kext connection
Sandbox.InitializeKextConnection(ref m_kextConnectionInfo);
if (m_kextConnectionInfo.Error != Sandbox.SandboxSuccess)
{
throw new BuildXLException($@"Unable to connect to sandbox kernel extension (Code: {m_kextConnectionInfo.Error}) - make sure it is loaded and retry! {KextInstallHelper}");
}
// check and set if the sandbox is running in debug configuration
bool isDebug = false;
Sandbox.CheckForDebugMode(ref isDebug, m_kextConnectionInfo);
ProcessUtilities.SetNativeConfiguration(isDebug);
#if DEBUG
if (!ProcessUtilities.IsNativeInDebugConfiguration())
#else
if (ProcessUtilities.IsNativeInDebugConfiguration())
#endif
{
throw new BuildXLException($"Sandbox kernel extension build flavor mismatch - the extension must match the engine build flavor, Debug != Release. {KextInstallHelper}");
}
// check if the sandbox version matches
var stringBufferLength = MaxVersionNumberLength + 1;
var version = new StringBuilder(stringBufferLength);
Sandbox.KextVersionString(version, stringBufferLength);
if (!RequiredKextVersionNumber.Equals(version.ToString().TrimEnd('\0')))
{
throw new BuildXLException($"Sandbox kernel extension version mismatch, the loaded kernel extension version '{version}' does not match the required version '{RequiredKextVersionNumber}'. {KextInstallHelper}");
}
if (config?.KextConfig != null)
{
if (!Sandbox.Configure(config.KextConfig.Value, m_kextConnectionInfo))
{
throw new BuildXLException($"Unable to configure sandbox kernel extension");
}
}
m_failureCallback = config?.FailureCallback;
// Initialize the shared memory region
Sandbox.InitializeKextSharedMemory(m_kextConnectionInfo, ref m_sharedMemoryInfo);
if (m_sharedMemoryInfo.Error != Sandbox.SandboxSuccess)
{
throw new BuildXLException($"Unable to allocate shared memory region for worker (Code:{m_sharedMemoryInfo.Error})");
}
if (!SetFailureNotificationHandler())
{
throw new BuildXLException($"Unable to set sandbox kernel extension failure notification callback handler");
}
m_workerThread = new Thread(() => StartReceivingAccessReports(m_sharedMemoryInfo.Address, m_sharedMemoryInfo.Port));
m_workerThread.IsBackground = true;
m_workerThread.Priority = ThreadPriority.Highest;
m_workerThread.Start();
}
private unsafe bool SetFailureNotificationHandler()
{
return Sandbox.SetFailureNotificationHandler(KextFailureCallback, m_kextConnectionInfo);
}
private unsafe void KextFailureCallback(void* refCon, int status)
{
m_failureCallback?.Invoke(status, $"Unrecoverable kernel extension failure happened - try reloading the kernel extension or restart your system. {KextInstallHelper}");
}
/// <summary>
/// Disposes the sandbox kernel extension connection and release the resources in the interop layer, when running tests this can be skipped
/// </summary>
public void Dispose()
{
if (!IsInTestMode)
{
ReleaseResources();
}
}
/// <summary>
/// Releases all resources and cleans up the interop instance too
/// </summary>
public void ReleaseResources()
{
Sandbox.DeinitializeKextSharedMemory(m_sharedMemoryInfo, m_kextConnectionInfo);
m_workerThread.Join();
Sandbox.DeinitializeKextConnection(m_kextConnectionInfo);
}
/// <summary>
/// Starts listening for reports from the kernel extension on a dedicated thread
/// </summary>
private void StartReceivingAccessReports(ulong address, uint port)
{
Sandbox.AccessReportCallback callback = (Sandbox.AccessReport report, int code) =>
{
if (code != Sandbox.ReportQueueSuccessCode)
{
var message = "Kernel extension report queue failed with error: " + code;
throw new BuildXLException(message, ExceptionRootCause.MissingRuntimeDependency);
}
// Update last received timestamp
Volatile.Write(ref m_lastReportReceivedTimestampTicks, DateTime.UtcNow.Ticks);
// Remember the latest enqueue time
Volatile.Write(ref m_reportQueueLastEnqueueTime, report.Statistics.EnqueueTime);
// The only way it can happen that no process is found for 'report.PipId' is when that pip is
// explicitly terminated (e.g., because it timed out or Ctrl-c was pressed)
if (m_pipProcesses.TryGetValue(report.PipId, out var process))
{
// if the process is found, its ProcessId must match the RootPid of the report.
if (process.ProcessId != report.RootPid)
{
m_failureCallback?.Invoke(-1, $"Unexpected PID for Pip {report.PipId:X}: Expected {process.ProcessId}, Reported {report.RootPid}");
}
else
{
process.PostAccessReport(report);
}
}
};
Sandbox.ListenForFileAccessReports(callback, Marshal.SizeOf<Sandbox.AccessReport>(), address, port);
}
/// <inheritdoc />
public bool NotifyUsage(uint cpuUsage, uint availableRamMB)
{
return Sandbox.UpdateCurrentResourceUsage(cpuUsage, availableRamMB, m_kextConnectionInfo);
}
/// <inheritdoc />
public void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process, Task reportCompletion) {}
/// <inheritdoc />
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process)
{
Contract.Requires(process.Started);
Contract.Requires(fam.PipId != 0);
if (!m_pipProcesses.TryAdd(fam.PipId, process))
{
throw new BuildXLException($"Process with PidId {fam.PipId} already exists");
}
var setup = new FileAccessSetup()
{
DllNameX64 = string.Empty,
DllNameX86 = string.Empty,
ReportPath = process.ExecutableAbsolutePath, // piggybacking on ReportPath to pass full executable path
};
using (var wrapper = Pools.MemoryStreamPool.GetInstance())
{
var debugFlags = true;
ArraySegment<byte> manifestBytes = fam.GetPayloadBytes(
loggingContext,
setup,
wrapper.Instance,
timeoutMins: 10, // don't care because on Mac we don't kill the process from the Kext once it times out
debugFlagsMatch: ref debugFlags);
Contract.Assert(manifestBytes.Offset == 0);
var result = Sandbox.SendPipStarted(
processId: process.ProcessId,
pipId: fam.PipId,
famBytes: manifestBytes.Array,
famBytesLength: manifestBytes.Count,
type: Sandbox.ConnectionType.Kext,
info: ref m_kextConnectionInfo);
return result;
}
}
/// <inheritdoc />
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName)
{
return Enumerable.Empty<(string, string)>();
}
/// <inheritdoc />
public void NotifyPipProcessTerminated(long pipId, int processId)
{
Sandbox.SendPipProcessTerminated(pipId, processId, type: Sandbox.ConnectionType.Kext, info: ref m_kextConnectionInfo);
}
/// <inheritdoc />
public void NotifyRootProcessExited(long pipId, SandboxedProcessUnix process)
{
// this implementation keeps track of what the root process is an when it exits,
// so it doesn't need to be notified about it separately
}
/// <inheritdoc />
public bool NotifyPipFinished(long pipId, SandboxedProcessUnix process)
{
if (m_pipProcesses.TryRemove(pipId, out var proc))
{
Contract.Assert(process == proc);
return true;
}
else
{
return false;
}
}
}
}

Просмотреть файл

@ -799,7 +799,7 @@ namespace BuildXL.Processes
{
m_failureCallback = failureCallback;
IsInTestMode = isInTestMode;
Native.Processes.ProcessUtilities.SetNativeConfiguration(SandboxConnection.IsInDebugMode);
Native.Processes.ProcessUtilities.SetNativeConfiguration(UnsandboxedProcess.IsInDebugMode);
}
/// <inheritdoc />
@ -807,9 +807,7 @@ namespace BuildXL.Processes
{
}
/// <summary>
/// Disposes the sandbox kernel extension connection and release the resources in the interop layer, when running tests this can be skipped
/// </summary>
/// <inheritdoc />
public void Dispose()
{
ReleaseResources();

Просмотреть файл

@ -273,7 +273,7 @@ namespace BuildXL.Processes
}
else if (OperatingSystemHelper.IsUnixOS)
{
return new SandboxedProcessUnix(sandboxedProcessInfo, ignoreReportedAccesses: sandboxKind == SandboxKind.MacOsKextIgnoreFileAccesses);
return new SandboxedProcessUnix(sandboxedProcessInfo);
}
else
{

Просмотреть файл

@ -80,9 +80,7 @@ namespace BuildXL.Processes
/// </summary>
public IDetoursEventListener? DetoursEventListener { get; private set; }
/// <summary>
/// A macOS kernel extension connection.
/// </summary>
/// <nodoc />
public ISandboxConnection? SandboxConnection;
/// <summary>

Просмотреть файл

@ -886,6 +886,7 @@ namespace BuildXL.Processes
}
// This assertion doesn't have to hold when using /sandboxKind:macOsKext because some messages may come out of order
// TODO [maly]: do we want to assert this on linux too???
Contract.Assert(OperatingSystemHelper.IsUnixOS || process != null, "Should see a process creation before its accesses (malformed report)");
// If no active ReportedProcess is found (e.g., because it already completed but we are still processing its access reports),

Просмотреть файл

@ -30,8 +30,7 @@ using static BuildXL.Utilities.Core.FormattableStringEx;
namespace BuildXL.Processes
{
/// <summary>
/// Implementation of <see cref="ISandboxedProcess"/> that relies on our kernel extension
/// for Unix-based systems (including MacOS and Linux).
/// Implementation of <see cref="ISandboxedProcess"/> for Unix-based systems (currently: Linux).
/// </summary>
public sealed class SandboxedProcessUnix : UnsandboxedProcess
{
@ -47,8 +46,6 @@ namespace BuildXL.Processes
private IEnumerable<ReportedProcess>? m_survivingChildProcesses;
private PipKextStats? m_pipKextStats;
private long m_processKilledFlag;
private ulong m_processExitTimeNs = ulong.MaxValue;
@ -84,7 +81,7 @@ namespace BuildXL.Processes
private ulong m_sumOfReportCreationTimesUs;
/// <summary>
/// Timeout period for inactivity from the sandbox kernel extension.
/// Timeout period for inactivity from the sandbox connection.
/// </summary>
internal TimeSpan ReportQueueProcessTimeout => SandboxConnection.IsInTestMode
? ReportQueueProcessTimeoutForTests ?? TimeSpan.FromSeconds(100)
@ -172,7 +169,7 @@ namespace BuildXL.Processes
public override int GetLastMessageCount() => m_reports.GetLastMessageCount();
/// <nodoc />
public SandboxedProcessUnix(SandboxedProcessInfo info, bool ignoreReportedAccesses = false)
public SandboxedProcessUnix(SandboxedProcessInfo info)
: base(info)
{
Contract.Requires(info.FileAccessManifest != null);
@ -184,7 +181,6 @@ namespace BuildXL.Processes
ChildProcessTimeout = info.NestedProcessTerminationTimeout;
AllowedSurvivingChildProcessNames = info.AllowedSurvivingChildProcessNames;
ReportQueueProcessTimeoutForTests = info.ReportQueueProcessTimeoutForTests;
IgnoreReportedAccesses = ignoreReportedAccesses;
RootJailInfo = info.RootJailInfo;
m_loggingContext = info.LoggingContext;
m_ptraceRunners = new List<Task<AsyncProcessExecutor>>();
@ -200,7 +196,7 @@ namespace BuildXL.Processes
}
// We cannot create a trace file if we are ignoring file accesses.
m_traceBuilder = info.CreateSandboxTraceFile && !ignoreReportedAccesses
m_traceBuilder = info.CreateSandboxTraceFile
? new SandboxedProcessTraceBuilder(info.FileStorage, info.PathTable)
: null;
@ -215,11 +211,9 @@ namespace BuildXL.Processes
info.FileSystemView,
m_traceBuilder);
var useSingleProducer = !(SandboxConnection.Kind == SandboxKind.MacOsHybrid || SandboxConnection.Kind == SandboxKind.MacOsDetours);
var executionOptions = new ActionBlockSlimConfiguration(
DegreeOfParallelism: 1, // Must be one, otherwise SandboxedPipExecutor will fail asserting valid reports
SingleProducerConstrained: useSingleProducer);
SingleProducerConstrained: true);
m_pendingReports = ActionBlockSlim.Create<AccessReport>(configuration: executionOptions,
(accessReport) =>
@ -260,8 +254,6 @@ namespace BuildXL.Processes
}
}
private bool NeedsShellWrapping() => OperatingSystemHelper.IsMacOS;
/// <inheritdoc />
protected override System.Diagnostics.Process CreateProcess(SandboxedProcessInfo info)
{
@ -273,13 +265,7 @@ namespace BuildXL.Processes
process.StartInfo.WorkingDirectory = "/";
}
if (NeedsShellWrapping())
{
// shell script streamed to stdin
process.StartInfo.FileName = ShellExecutable;
process.StartInfo.Arguments = string.Empty;
}
else if (info.RootJailInfo == null)
if (info.RootJailInfo == null)
{
foreach (var kvp in AdditionalEnvVars(info))
{
@ -375,18 +361,6 @@ namespace BuildXL.Processes
/// </summary>
private async Task OnProcessStartedAsync(SandboxedProcessInfo info)
{
if (NeedsShellWrapping())
{
// The shell wrapper script started, so generate "Process Created" report before the actual pip process starts
// (the rest of the system expects to see it before any other file access reports).
//
// IMPORTANT (macOS-only):
// do this before notifying sandbox kernel extension, because otherwise it can happen that a report
// from the extension is received before the "process created" report is handled, causing
// a "Should see a process creation before its accesses" assertion exception.
ReportProcessCreated();
}
if (OperatingSystemHelper.IsLinuxOS)
{
m_perfCollector?.Start();
@ -421,9 +395,6 @@ namespace BuildXL.Processes
}
}
private string DetoursFile => Path.Combine(Path.GetDirectoryName(AssemblyHelper.GetThisProgramExeLocation()) ?? string.Empty, "libBuildXLDetours.dylib");
private const string DetoursEnvVar = "DYLD_INSERT_LIBRARIES";
/// <inheritdoc />
protected override IEnumerable<ReportedProcess>? GetSurvivingChildProcesses()
{
@ -492,7 +463,7 @@ namespace BuildXL.Processes
LogDebug("GetReportsAsync: Returning reports.");
return IgnoreReportedAccesses ? null : m_reports;
return m_reports;
}
/// <inheritdoc />
@ -521,12 +492,6 @@ namespace BuildXL.Processes
KillAllChildProcesses();
}
if (m_pipKextStats != null)
{
var statsJson = JsonSerializer.Serialize(m_pipKextStats.Value);
LogDebug($"Process Kext Stats: {statsJson}");
}
// If ptrace runners have not finished yet, then do that now
// Completing the task below will make us kill any leftover PTraceRunner processes
KillActivePTraceRunners();
@ -634,73 +599,24 @@ namespace BuildXL.Processes
private async Task FeedStdInAsync(SandboxedProcessInfo info, string? processStdinFileName, bool forceAddExecutionPermission = true)
{
Contract.Requires(info.RootJailInfo == null || !NeedsShellWrapping(), "Cannot run root jail on this OS");
// if no shell wrapping is needed, only feed processStdinFileName (if specified)
if (!NeedsShellWrapping())
if (processStdinFileName != null)
{
if (processStdinFileName != null)
{
string stdinContent =
string stdinContent =
#if NETCOREAPP
await File.ReadAllTextAsync(processStdinFileName);
await File.ReadAllTextAsync(processStdinFileName);
#else
File.ReadAllText(processStdinFileName);
File.ReadAllText(processStdinFileName);
#endif
await Process.StandardInput.WriteAsync(stdinContent);
}
Process.StandardInput.Close();
return;
await Process.StandardInput.WriteAsync(stdinContent);
}
// TODO: instead of generating a bash script (and be exposed to all kinds of injection attacks) we should write a wrapper runner program
string redirectedStdin = processStdinFileName != null ? $" < {ToPathInsideRootJail(processStdinFileName)}" : string.Empty;
// this additional round of escaping is needed because we are flushing the arguments to a shell script
string escapedArguments = (EnsureQuoted(info.Arguments) ?? string.Empty)
.Replace("$", "\\$")
.Replace("`", "\\`");
string cmdLine = $"{CommandLineEscaping.EscapeAsCommandLineWord(info.FileName)} {escapedArguments} {redirectedStdin}";
LogDebug("Feeding stdin");
var lines = new List<string>();
lines.Add("set -e");
if (info.SandboxConnection!.Kind == SandboxKind.MacOsHybrid || info.SandboxConnection.Kind == SandboxKind.MacOsDetours)
{
lines.Add($"export {DetoursEnvVar}={DetoursFile}");
}
foreach (var envKvp in info.SandboxConnection.AdditionalEnvVarsToSet(info, UniqueName))
{
lines.Add($"export {envKvp.Item1}={envKvp.Item2}");
}
lines.Add($"exec {cmdLine}");
if (info.ForceAddExecutionPermission)
{
SetExecutePermissionIfNeeded(info.FileName, throwIfNotFound: false);
}
foreach (string line in lines)
{
await Process.StandardInput.WriteLineAsync(line);
}
LogDebug("Done feeding stdin:" + Environment.NewLine + string.Join(Environment.NewLine, lines));
Process.StandardInput.Close();
return;
}
private IEnumerable<(string, string?)> AdditionalEnvVars(SandboxedProcessInfo info)
{
return info.SandboxConnection!
.AdditionalEnvVarsToSet(info, UniqueName)
.Concat(info.SandboxConnection.Kind == SandboxKind.MacOsHybrid || info.SandboxConnection.Kind == SandboxKind.MacOsDetours
? new (string, string?)[] { (DetoursEnvVar, DetoursFile) }
: Array.Empty<(string, string?)>());
return info.SandboxConnection!.AdditionalEnvVarsToSet(info, UniqueName);
}
internal override void FeedStdErr(SandboxedProcessOutputBuilder builder, string line)
@ -1009,10 +925,6 @@ namespace BuildXL.Processes
if (report.Operation == FileOperation.OpProcessTreeCompleted)
{
if (SandboxConnection is SandboxConnectionKext)
{
m_pipKextStats = report.DecodePipKextStats();
}
m_pendingReports.Complete();
}
else
@ -1040,13 +952,6 @@ namespace BuildXL.Processes
return;
}
if (IgnoreReportedAccesses &&
report.Operation != FileOperation.OpProcessStart &&
report.Operation != FileOperation.OpProcessExit)
{
return;
}
m_reports.ReportFileAccess(ref report, ReportProvider);
}

Просмотреть файл

@ -479,19 +479,6 @@ namespace BuildXL.Processes.Tracing
Message = "Execute AnyBuild bootstrapper: {command}")]
public abstract void ExecuteAnyBuildBootstrapper(LoggingContext context, string command);
[GeneratedEvent(
(int)LogEventId.LogMacKextFailure,
EventGenerators = EventGenerators.LocalOnly,
EventLevel = Level.Error,
Keywords = (int)Keywords.UserMessage,
EventTask = (int)Tasks.PipExecutor,
Message = EventConstants.PipPrefix + "{message}")]
public abstract void LogMacKextFailure(
LoggingContext context,
long pipSemiStableHash,
string pipDescription,
string message);
[GeneratedEvent(
(int)LogEventId.LogAppleSandboxPolicyGenerated,
EventGenerators = EventGenerators.LocalOnly,

Просмотреть файл

@ -138,7 +138,7 @@ namespace BuildXL.Processes.Tracing
BrokeredDetoursInjectionFailed = 10100,
LogDetoursDebugMessage = 10101,
LogAppleSandboxPolicyGenerated = 10102,
LogMacKextFailure = 10103,
// was: LogMacKextFailure = 10103,
LinuxSandboxReportedBinaryRequiringPTrace = 10104,
PTraceRunnerError = 10105,
PTraceSandboxLaunchedForPip = 10106,

Просмотреть файл

@ -220,7 +220,16 @@ namespace BuildXL.Processes
/// <inheritdoc />
public int ProcessId => m_processExecutor.ProcessId;
internal bool DebugLogEnabled => SandboxConnection.IsInDebugMode || ShouldReportFileAccesses;
internal bool DebugLogEnabled => IsInDebugMode || ShouldReportFileAccesses;
/// <nodoc />
/// TODO [maly] - does this belong somewhere else?
public static bool IsInDebugMode { get; } =
#if DEBUG
true;
#else
false;
#endif
/// <inheritdoc />
public virtual void Dispose()

Просмотреть файл

@ -21,7 +21,7 @@ namespace BuildXL.Scheduler
/// Initialize runtime state, optionally apply a filter and schedule all ready pips.
/// Do not start the actual execution.
/// </summary>
bool InitForOrchestrator([NotNull]LoggingContext loggingContext, RootFilter filter = null, SchedulerState state = null, ISandboxConnection sandboxConnectionKext = null);
bool InitForOrchestrator([NotNull]LoggingContext loggingContext, RootFilter filter = null, SchedulerState state = null, ISandboxConnection sandboxConnection = null);
/// <summary>
/// Start running.

Просмотреть файл

@ -229,14 +229,14 @@ namespace BuildXL.Scheduler
private readonly List<Worker> m_workers;
/// <summary>
/// Indicates if processes should be scheduled using the macOS sandbox when BuildXL is executing
/// Indicates if processes should be scheduled using a unix sandbox when BuildXL is executing
/// </summary>
protected virtual bool MacOsSandboxingEnabled =>
protected virtual bool UnixSandboxingEnabled =>
OperatingSystemHelper.IsUnixOS &&
m_configuration.Sandbox.UnsafeSandboxConfiguration.SandboxKind != SandboxKind.None;
/// <summary>
/// A kernel extension connection object for macOS sandboxing
/// A connection to a sandbox (for unix sandboxing)
/// </summary>
[AllowNull]
protected ISandboxConnection SandboxConnection;
@ -5403,7 +5403,7 @@ namespace BuildXL.Scheduler
/// <inheritdoc />
[SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
ISandboxConnection IPipExecutionEnvironment.SandboxConnection => !MacOsSandboxingEnabled ? null : SandboxConnection;
ISandboxConnection IPipExecutionEnvironment.SandboxConnection => !UnixSandboxingEnabled ? null : SandboxConnection;
/// <inheritdoc />
[SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
@ -6154,7 +6154,7 @@ namespace BuildXL.Scheduler
/// <summary>
/// Initialize runtime state, optionally apply a filter and schedule all ready pips
/// </summary>
public bool InitForOrchestrator(LoggingContext loggingContext, RootFilter filter = null, SchedulerState schedulerState = null, ISandboxConnection sandboxConnectionKext = null)
public bool InitForOrchestrator(LoggingContext loggingContext, RootFilter filter = null, SchedulerState schedulerState = null, ISandboxConnection sandboxConnection = null)
{
Contract.Requires(loggingContext != null);
Contract.Assert(!IsInitialized);
@ -6192,8 +6192,7 @@ namespace BuildXL.Scheduler
// This logging context must be set prior to any scheduling, as it might be accessed.
m_executePhaseLoggingContext = pm.LoggingContext;
m_hasFailures = m_hasFailures || InitSandboxConnectionKext(loggingContext, sandboxConnectionKext);
m_hasFailures = m_hasFailures || InitSandboxConnection(loggingContext, sandboxConnection);
// Start workers after scheduler runtime state is successfully established and we have some build to run
if (!HasFailed && IsDistributedOrchestrator)
{
@ -6274,82 +6273,30 @@ namespace BuildXL.Scheduler
}
/// <summary>
/// Initilizes the kernel extension connection if required and reports back success or failure, allowing
/// Initilizes the sandbox connection if required and reports back success or failure, allowing
/// for a graceful terminaton of BuildXL.
/// </summary>
protected virtual bool InitSandboxConnectionKext(LoggingContext loggingContext, ISandboxConnection sandboxConnection = null)
protected virtual bool InitSandboxConnection(LoggingContext loggingContext, ISandboxConnection sandboxConnection = null)
{
if (MacOsSandboxingEnabled)
if (UnixSandboxingEnabled)
{
try
{
// Setup the kernel extension connection so we can potentially execute pips later
// Setup the sandbox connection so we can potentially execute pips later
if (sandboxConnection == null)
{
var config = new SandboxConnectionKext.Config
{
MeasureCpuTimes = m_configuration.Sandbox.MeasureProcessCpuTimes,
FailureCallback = sandboxFailureCallback,
KextConfig = new Sandbox.KextConfig
{
ReportQueueSizeMB = m_configuration.Sandbox.KextReportQueueSizeMb,
EnableReportBatching = m_configuration.Sandbox.KextEnableReportBatching,
#if !PLATFORM_WIN
EnableCatalinaDataPartitionFiltering = OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport,
#endif
ResourceThresholds = new Sandbox.ResourceThresholds
{
CpuUsageBlockPercent = m_configuration.Sandbox.KextThrottleCpuUsageBlockThresholdPercent,
CpuUsageWakeupPercent = m_configuration.Sandbox.KextThrottleCpuUsageWakeupThresholdPercent,
MinAvailableRamMB = m_configuration.Sandbox.KextThrottleMinAvailableRamMB,
}
}
};
switch (m_configuration.Sandbox.UnsafeSandboxConfiguration.SandboxKind)
{
case SandboxKind.LinuxDetours:
{
sandboxConnection = new SandboxConnectionLinuxDetours(sandboxFailureCallback);
break;
}
case SandboxKind.MacOsEndpointSecurity:
case SandboxKind.MacOsDetours:
case SandboxKind.MacOsHybrid:
{
sandboxConnection =
(ISandboxConnection)new SandboxConnection(m_configuration.Sandbox.UnsafeSandboxConfiguration.SandboxKind, isInTestMode: false);
break;
}
default:
{
sandboxConnection = OperatingSystemHelper.IsLinuxOS
? new SandboxConnectionLinuxDetours(sandboxFailureCallback)
: (ISandboxConnection)new SandboxConnectionKext(config);
break;
}
}
if (m_performanceAggregator != null && config.KextConfig.Value.ResourceThresholds.IsProcessThrottlingEnabled())
{
m_performanceAggregator.MachineCpu.OnChange += (aggregator) =>
{
double availableRam = m_performanceAggregator.MachineAvailablePhysicalMB.Latest;
uint cpuUsageBasisPoints = Convert.ToUInt32(Math.Round(aggregator.Latest * 100));
uint availableRamMB = double.IsNaN(availableRam) || double.IsInfinity(availableRam)
? 0
: Convert.ToUInt32(Math.Round(availableRam));
sandboxConnection.NotifyUsage(cpuUsageBasisPoints, availableRamMB);
};
}
// The only unix sandbox supported at the moment:
var sandboxKind = m_configuration.Sandbox.UnsafeSandboxConfiguration.SandboxKind;
Contract.Assert(sandboxKind == SandboxKind.Default || sandboxKind == SandboxKind.LinuxDetours,
$"Unknown Unix sandbox kind: {m_configuration.Sandbox.UnsafeSandboxConfiguration.SandboxKind}");
sandboxConnection = new SandboxConnectionLinuxDetours(sandboxFailureCallback);
}
SandboxConnection = sandboxConnection;
}
catch (BuildXLException ex)
{
Logger.Log.KextFailedToInitializeConnectionManager(loggingContext, (ex.InnerException ?? ex).Message);
Logger.Log.FailedToInitializeSandboxConnection(loggingContext, (ex.InnerException ?? ex).Message);
return true; // Indicates error
}
}
@ -6358,7 +6305,7 @@ namespace BuildXL.Scheduler
void sandboxFailureCallback(int status, string description)
{
Logger.Log.KextFailureNotificationReceived(loggingContext, status, description);
Logger.Log.SandboxFailureNotificationReceived(loggingContext, status, description);
RequestTermination();
}
}
@ -6596,7 +6543,7 @@ namespace BuildXL.Scheduler
m_hasFailures = m_hasFailures || !TryInitSchedulerRuntimeState(loggingContext, schedulerState: null);
InitPipStates(loggingContext);
m_hasFailures = m_hasFailures || InitSandboxConnectionKext(loggingContext);
m_hasFailures = m_hasFailures || InitSandboxConnection(loggingContext);
Contract.Assert(!HasFailed || loggingContext.ErrorWasLogged, "Scheduler encountered errors during initialization, but none were logged.");
return !HasFailed;

Просмотреть файл

@ -478,11 +478,6 @@ namespace BuildXL.Scheduler.Tracing
/// </summary>
public bool ValidateDistribution;
/// <summary>
/// Extra optional fingerprint salt.
/// </summary>
public string RequiredKextVersionNumber;
/// <summary>
/// Whether /unsafe_explicitlyReportDirectoryProbes flag was passed to BuildXL. (disabled by default)
/// </summary>
@ -528,7 +523,6 @@ namespace BuildXL.Scheduler.Tracing
NormalizeReadTimestamps = salts.NormalizeReadTimestamps;
PipWarningsPromotedToErrors = salts.PipWarningsPromotedToErrors;
ValidateDistribution = salts.ValidateDistribution;
RequiredKextVersionNumber = salts.RequiredKextVersionNumber;
ExplicitlyReportDirectoryProbes = salts.ExplicitlyReportDirectoryProbes;
IgnoreDeviceIoControlGetReparsePoint = salts.IgnoreDeviceIoControlGetReparsePoint;
HonorDirectoryCasingOnDisk = salts.HonorDirectoryCasingOnDisk;
@ -564,7 +558,6 @@ namespace BuildXL.Scheduler.Tracing
normalizeReadTimestamps: NormalizeReadTimestamps,
validateDistribution: ValidateDistribution,
pipWarningsPromotedToErrors: PipWarningsPromotedToErrors,
requiredKextVersionNumber: RequiredKextVersionNumber,
explicitlyReportDirectoryProbes: ExplicitlyReportDirectoryProbes,
ignoreDeviceIoControlGetReparsePoint: IgnoreDeviceIoControlGetReparsePoint,
honorDirectoryCasingOnDisk: HonorDirectoryCasingOnDisk
@ -601,7 +594,6 @@ namespace BuildXL.Scheduler.Tracing
writer.Write(MaskUntrackedAccesses);
writer.Write(NormalizeReadTimestamps);
writer.Write(PipWarningsPromotedToErrors);
writer.Write(RequiredKextVersionNumber);
writer.Write(IgnoreFullReparsePointResolving);
writer.Write(ExplicitlyReportDirectoryProbes);
writer.Write(IgnoreDeviceIoControlGetReparsePoint);
@ -632,7 +624,6 @@ namespace BuildXL.Scheduler.Tracing
MaskUntrackedAccesses = reader.ReadBoolean();
NormalizeReadTimestamps = reader.ReadBoolean();
PipWarningsPromotedToErrors = reader.ReadBoolean();
RequiredKextVersionNumber = reader.ReadString();
IgnoreFullReparsePointResolving = reader.ReadBoolean();
ExplicitlyReportDirectoryProbes = reader.ReadBoolean();
IgnoreDeviceIoControlGetReparsePoint = reader.ReadBoolean();

Просмотреть файл

@ -3553,22 +3553,22 @@ namespace BuildXL.Scheduler.Tracing
public abstract void FailedToDuplicateSchedulerFile(LoggingContext context, string sourcePath, string destinationPath, string reason);
[GeneratedEvent(
(int)LogEventId.KextFailedToInitializeConnectionManager,
(int)LogEventId.FailedToInitializeSandboxConnection,
EventGenerators = EventGenerators.LocalOnly,
EventLevel = Level.Error,
EventTask = (ushort)Tasks.Scheduler,
Keywords = (int)(Keywords.UserMessage | Keywords.InfrastructureIssue),
Message = "Failed to initialize the sandbox connection manager: {reason}")]
public abstract void KextFailedToInitializeConnectionManager(LoggingContext context, string reason);
Message = "Failed to initialize the sandbox connection: {reason}")]
public abstract void FailedToInitializeSandboxConnection(LoggingContext context, string reason);
[GeneratedEvent(
(int)LogEventId.KextFailureNotificationReceived,
(int)LogEventId.SandboxFailureNotificationReceived,
EventGenerators = EventGenerators.LocalOnly,
EventLevel = Level.Error,
EventTask = (ushort)Tasks.Scheduler,
Keywords = (int)(Keywords.UserMessage | Keywords.InfrastructureIssue),
Message = "Received unrecoverable error from sandbox connection, please reload the extension and retry, tweaking configuration parameters if necessary (e.g., /numberOfKextConnections, /reportQueueSizeMb). Error code: {errorCode}. Additional description: {description}.")]
public abstract void KextFailureNotificationReceived(LoggingContext context, int errorCode, string description);
Message = "Received unrecoverable error from sandbox connection. Error code: {errorCode}. Additional description: {description}.")]
public abstract void SandboxFailureNotificationReceived(LoggingContext context, int errorCode, string description);
[GeneratedEvent(
(ushort)LogEventId.LowRamMemory,

Просмотреть файл

@ -472,9 +472,9 @@ namespace BuildXL.Scheduler.Tracing
FailedToDuplicateSchedulerFile = 14400,
// Sandbox kernel extension connection manger errors
KextFailedToInitializeConnectionManager = 14500,
KextFailureNotificationReceived = 14501,
// Sandbox connection errors
FailedToInitializeSandboxConnection = 14500,
SandboxFailureNotificationReceived = 14501,
FailedToLoadPipGraphFragment = 14502,
PipCacheLookupStats = 14503,

Просмотреть файл

@ -1,268 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BuildXL.Interop.Unix;
using BuildXL.Processes;
using BuildXL.Utilities.Core;
using BuildXL.Utilities.Configuration;
using BuildXL.Utilities.Instrumentation.Common;
using Test.BuildXL.Executables.TestProcess;
using Test.BuildXL.TestUtilities.Xunit;
using Xunit;
using Xunit.Abstractions;
using static BuildXL.Interop.Unix.Sandbox.AccessReport;
namespace Test.BuildXL.Processes
{
[TestClassIfSupported(requiresMacOperatingSystem: true)]
public class SandboxedProcessMacOsTest : SandboxedProcessTestBase
{
public SandboxedProcessMacOsTest(ITestOutputHelper output)
: base(output) { }
private sealed class Connection : ISandboxConnection
{
public ulong MinReportQueueEnqueueTime { get; set; }
public delegate void ProcessTerminatedHandler(long pipId, int processId);
public event ProcessTerminatedHandler ProcessTerminated;
public TimeSpan CurrentDrought
{
get
{
ulong nowNs = Sandbox.GetMachAbsoluteTime();
ulong minReportTimeNs = MinReportQueueEnqueueTime;
return TimeSpan.FromTicks(nowNs > minReportTimeNs ? (long)((nowNs - minReportTimeNs) / 100) : 0);
}
}
public void Dispose() { }
public bool IsInTestMode => true;
public bool NotifyUsage(uint cpuUsage, uint availableRamMB) { return true; }
public void NotifyPipReady(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process, Task reportCompletion) {}
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) { return true; }
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(SandboxedProcessInfo info, string uniqueName) { return Enumerable.Empty<(string, string)>(); }
public void NotifyPipProcessTerminated(long pipId, int processId) { ProcessTerminated?.Invoke(pipId, processId); }
public void NotifyRootProcessExited(long pipId, SandboxedProcessUnix process) {}
public bool NotifyPipFinished(long pipId, SandboxedProcessUnix process) { return true; }
public void ReleaseResources() { }
public SandboxKind Kind => SandboxKind.MacOsKext;
}
private readonly Connection s_connection = new Connection();
private class ReportInstruction
{
public SandboxedProcessUnix Process;
public Sandbox.AccessReportStatistics Stats;
public int Pid;
public FileOperation Operation;
public string Path;
public bool Allowed;
}
[Fact]
public async Task CheckProcessTreeTimoutOnReportQueueStarvationAsync()
{
var processInfo = CreateProcessInfoWithSandboxConnection(Operation.Echo("hi"));
processInfo.ReportQueueProcessTimeoutForTests = TimeSpan.FromMilliseconds(10);
// Set the last enqueue time to now
s_connection.MinReportQueueEnqueueTime = Sandbox.GetMachAbsoluteTime();
using (var process = CreateAndStartSandboxedProcess(processInfo))
{
// Post nothing to the report queue, and the process tree must be timed out
// after ReportQueueProcessTimeout has been reached.
var result = await process.GetResultAsync();
XAssert.IsTrue(result.Killed, "Expected process to have been killed");
XAssert.IsFalse(result.TimedOut, "Didn't expect process to have timed out");
}
}
[Fact]
public async Task CheckProcessTreeTimoutOnReportQueueStarvationAndStuckRootProcessAsync()
{
var processInfo = CreateProcessInfoWithSandboxConnection(Operation.Block(), measureTimings: true);
processInfo.ReportQueueProcessTimeoutForTests = TimeSpan.FromMilliseconds(10);
// Set the last enqueue time to now
s_connection.MinReportQueueEnqueueTime = Sandbox.GetMachAbsoluteTime();
using (var process = CreateAndStartSandboxedProcess(processInfo))
{
// Post nothing to the report queue, and the process tree must be timed out after ReportQueueProcessTimeout
// has been reached, including the stuck root process
var result = await process.GetResultAsync();
XAssert.IsTrue(result.Killed, "Expected process to have been killed");
XAssert.IsFalse(result.TimedOut, "Didn't expect process to have timed out");
}
}
[SuppressMessage("AsyncUsage", "AsyncFixer04:DisposableObjectUsedInFireForgetAsyncCall", Justification = "The task is awaited before the object is disposed")]
[Fact]
public async Task CheckProcessTreeTimoutOnNestedChildProcessTimeoutWhenRootProcessExitedAsync()
{
var processInfo = CreateProcessInfoWithSandboxConnection(Operation.Echo("hi"));
processInfo.NestedProcessTerminationTimeout = TimeSpan.FromMilliseconds(10);
// Set the last enqueue time to now
s_connection.MinReportQueueEnqueueTime = Sandbox.GetMachAbsoluteTime();
using (var process = CreateAndStartSandboxedProcess(processInfo))
{
var time = s_connection.MinReportQueueEnqueueTime;
var childProcessPath = "/dummy/exe2";
var childProcessPid = process.ProcessId + 1;
// first post some reports indicating that
// - a child process was spawned
// - the main process exited
// (not posting that the child process exited)
var postTask1 = GetContinuouslyPostAccessReportsTask(process, new List<ReportInstruction>
{
new ReportInstruction() {
Process = process,
Operation = FileOperation.OpProcessStart,
Stats = new Sandbox.AccessReportStatistics()
{
EnqueueTime = time + ((ulong) TimeSpan.FromMilliseconds(100).Ticks * 100),
DequeueTime = time + ((ulong) TimeSpan.FromMilliseconds(200).Ticks * 100),
},
Pid = childProcessPid,
Path = childProcessPath,
Allowed = true
},
new ReportInstruction() {
Process = process,
Operation = FileOperation.OpProcessExit,
Stats = new Sandbox.AccessReportStatistics()
{
EnqueueTime = time + ((ulong) TimeSpan.FromMilliseconds(300).Ticks * 100),
DequeueTime = time + ((ulong) TimeSpan.FromMilliseconds(400).Ticks * 100),
},
Pid = process.ProcessId,
Path = "/dummy/exe",
Allowed = true
},
new ReportInstruction() {
Process = process,
Operation = FileOperation.OpKAuthCreateDir,
Stats = new Sandbox.AccessReportStatistics()
{
EnqueueTime = time + ((ulong) TimeSpan.FromMilliseconds(500).Ticks * 100),
DequeueTime = time + ((ulong) TimeSpan.FromMilliseconds(600).Ticks * 100),
},
Pid = childProcessPid,
Path = childProcessPath,
Allowed = true
},
});
// SandboxedProcessMac should decide to kill the process because its child survived;
// when it does that, it will call this callback. When that happens, we must post
// OpProcessTreeCompleted because SandboxedProcessMac will keep waiting for it.
s_connection.ProcessTerminated += (pipId, pid) =>
{
postTask1.GetAwaiter().GetResult();
ContinuouslyPostAccessReports(process, new List<ReportInstruction>
{
new ReportInstruction() {
Process = process,
Operation = FileOperation.OpProcessTreeCompleted,
Stats = new Sandbox.AccessReportStatistics()
{
EnqueueTime = time + ((ulong) TimeSpan.FromMilliseconds(900).Ticks * 100),
DequeueTime = time + ((ulong) TimeSpan.FromMilliseconds(1000).Ticks * 100),
},
Pid = process.ProcessId,
Path = "/dummy/exe",
Allowed = true
}
});
};
var result = await process.GetResultAsync();
await postTask1; // await here as well just to make AsyncFixer happy
XAssert.IsTrue(result.Killed, "Expected process to have been killed");
XAssert.IsFalse(result.TimedOut, "Didn't expect process to have timed out");
XAssert.IsNotNull(result.SurvivingChildProcesses, "Expected surviving child processes");
XAssert.IsTrue(result.SurvivingChildProcesses.Any(p => p.Path == childProcessPath),
$"Expected surviving child processes to contain {childProcessPath}; " +
$"instead it contains: {string.Join(", ", result.SurvivingChildProcesses.Select(p => p.Path))}");
}
}
private SandboxedProcessInfo CreateProcessInfoWithSandboxConnection(Operation op, bool measureTimings = false)
{
var info = ToProcessInfo(ToProcess(op), sandboxConnection: s_connection);
info.MonitoringConfig = measureTimings ? new SandboxedProcessResourceMonitoringConfig(enabled: true, refreshInterval: TimeSpan.FromMilliseconds(10)) : null;
return info;
}
private SandboxedProcessUnix CreateAndStartSandboxedProcess(SandboxedProcessInfo info)
{
var process = new SandboxedProcessUnix(info);
process.Start();
return process;
}
private void ContinuouslyPostAccessReports(SandboxedProcessUnix process, List<ReportInstruction> instructions)
{
Analysis.IgnoreResult(GetContinuouslyPostAccessReportsTask(process, instructions), "fire and forget");
}
private Task GetContinuouslyPostAccessReportsTask(SandboxedProcessUnix process, List<ReportInstruction> instructions)
{
XAssert.IsNotNull(process);
XAssert.IsNotNull(instructions);
return Task.Run(async () =>
{
foreach (var instr in instructions)
{
PostAccessReport(instr.Process, instr.Operation, instr.Stats, instr.Pid, instr.Path, instr.Allowed);
// Advance the minimum enqueue time
s_connection.MinReportQueueEnqueueTime = instr.Stats.EnqueueTime;
// wait a bit before sending the next one
await Task.Delay(100);
}
});
}
private static Sandbox.AccessReport PostAccessReport(SandboxedProcessUnix proc, FileOperation operation, Sandbox.AccessReportStatistics stats,
int pid = 1234, string path = "/dummy/path", bool allowed = true)
{
var report = new Sandbox.AccessReport
{
Operation = operation,
Statistics = stats,
Pid = pid,
PathOrPipStats = Sandbox.AccessReport.EncodePath(path),
Status = allowed ? (uint)FileAccessStatus.Allowed : (uint)FileAccessStatus.Denied
};
proc.PostAccessReport(report);
return report;
}
}
}

Просмотреть файл

@ -1356,7 +1356,7 @@ namespace Test.BuildXL.Processes
/// Tests that FileAccessManifest.ReportProcessArgs option controls whether the
/// command line arguments of all launched processes will be captured and reported
/// </summary>
[TheoryIfSupported(requiresWindowsOrLinuxOperatingSystem: true)] // Sandbox Kext on macOS doesn't capture process cmdline
[TheoryIfSupported(requiresWindowsOrLinuxOperatingSystem: true)]
[InlineData(true)]
[InlineData(false)]
public async Task ReportProcessCommandLineArgs(bool reportProcessArgs)

Просмотреть файл

@ -382,7 +382,7 @@ namespace Test.BuildXL.Scheduler
Contract.Assert(cacheLayer != null);
Contract.Assume(scheduler != null);
scheduler.InitForOrchestrator(LoggingContext, sandboxConnectionKext: GetSandboxConnection());
scheduler.InitForOrchestrator(LoggingContext, sandboxConnection: GetSandboxConnection());
scheduler.Start(LoggingContext);
bool success = scheduler.WhenDone().Result;

Просмотреть файл

@ -34,18 +34,6 @@ namespace Test.BuildXL.Scheduler
public ScheduleRunData RunData { get; } = new ScheduleRunData();
public bool SandboxingWithKextEnabled => OperatingSystemHelper.IsUnixOS;
protected override bool InitSandboxConnectionKext(LoggingContext loggingContext, ISandboxConnection SandboxConnectionKext = null)
{
if (SandboxingWithKextEnabled)
{
SandboxConnection = SandboxConnectionKext ?? XunitBuildXLTest.GetSandboxConnection();
}
return false;
}
private readonly TestPipQueue m_testPipQueue;
public TestScheduler(

Просмотреть файл

@ -67,7 +67,7 @@ namespace Test.BuildXL.Scheduler.Utils
private readonly bool m_allowUnspecifiedSealedDirectories;
private IReadOnlyDictionary<PipId, IReadOnlyCollection<Pip>> m_servicePipToClientProcesses;
private readonly IFileMonitoringViolationAnalyzer m_disabledFileMonitoringViolationAnalyzer = new DisabledFileMonitoringViolationAnalyzer();
private readonly ISandboxConnection m_sandboxConnectionKext;
private readonly ISandboxConnection m_sandboxConnection;
public Dictionary<FileArtifact, string> HostMaterializedFileContents = new Dictionary<FileArtifact, string>();
@ -134,7 +134,7 @@ namespace Test.BuildXL.Scheduler.Utils
Cache = pipCache;
FileAccessAllowlist = fileAccessAllowlist;
m_allowUnspecifiedSealedDirectories = allowUnspecifiedSealedDirectories;
m_sandboxConnectionKext = sandboxConnection;
m_sandboxConnection = sandboxConnection;
if (Cache == null)
{
@ -638,7 +638,7 @@ namespace Test.BuildXL.Scheduler.Utils
SemanticPathExpander IFileContentManagerHost.SemanticPathExpander => PathExpander;
public ISandboxConnection SandboxConnection => m_sandboxConnectionKext;
public ISandboxConnection SandboxConnection => m_sandboxConnection;
public VmInitializer VmInitializer { get; }

Просмотреть файл

@ -145,21 +145,6 @@ namespace BuildXL.FrontEnd.Utilities
{
info.SandboxConnection = new SandboxConnectionLinuxDetours(sandboxConnectionFailureCallback);
}
else if (OperatingSystemHelper.IsMacOS)
{
info.SandboxConnection = new SandboxConnectionKext(
new SandboxConnectionKext.Config
{
FailureCallback = sandboxConnectionFailureCallback,
KextConfig = new Interop.Unix.Sandbox.KextConfig
{
ReportQueueSizeMB = 1024,
#if PLATFORM_OSX
EnableCatalinaDataPartitionFiltering = OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport
#endif
}
});
}
var process = await SandboxedProcessFactory.StartAsync(info, forceSandboxing: true);

Просмотреть файл

@ -19,11 +19,6 @@ namespace BuildXL.Pips.Graph
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")]
public struct ExtraFingerprintSalts : IEquatable<ExtraFingerprintSalts>
{
// For non-Unix platforms, this is an arbitrary fixed value
private static readonly string s_requiredKextVersionNumber = OperatingSystemHelper.IsUnixOS
? KextInfo.RequiredKextVersionNumber
: "0";
private static readonly ExtraFingerprintSalts s_defaultValue = new ExtraFingerprintSalts(
ignoreSetFileInformationByHandle: false,
ignoreZwRenameFileInformation: false,
@ -46,7 +41,6 @@ namespace BuildXL.Pips.Graph
normalizeReadTimestamps: true,
pipWarningsPromotedToErrors: false,
validateDistribution: false,
requiredKextVersionNumber: s_requiredKextVersionNumber,
explicitlyReportDirectoryProbes: false,
ignoreDeviceIoControlGetReparsePoint: true, // Remove this flag from the fingerprint once we validated there are no breaking changes
honorDirectoryCasingOnDisk: false
@ -93,7 +87,6 @@ namespace BuildXL.Pips.Graph
PipFingerprintingVersion.TwoPhaseV2,
fingerprintSalt,
searchPathToolsHash,
requiredKextVersionNumber: s_requiredKextVersionNumber,
config.Sandbox.ExplicitlyReportDirectoryProbes,
config.Sandbox.IgnoreDeviceIoControlGetReparsePoint,
config.Cache.HonorDirectoryCasingOnDisk
@ -137,7 +130,6 @@ namespace BuildXL.Pips.Graph
/// <param name="fingerprintVersion">The fingerprint version.</param>
/// <param name="fingerprintSalt">The extra, optional fingerprint salt.</param>
/// <param name="searchPathToolsHash">The extra, optional salt of path fragments of tool locations for tools using search path enumeration.</param>
/// <param name="requiredKextVersionNumber">The required kernel extension version number.</param>
/// <param name="explicitlyReportDirectoryProbes">Whether /unsafe_explicitlyReportDirectoryProbes was passed to BuildXL.</param>
/// <param name="ignoreDeviceIoControlGetReparsePoint">Whether /ignoreDeviceIoControlGetReparsePoint was passed to BuildXL.</param>
/// <param name="honorDirectoryCasingOnDisk">Whether /honorDirectoryCasingOnDisk was passed to BuildXL.</param>
@ -163,7 +155,6 @@ namespace BuildXL.Pips.Graph
PipFingerprintingVersion fingerprintVersion,
string fingerprintSalt,
ContentHash? searchPathToolsHash,
string requiredKextVersionNumber,
bool explicitlyReportDirectoryProbes,
bool ignoreDeviceIoControlGetReparsePoint,
bool honorDirectoryCasingOnDisk
@ -190,7 +181,6 @@ namespace BuildXL.Pips.Graph
SearchPathToolsHash = searchPathToolsHash;
IgnoreGetFinalPathNameByHandle = ignoreGetFinalPathNameByHandle;
PipWarningsPromotedToErrors = pipWarningsPromotedToErrors;
RequiredKextVersionNumber = requiredKextVersionNumber;
m_calculatedSaltsFingerprint = null;
ExplicitlyReportDirectoryProbes = explicitlyReportDirectoryProbes;
IgnoreDeviceIoControlGetReparsePoint = ignoreDeviceIoControlGetReparsePoint;
@ -311,12 +301,6 @@ namespace BuildXL.Pips.Graph
/// that we don't cache pips that are errors.</remarks>
public bool PipWarningsPromotedToErrors { get; }
/// <summary>
/// The required kernel extension version number. We want to make sure the fingerprints are locked to the
/// sandbox kernel extension version and invalidate if it changes on subsequent builds.
/// </summary>
public string RequiredKextVersionNumber { get; set; }
/// <summary>
/// Whether /unsafe_explicitlyReportDirectoryProbes flag was passed to BuildXL. (disabled by default)
/// </summary>
@ -373,7 +357,6 @@ namespace BuildXL.Pips.Graph
&& SearchPathToolsHash.Value.Equals(SearchPathToolsHash.Value)
&& other.PipWarningsPromotedToErrors == PipWarningsPromotedToErrors
&& other.ValidateDistribution == ValidateDistribution
&& other.RequiredKextVersionNumber.Equals(RequiredKextVersionNumber)
&& other.ExplicitlyReportDirectoryProbes.Equals(ExplicitlyReportDirectoryProbes)
&& other.IgnoreDeviceIoControlGetReparsePoint.Equals(IgnoreDeviceIoControlGetReparsePoint)
&& other.HonorDirectoryCasingOnDisk.Equals(HonorDirectoryCasingOnDisk);
@ -410,7 +393,6 @@ namespace BuildXL.Pips.Graph
hashCode = (hashCode * 397) ^ NormalizeReadTimestamps.GetHashCode();
hashCode = (hashCode * 397) ^ PipWarningsPromotedToErrors.GetHashCode();
hashCode = (hashCode * 397) ^ ValidateDistribution.GetHashCode();
hashCode = (hashCode * 397) ^ (RequiredKextVersionNumber?.GetHashCode() ?? 0);
hashCode = (hashCode * 397) ^ IgnoreFullReparsePointResolving.GetHashCode();
hashCode = (hashCode * 397) ^ ExplicitlyReportDirectoryProbes.GetHashCode();
hashCode = (hashCode * 397) ^ IgnoreDeviceIoControlGetReparsePoint.GetHashCode();
@ -469,11 +451,6 @@ namespace BuildXL.Pips.Graph
fingerprinter.Add(nameof(ValidateDistribution), 1);
}
if (!string.IsNullOrEmpty(RequiredKextVersionNumber))
{
fingerprinter.Add(nameof(RequiredKextVersionNumber), RequiredKextVersionNumber);
}
if (ExplicitlyReportDirectoryProbes)
{
fingerprinter.Add(nameof(ExplicitlyReportDirectoryProbes), 1);

Просмотреть файл

@ -1,50 +0,0 @@
#!/bin/bash
done=false;
exitCode=0;
failures=0;
while [ $done == false ]
do
#listen for opens, writes, and reads and append to file passed as argument ($1)... with walltimestamp of each I/O event
echo "--==Monitoring I/O with DTrace Script==--";
#...................................................
# There are several choices here that need explaining
# (1) We have to separate the scripts for the open probes and the read/write probs.
#
# (1.1) This is because write/reads often cause errors with copyinstr because copyinstr() subroutines cannot read from user addresses which have not yet been touched... so rather we rely on fds, which
# is a structure holding file description information. However..., this structure is incomplete in Mac OSX and replaces the mount path with '??', so these '??' have to be replaced later. We will do this by looking at
# files that are opened previously and use that path.
# (1.2) Open probs do not play well with fds, however copyinstr seems to work just fine... so we use copyinstr to get the path of the file that is opened.
#
# (2) Either root or sudo is assumed. For best performance with sudo, please require no password... (update /etc/sudoers by running "sudo visudo" and set "%admin all=(all) nopasswd: all")
# For sudo, you can enter the password at the prompt at start, but if there is a failure and you are not at keyboard to enter password again, then this script will miss I/O.
#
# (3) "/pid != $pid/" matches on processes other than DTrace. This helps not record any syscalls of DTrace itself and entering some cascade of events caused by DTrace itself.
#
# (4) SIP must be disabled... check "csrutil status" to see if your system has SIP disabled, otherwise Restart->command+R->open terminal->csrutil disable
#...................................................
if [ $EUID -ne 0 ]; then
echo "Running with SUDO"
sudo dtrace -n 'syscall::*open*:entry /pid != $pid/{ printf("[%Y] %s %s", walltimestamp, execname, copyinstr(arg0)) }' -n 'syscall::*write*:entry, syscall::*read*:entry /pid != $pid/ { printf("[%Y] %s", walltimestamp, fds[arg0].fi_pathname) }' >> $1
else
echo "Running as root"
dtrace -n 'syscall::*open*:entry /pid != $pid/{ printf("[%Y] %s %s", walltimestamp, execname, copyinstr(arg0)) }' -n 'syscall::*write*:entry, syscall::*read*:entry /pid != $pid/ { printf("[%Y] %s", walltimestamp, fds[arg0].fi_pathname) }' >> $1
fi
exitCode=$?;
#............................
# DTrace sometimes fails with:
# dtrace: processing aborted: Abort due to systemic unresponsiveness
# , so we check for this and start back up if it fails.
#............................
if [ $exitCode -ne 0 ]; then
done=false;
((failures++));
echo "Restarting with [$failures] failures and exit code of [$exitCode].";
else
done=true;
fi
done
echo "Completed with [$failures] failures and exit code of [$exitCode].";

Просмотреть файл

@ -1,5 +0,0 @@
## Ignore various detritus left behind by common tooling.
.vscode/
bin/
obj/
*.log

Просмотреть файл

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<!-- The estimated duration in seconds of an end to end build, input and output hashing will be adjusted to match this approximately -->
<add key="duration" value="180"/> <!-- Seconds -->
<add key="verbose" value="false" />
<add key="logging" value="false" />
<add key="worker_count" value="5" />
<!-- The observer_pattern specifies a filter for the output directory observation -->
<add key="observer_pattern" value="*" />
<!-- The estimated MD5 file hashes per second estimated by the benchmark run 'iosimulator benchmark inputs.txt', don't edit this manually!-->
<add key="rate" value="200"/>
<!-- The percentage of input files used to run the benchmark on -->
<add key="sample_percentage" value="10"/>
</appSettings>
</configuration>

Просмотреть файл

@ -1,39 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace IOSimulator
{
static class exiting
{
// see https://stackoverflow.com/questions/943635/getting-a-sub-array-from-an-existing-array
public static T[] SubArray<T>(this T[] data, int index, int length)
{
T[] result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
// see https://stackoverflow.com/questions/2070356/find-common-prefix-of-strings
public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> source)
{
var enumerators = source.Select(e => e.GetEnumerator()).ToArray();
try
{
while (enumerators.All(e => e.MoveNext()))
{
yield return enumerators.Select(e => e.Current).ToArray();
}
}
finally
{
Array.ForEach(enumerators, e => e.Dispose());
}
}
}
}

Просмотреть файл

@ -1,73 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace IOSimulator
{
public static class Hashing
{
public static bool HashFileWithPath(string filePath, out string md5Hash, bool verbose = false)
{
try
{
const FileOptions FileFlagNoBuffering = (FileOptions)0x20000000;
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, FileFlagNoBuffering))
using (var md5 = MD5.Create())
{
var hash = md5.ComputeHash(stream);
md5Hash = HexValue(ref hash);
}
}
catch (Exception ex)
{
if (verbose) Console.WriteLine(ex.ToString());
md5Hash = String.Empty;
return false;
}
// Successfully hashed
return true;
}
/// <summary>
/// Creates a MD5 hash from a byte array, returns if the hashing was successful and the hexadecimal representation
/// of the MD5 hash on success
/// </summary>
public static bool HashByteArray(ref byte[] bytes, out string md5Hash, bool verbose = false)
{
try
{
using (var md5 = MD5.Create())
{
var hash = md5.ComputeHash(bytes);
md5Hash = HexValue(ref hash);
}
}
catch (Exception ex)
{
if (verbose) Console.WriteLine(ex.ToString());
md5Hash = String.Empty;
return false;
}
// Successfully hashed
return true;
}
private static string HexValue(ref byte[] bytes)
{
StringBuilder result = new StringBuilder(bytes.Length * 2);
for (int i = 0; i < bytes.Length; i++)
{
result.Append(bytes[i].ToString("X2"));
}
return result.ToString();
}
}
}

Просмотреть файл

@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.1" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,540 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using System.Diagnostics;
using System.Xml;
using System.Runtime.InteropServices;
namespace IOSimulator
{
class IOSimulatorApp
{
private static int rate = 0;
private static int duration = 0;
private static int workerCount = 2;
private static int samplePercentage = 5;
private static ConcurrentDictionary<int, (int, int)> stats = new ConcurrentDictionary<int, (int, int)>();
public static bool Verbose = false;
public static bool Logging = false;
public static string ObserverPattern = "*";
public static string InputHashesLogPath = @"inputHashes.log";
public static string OutputHashesLogPath = @"outputHashes.log";
// Get the application settings
private static bool ReadSettings()
{
try
{
var appSettings = ConfigurationManager.AppSettings;
if (appSettings.Count > 0)
{
foreach (string key in appSettings.AllKeys)
{
switch (key)
{
case "duration":
{
duration = Int32.Parse(appSettings[key] ?? "0");
break;
}
case "rate":
{
// Fallback to 200 hashed files / second
rate = Int32.Parse(appSettings[key] ?? "200");
break;
}
case "worker_count":
{
workerCount = Int32.Parse(appSettings[key] ?? "2");
break;
}
case "observer_pattern":
{
ObserverPattern = appSettings[key] ?? "*";
break;
}
case "sample_percentage":
{
samplePercentage = Int32.Parse(appSettings[key] ?? "5");
break;
}
case "verbose":
{
Verbose = Convert.ToBoolean(appSettings[key] ?? "false");
break;
}
case "logging":
{
Logging = Convert.ToBoolean(appSettings[key] ?? "false");
break;
}
default:
break;
}
}
if (duration > 0)
{
return true;
}
Console.WriteLine(@"No valid configuration settings found, exiting!");
}
}
catch (ConfigurationErrorsException ex)
{
Console.WriteLine(ex.ToString());
}
return false;
}
private enum SectionName
{
None,
Input,
Output
}
private static (int, int, int, int) GetInputAndOutputEntriesCount(string inputFilePath)
{
try
{
// Used to partition the input file
int startLineInputs = 0, endLineInputs = 0, startLineOutputs = 0, currentLine = 1;
using (StreamReader sr = new StreamReader(inputFilePath))
{
SectionName currentSection = SectionName.None;
string line;
while ((line = sr.ReadLine()) != null)
{
// Switch the lists to put inputs and outputs in the correct places
currentSection = line == "[Input]" ? SectionName.Input : line == "[Output]" ? SectionName.Output : currentSection != SectionName.None ? currentSection : SectionName.None;
if (currentSection == SectionName.None)
{
Console.WriteLine("Please add [Input] and [Output] sections to the input file.");
break;
}
if (line == "[Input]")
{
startLineInputs = currentLine + 1;
}
else if (line == "[Output]")
{
endLineInputs = currentLine - 1;
startLineOutputs = currentLine + 1;
}
currentLine++;
}
}
return (startLineInputs, endLineInputs, startLineOutputs, currentLine - 1);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return (0, 0, 0, 0);
}
private static void benchmark(ref (int, int, int, int) inputOutputCounts, string inputFilePath)
{
Console.WriteLine("Running benchmark on {0}% of randomized input sources, please be patient...", samplePercentage);
string configLocation = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "IOSimulator.dll.config");
var numberOfInputs = Math.Max(inputOutputCounts.Item1, inputOutputCounts.Item2) - Math.Min(inputOutputCounts.Item1, inputOutputCounts.Item2);
int filesHashed = 0;
// Benchmark with the configured samplePrecentage of input files, this will be raw sequential read from one thread only
Stopwatch benchmark = Stopwatch.StartNew();
var samples = File.ReadLines(inputFilePath).Skip((inputOutputCounts.Item1 - 1)).Take(((numberOfInputs / 100) * samplePercentage));
foreach (string inputFile in samples)
{
try
{
if (!File.Exists(inputFile)) continue;
Hashing.HashFileWithPath(inputFile, out var hash, Verbose);
}
catch (Exception)
{
// Just continue if file can't be hashed in the benchmark run
continue;
}
filesHashed++;
}
benchmark.Stop();
double benchmarkLengthSeconds = ((double) benchmark.ElapsedMilliseconds) / 1000.0;
double filesHashedPerSecond = ((double) filesHashed) / benchmarkLengthSeconds;
double rateWithPrecision = Math.Ceiling(filesHashedPerSecond);
long rate = (long) rateWithPrecision;
Console.WriteLine("---------------------------------------------------");
Console.WriteLine("Hashed:\t{0} files", filesHashed);
Console.WriteLine("Rate:\t{0} files hashed / second with single worker", rate);
Console.WriteLine("Rate:\t{0} for {1} worker(s)", rate * workerCount, workerCount);
Console.WriteLine("---------------------------------------------------");
Console.Write("Adjusting your {0} please wait... ", configLocation);
// Update the rate key in the app settings automatically
try
{
XmlDocument doc = new XmlDocument();
doc.Load(configLocation);
var ie = doc.SelectNodes("/configuration/appSettings/add").GetEnumerator();
while (ie.MoveNext())
{
if ((ie.Current as XmlNode).Attributes["key"].Value =="rate")
{
var adjustedRate = rate * workerCount;
(ie.Current as XmlNode).Attributes["value"].Value = adjustedRate.ToString();
}
}
doc.Save(configLocation);
Console.Write("Done!\n");
}
catch (Exception ex)
{
Console.WriteLine("Adjusting IOSimulator config failed:\n{0}", ex.ToString());
}
}
private static bool CheckInputOutputEntryCounts(string pathToInputFile, out (int, int, int, int) entryCounts)
{
entryCounts = GetInputAndOutputEntriesCount(pathToInputFile);
if (entryCounts.Item1 == 0 || entryCounts.Item2 == 0 || entryCounts.Item3 == 0 || entryCounts.Item4 == 0)
{
Console.WriteLine("The input file contains no data, exiting!");
return false;
}
return true;
}
static int Main(string[] args)
{
if (args.Length == 2)
{
if (args[0] == "--benchmark")
{
if (!CheckInputOutputEntryCounts(args[1], out var inputOutputCounts))
{
return 1;
}
if (ReadSettings())
{
benchmark(ref inputOutputCounts, args[1]);
return 0;
}
}
Console.WriteLine("Usage: iosimulator [--benchmark] inputfile");
return 1;
}
else if (args.Length == 1)
{
if (ReadSettings())
{
if (rate <= 5 || rate >= 3500)
{
Console.WriteLine(@"Parsed rate is not within threshold (5 >= rate <= 3500) - please re-run the benchmark, exiting!");
return 1;
}
// inputOutputCounts.Item1 are input entry counts, Item2 are output folder counts
if (!CheckInputOutputEntryCounts(args[0], out var inputOutputCounts))
{
return 1;
}
var totalInputs = Math.Max(inputOutputCounts.Item1, inputOutputCounts.Item2) - Math.Min(inputOutputCounts.Item1, inputOutputCounts.Item2) + 1;
var totalOutputs = Math.Max(inputOutputCounts.Item3, inputOutputCounts.Item4) - Math.Min(inputOutputCounts.Item3, inputOutputCounts.Item4) + 1;
// Reset log files
if (File.Exists(InputHashesLogPath))
{
File.Delete(InputHashesLogPath);
}
if (File.Exists(OutputHashesLogPath))
{
File.Delete(OutputHashesLogPath);
}
// Make sure we have input and output hash logs in the case of verbose logging
File.Create(InputHashesLogPath).Close();
File.Create(OutputHashesLogPath).Close();
var tasks = new List<Task>();
// Workload
Action<object> InputHasher = (object context) =>
{
var workerContext = (ValueTuple<string, (int , int), int, CancellationToken>)context;
var inputFilePath = workerContext.Item1;
int startIndex = workerContext.Item2.Item1;
int count = workerContext.Item2.Item2;
CancellationToken ct = workerContext.Item4;
// The throttling heuristic is very simple, after some tests with an SSD and a beefy setup
// reading in and MD5 hashing a byte stream is on average ~550 files / sec or around 1.8 milliseconds
// per file. If we have a lot of input files e.g. 12.000.000 and the build duration is four hours or 14.400 seconds,
// we look at the coefficient of duration divided by number of files, in this case the hashing would need to take 0.0012
// seconds or 1.2 milliseconds per file. Because we said 1.8 milliseconds / file is the best we can do we don't throttle
// and just hash for the entire duration to create I/O load. Another case is we have 1 hour or 3600 seconds and 10000 files,
// so an average of 0.36 seconds or 360 milliseconds to hash per file. Now we need to slow down to not finish early,
// so we look at the difference between calculated hashing and expected hashing, 358 milliseconds and let the
// task sleep for this long in between every file hash. This would end up sleeping 0.358 seconds multiplied by 10000 files,
// so 3580 seconds or ~59:40 minutes. With the remaining 20 seconds multiplied by 550 files / seconds, we do the necessary
// 11000 file hashes.
// IMPORTANT: Running 'iosimulator --benchmark input.txt" does a sequential read and hash benchmark on samplePercentage of
// input files and estimates the throughput rate - default is 200 files hashes / second. Users must run benchmark once!
int elementsProcessedTotal = 0;
int numberOfFailedFiles = 0;
double hashDurationPerFileMilliseconds = ((double) duration) / ((double) count) * 1000.0;
double durationForOneFileMilliseconds = 1000.0 / ((double) rate);
double sleepThreshold =
hashDurationPerFileMilliseconds > durationForOneFileMilliseconds ? (hashDurationPerFileMilliseconds - durationForOneFileMilliseconds) : 0.0;
startIndex = startIndex == 1 ? startIndex : ++startIndex;
var inputs = File.ReadLines(inputFilePath).Skip(startIndex).Take(count);
foreach (string inputFile in inputs)
{
// Cancel if requested
if (ct.IsCancellationRequested)
{
break;
}
try
{
if (!File.Exists(inputFile))
{
numberOfFailedFiles++;
}
else
{
if (Hashing.HashFileWithPath(inputFile, out var hash, Verbose))
{
elementsProcessedTotal++;
if (Logging)
{
using (StreamWriter sw = File.AppendText(InputHashesLogPath))
{
sw.WriteLine("[{0} - Worker #{1}] Hashed: {2} with {3}", DateTime.Now, workerContext.Item3, inputFile, hash);
}
}
}
else
{
numberOfFailedFiles++;
}
}
// Add worker numbers to the statistics
stats.AddOrUpdate(workerContext.Item3, (elementsProcessedTotal, numberOfFailedFiles),
(k, v) => (elementsProcessedTotal, numberOfFailedFiles));
}
catch (Exception ex)
{
if (Verbose) Console.WriteLine(ex.ToString());
continue;
}
Thread.Sleep((int)Math.Ceiling(sleepThreshold));
}
};
Stopwatch endToEndTime = Stopwatch.StartNew();
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Console.WriteLine("Checking input data for correctness...");
// Output directory observers and hashers
List<OutputDirectortWatcher> outputDirectoryWatchers = new List<OutputDirectortWatcher>();
// Skip two entries (headers) and the total number of input paths to get the output paths to observe
var outputDirectories = File.ReadLines(args[0]).Skip((totalInputs + 2)).Take(totalOutputs).ToArray();
// Sort the output directories, look at the first two letter prefix and find the smallest common path
Array.Sort<string>(outputDirectories.ToArray());
int prefixLength = 3; // Check for '/xy' on Unix systems
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
prefixLength = 5; // Check for 'c:\\xy' on Windows systems
}
int lastIndex = 0;
string lastPrefix = outputDirectories[0].Substring(0, prefixLength);
var sanitizedOutputs = new List<string>();
for (int i = 0; i < outputDirectories.Length; ++i)
{
var prefix = outputDirectories[i].Substring(0, prefixLength);
// Make sure to at least get one directory
if (lastPrefix != prefix || ((i+1) == outputDirectories.Length && lastIndex == 0))
{
var subset = outputDirectories.SubArray(lastIndex, ((lastIndex == 0 && i == 0) ? outputDirectories.Length : i - lastIndex));
lastIndex = i;
string match = string.Join(Path.DirectorySeparatorChar.ToString(),
subset.Select(s => s.Split(Path.DirectorySeparatorChar).AsEnumerable()).Transpose()
.TakeWhile(s => s.All(d => d == s.First())).Select(s => s.First()));
if (match.Length > 0) sanitizedOutputs.Add(match);
}
lastPrefix = prefix;
}
foreach (string outputPath in sanitizedOutputs)
{
try
{
// Don't observe files and deleted directories
FileAttributes attr = File.GetAttributes(outputPath);
if (!attr.HasFlag(FileAttributes.Directory)) continue;
if (!Directory.Exists(outputPath)) continue;
var watcher = new OutputDirectortWatcher(outputPath);
outputDirectoryWatchers.Add(watcher);
watcher.Start();
}
catch (Exception ex)
{
if (Verbose) Console.WriteLine(ex.ToString());
}
}
// Input hashers
int numberOfInputsPerWorker = totalInputs / workerCount;
for (int count = numberOfInputsPerWorker, index = 0, i = 0; i < workerCount; i++)
{
// Give the remainder of the work to the last worker
if (i == (workerCount - 1))
{
count += totalInputs % workerCount;
}
// Split work
tasks.Add(Task.Factory.StartNew(InputHasher, (args[0], ((index == 0 ? 1 : index), count), i, token), token));
index += numberOfInputsPerWorker;
}
try
{
Console.WriteLine("--------------------------------------------------------");
Console.WriteLine("NOTE: PLEASE RUN THE BENCHMARK ONCE TO ADJUST I/O RATES!");
Console.WriteLine("--------------------------------------------------------");
Console.WriteLine("Input files:\t\t{0}", totalInputs);
Console.WriteLine("Input workers:\t\t{0}", workerCount);
Console.WriteLine("Output directories:\t{0}", sanitizedOutputs.Count);
Console.WriteLine("Verbose:\t\t{0}", Verbose);
Console.WriteLine("Logging enabled:\t{0}", Logging);
Console.WriteLine("Hash rate:\t\t{0} files / second", rate);
Console.WriteLine("Duration:\t\t{0} seconds", duration);
Console.WriteLine("--------------------------------------------------------\n");
Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) {
tokenSource.Cancel();
tokenSource.Dispose();
Console.WriteLine("Execution canceled, writing out preliminary results...");
e.Cancel = true;
};
// Wait for all the tasks to finish or timeout after the configured max duration
// This also kill the output observers
tokenSource.CancelAfter(duration * 1000);
Task.WaitAll(tasks.ToArray(), duration * 1000, token);
}
catch (OperationCanceledException)
{
// This can happen if cancellation happens quicker than the wait threshold, ingore it
}
catch (AggregateException e)
{
foreach (Exception err in e.InnerExceptions)
{
if (Verbose) Console.WriteLine("\n---\n{0}", err.ToString());
}
return 1;
}
finally
{
tokenSource.Dispose();
}
printStats(ref endToEndTime, totalInputs, ref outputDirectoryWatchers);
}
}
else
{
Console.WriteLine("Usage: iosimulator [--benchmark] inputfile");
return 1;
}
return 0;
}
private static void printStats(ref Stopwatch endToEndTime, int totalInputs, ref List<OutputDirectortWatcher> outputDirectoryWatchers)
{
long endToEndMilliseconds = endToEndTime.ElapsedMilliseconds;
endToEndTime.Stop();
Console.WriteLine("Results:\n--------------------------------------------------------");
for (int i=0; i < workerCount; i++)
{
ValueTuple<int, int> workerStats;
if (stats.TryGetValue(i, out workerStats))
{
Console.WriteLine("Worker #{0} hashed:\t{1} files", i, workerStats.Item1);
}
}
var numberOfFilesHashed = stats.Sum(t => t.Value.Item1);
Console.WriteLine("Inputs hashed:\t\t{0} ({1}%)", numberOfFilesHashed, (numberOfFilesHashed / (totalInputs / 100)));
Console.WriteLine("Failed input files:\t{0}", stats.Sum(t => t.Value.Item2));
Console.WriteLine("Outputs hashed:\t\t{0} (Creates / Updates)", outputDirectoryWatchers.Sum(w => w.filesHashed));
Console.WriteLine("End to end time:\t{0} seconds", endToEndMilliseconds / 1000);
Console.WriteLine("--------------------------------------------------------");
}
}
}

Просмотреть файл

@ -1,100 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace IOSimulator
{
/// <summary>
/// An OutputDirectoryWatcher is initialized with a path to a directory and uses a file watcher to
/// observe any directory or file changes within that directory that match a given pattern. Also calculates
/// MD5 hashes for the file change events only to simulate I/O load.
/// </summary>
public class OutputDirectortWatcher
{
private FileSystemWatcher watcher;
private List<string> hashes;
public int filesHashed;
public OutputDirectortWatcher(string outputDirectoryPath)
{
FileAttributes attr = File.GetAttributes(outputDirectoryPath);
if (!attr.HasFlag(FileAttributes.Directory))
{
throw new Exception("Only paths to directories should be used to initialize an OutputDirectortWatcher!");
}
hashes = new List<string>();
filesHashed = 0;
watcher = new FileSystemWatcher();
watcher.Path = outputDirectoryPath;
watcher.IncludeSubdirectories = true;
watcher.NotifyFilter =
NotifyFilters.CreationTime |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.FileName |
NotifyFilters.DirectoryName;
watcher.Filter = IOSimulatorApp.ObserverPattern;
}
public void Start()
{
watcher.Created += new FileSystemEventHandler(this.OnChanged);
watcher.Changed += new FileSystemEventHandler(this.OnChanged);
// We currently just track file creations and changes in the specified directory and calculate hashes for those
// enable if more is needed
// watcher.Deleted += new FileSystemEventHandler(this.OnDeleted);
// watcher.Renamed += new RenamedEventHandler(this.OnRenamed);
watcher.EnableRaisingEvents = true;
}
// Define the event handlers.
public void OnChanged(object source, FileSystemEventArgs e)
{
try
{
FileAttributes attr = File.GetAttributes(e.FullPath);
if (!attr.HasFlag(FileAttributes.Directory))
{
// Go hash the output change
Task.Run(async () =>
{
using (FileStream SourceStream = File.Open(e.FullPath, FileMode.Open))
{
var result = new byte[SourceStream.Length];
await SourceStream.ReadAsync(result, 0, (int)SourceStream.Length);
if (Hashing.HashByteArray(ref result, out var hash, verbose: IOSimulatorApp.Verbose))
{
filesHashed++;
if (IOSimulatorApp.Logging)
{
using (StreamWriter sw = File.AppendText(IOSimulatorApp.OutputHashesLogPath))
{
sw.WriteLine("[{0} - Observer] Hashed: {1} with {2}", DateTime.Now, e.FullPath, hash);
}
}
}
}
});
}
}
catch (Exception ex)
{
if (IOSimulatorApp.Verbose) Console.WriteLine(ex.ToString());
}
}
}
}

Просмотреть файл

@ -1,91 +0,0 @@
**The source code is provided as is and was put together quickly for testing purposes only!**
## Introduction
This application is used to simulate possible BuildXL I/O load on macOS. The scenario followed here would be similar to a BuildXL "clean build", where all input files have to be read-in and hashed and also all output files generated by the BuildXL build have to be analyzed and hashed too.
The application can be ran alongside an existing build and simulates I/O load depending on several config settings and the main input file.
##Input file
The input file is used to describe a list of inputs specified by absolute paths and a list of output directories to be observed while the application is executing. The format is as follows, from an imaginary `input.txt`:
```[Input]
/Users/John/Work/Project/src/a.cpp
/Users/John/Work/Project/src/b.cpp
/Users/John/Work/Project/src/SomeSubfolder/a.cpp
[Output]
/Users/John/Work/Project/out/libraries
/Users/John/Work/Project/out/symbols
/Users/John/Work/Project/out/pictures
```
Here we have defined 3 input files that will be hashed over the duration of the application execution and 3 output folders that will be monitored and if file creations or updates happen, hash those too! Please don't forget to annotate the input and output files with the appropriate section header otherwise the application will not start.
## Benchmark
Because the application tries to approximate possible BuildXL build I/O load, it is important to analyze the machine prior to running it alongside a build. The application has to estimate how many hashes (MD5) it can calculate per second. To do this, it takes a percentage of the input files specified and can run a benchmark. After the benchmark finishes, the application updates its own config file to reflect the estimated hash rate. **Important: do this before the first run!**
```console
./IOSimulator --benchmark input.txt
Running benchmark on 50% of randomized input sources, please be patient...
----------------------------------------------
Hashed: 65299 files
Rate: 699.31245716244 files hashed / second!
----------------------------------------------
Adjusting your /Users/krisso/Downloads/iosimulator/IOSimulator.dll.config please wait... Done!
```
Note: The percentage of files used as a sample from the input can be adjusted in the `IOSimulator.dll.config` file:
`<add key="sample_percentage" value="50" />`
## Execution
Now that the benchmark has been done, the application can be additionally tweaked through settings changes or executed along-side a build or tool, e.g.
```console
./IOSimulator input.txt
--------------------------------------------------------
NOTE: PLEASE RUN THE BENCHMARK ONCE TO ADJUST I/O RATES!
--------------------------------------------------------
Input files: 130632
Input workers: 5
Output directories: 1
Logging enabled: False
Hash rate: 700 files / second
Duration: 30 seconds
--------------------------------------------------------
Results:
--------------------------------------------------------
Worker #0 hashed: 15301 files
Worker #1 hashed: 11697 files
Worker #2 hashed: 17966 files
Worker #3 hashed: 10802 files
Worker #4 hashed: 20329 files
Inputs hashed: 76095 (58%)
Outputs hashed: 0 (Creates / Updates)
Failed input files: 0
End to end time: 30 seconds
--------------------------------------------------------
```
In this example the duration was configured to be 30 seconds and the benchmark prior to this run estimated a throughput of 700 files hashes / second.
Note: The execution duration can be adjusted inside of `IOSimulator.dll.config` file and the changes will be reflected on the next run.
`<add key="duration" value="30"/> <!-- Seconds -->`
You can also adjust verbosity `verbose`, enable logging `logging`, adjust the concurrent worker count `worker_count` and define a pattern for the output observers `observer_pattern`. All of those values have sensible defaults:
```xml
<add key="verbose" value="false" />
<add key="logging" value="false" />
<add key="worker_count" value="5" />
<add key="observer_pattern" value="*" />
```
If you take a look at the results you will see that in this particular run the execution was canceled after 30 seconds, exactly as specified by the duration setting. The application tries to throttle the hashing execution through simple heuristics too, so the duration should always be met even when there are almost no input files and a long time period is configured and so on.
Some files can't be hashed so the `Inputs hashed` must always correspond to the input files specified. Common issues can be unauthorized accesses or ACLs. IOSimulator skips those files without failing and shows a simple indicator of the number of files that failed. If more information is necessary, the verbose flag in the configuration can help.

Просмотреть файл

@ -1,2 +0,0 @@
#!/bin/sh
dotnet publish -c Release

Просмотреть файл

@ -1,8 +0,0 @@
[Input]
/Users/John/Work/Project/src/a.cpp
/Users/John/Work/Project/src/b.cpp
/Users/John/Work/Project/src/SomeSubfolder/a.cpp
[Output]
/Users/John/Work/Project/out/libraries
/Users/John/Work/Project/out/symbols
/Users/John/Work/Project/out/pictures

Просмотреть файл

@ -1,607 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BuildXL.Interop.Unix;
using BuildXL.Processes;
using BuildXL.ProcessPipExecutor;
using BuildXL.ToolSupport;
using BuildXL.Utilities;
using BuildXL.Utilities.Core;
using BuildXL.Utilities.Configuration;
using BuildXL.Utilities.CrashReporting;
using BuildXL.Utilities.Instrumentation.Common;
namespace BuildXL.SandboxExec
{
/// <summary>
/// Executes a supplied executable and its arguments within the BuildXL sandbox and outputs all observed file accesses
/// </summary>
public class SandboxExecRunner : ISandboxedProcessFileStorage
{
private const string AccessOutput = "accesses.out";
private static readonly double s_defaultProcessTimeOut = TimeSpan.FromMinutes(10).TotalSeconds;
private static readonly double s_defaultProcessTimeOutMax = TimeSpan.FromHours(4).TotalSeconds;
private static readonly LoggingContext s_loggingContext = new LoggingContext("BuildXL.SandboxExec");
private static CrashCollectorMacOS s_crashCollector;
/// <summary>
/// Configuration options for this tool.
/// </summary>
public readonly struct Options
{
/// <summary>
/// Defaults
/// </summary>
public static readonly Options Defaults =
new Options(
verbose: false,
logToStdOut: false,
enableReportBatching: false,
reportQueueSizeMB: 1024,
enableTelemetry: true,
processTimeout: (int) s_defaultProcessTimeOut,
trackDirectoryCreation: false,
sandboxKind: SandboxKind.MacOsKext);
/// <summary>
/// When set to true, the output contains long instead of short description of reported accesses.
/// </summary>
public bool Verbose { get; }
/// <summary>
/// When set to true, the observed output goes to stdout, otherwise to AccessOutput
/// </summary>
public bool LogToStdOut { get; }
/// <summary>
/// Size of the kernel report queue in MB
/// </summary>
public uint ReportQueueSizeMB { get; }
/// <summary>
/// Tells the kext whether to batch reports or not.
/// </summary>
public bool EnableReportBatching { get; }
/// <summary>
/// When set to true, record statistics about execution time, deduping and send logs to remote telemetry.
/// </summary>
public bool EnableTelemetry { get; }
/// <summary>
/// Number of seconds before the parent process is timed out
/// </summary>
public int ProcessTimeout { get; }
/// <summary>
/// When set, directory creation is reported as "Write"; otherwise, it is reported as "Read"
/// </summary>
public bool TrackDirectoryCreation { get; }
/// <summary>
/// Sandboxing kind indicates which sandbox implementation to choose for observing I/O events
/// </summary>
public SandboxKind SandboxKindUsed { get; }
/// <nodoc />
public Options(bool verbose, bool logToStdOut, uint reportQueueSizeMB, bool enableTelemetry, int processTimeout, bool trackDirectoryCreation, bool enableReportBatching, SandboxKind sandboxKind)
{
Verbose = verbose;
LogToStdOut = logToStdOut;
ReportQueueSizeMB = reportQueueSizeMB;
EnableReportBatching = enableReportBatching;
EnableTelemetry = enableTelemetry;
ProcessTimeout = processTimeout;
TrackDirectoryCreation = trackDirectoryCreation;
SandboxKindUsed = sandboxKind;
}
}
/// <summary>
/// Builder for <see cref="Options"/>
/// </summary>
public class OptionsBuilder
{
/// <nodoc />
public bool Verbose;
/// <nodoc />
public bool LogToStdOut;
/// <nodoc />
public uint ReportQueueSizeMB;
/// <nodoc />
public bool EnableReportBatching;
/// <nodoc />
public bool EnableTelemetry;
/// <nodoc />
public int ProcessTimeout;
/// <nodoc />
public bool TrackDirectoryCreation;
/// <nodoc />
public SandboxKind SandboxKindUsed;
/// <nodoc />
public OptionsBuilder() { }
/// <nodoc />
public OptionsBuilder(Options opts)
{
Verbose = opts.Verbose;
LogToStdOut = opts.LogToStdOut;
ReportQueueSizeMB = opts.ReportQueueSizeMB;
EnableReportBatching = opts.EnableReportBatching;
EnableTelemetry = opts.EnableTelemetry;
ProcessTimeout = opts.ProcessTimeout;
TrackDirectoryCreation = opts.TrackDirectoryCreation;
SandboxKindUsed = opts.SandboxKindUsed;
}
/// <nodoc />
public Options Finish() => new Options(Verbose, LogToStdOut, ReportQueueSizeMB, EnableTelemetry, ProcessTimeout, TrackDirectoryCreation, EnableReportBatching, SandboxKindUsed);
}
private readonly Options m_options;
private readonly ISandboxConnection m_sandboxConnection;
private PathTable m_pathTable;
/// <summary>
/// For unit tests only.
/// </summary>
public SandboxExecRunner(ISandboxConnection connection)
{
m_options = Options.Defaults;
m_sandboxConnection = connection;
}
/// <nodoc />
public SandboxExecRunner() : this(Options.Defaults) { }
/// <nodoc />
public SandboxExecRunner(Options options)
{
m_options = options;
s_crashCollector = OperatingSystemHelper.IsMacOS
? new CrashCollectorMacOS(new[] { CrashType.SandboxExec, CrashType.Kernel })
: null;
if (!OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport &&
(m_options.SandboxKindUsed == SandboxKind.MacOsEndpointSecurity || m_options.SandboxKindUsed == SandboxKind.MacOsHybrid))
{
throw new NotSupportedException("EndpointSecurity and Hybrid sandbox types can't be run on system older than macOS Catalina (10.15+).");
}
// m_sandboxConnection
if (OperatingSystemHelper.IsLinuxOS)
{
m_sandboxConnection = new SandboxConnectionLinuxDetours(FailureCallback);
}
else if (OperatingSystemHelper.IsMacOS)
{
if (m_options.SandboxKindUsed == SandboxKind.MacOsKext)
{
m_sandboxConnection = new SandboxConnectionKext(new SandboxConnectionKext.Config
{
FailureCallback = FailureCallback,
KextConfig = new Sandbox.KextConfig
{
ReportQueueSizeMB = m_options.ReportQueueSizeMB,
EnableReportBatching = m_options.EnableReportBatching,
#if PLATFORM_OSX
EnableCatalinaDataPartitionFiltering = OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport
#endif
}
});
}
else
{
m_sandboxConnection = new SandboxConnection(m_options.SandboxKindUsed, isInTestMode: false);
}
}
else
{
m_sandboxConnection = null;
}
}
private void FailureCallback(int status, string description)
{
m_sandboxConnection.Dispose();
throw new SystemException($"Received unrecoverable error from the sandbox (Code: {status.ToString("X")}, Description: {description}), please reload the extension and retry.");
}
/// <summary>
/// Splits given command line arguments into tool arguments and process arguments.
///
/// Tool arguments are optional. When provided, they must be specified first,
/// then followed by "--" and then followed by process arguments.
/// </summary>
public static (Options toolOptions, string[] procArgs) ParseArgs(string[] args)
{
var separatorArgIdx = IndexOf(args, "--");
var toolArgs = separatorArgIdx == -1 ? new string[0] : args.Take(separatorArgIdx).ToArray();
var procArgs = args.Skip(separatorArgIdx + 1).ToArray();
Options toolOptions = ParseOptions(toolArgs);
return (toolOptions, procArgs);
}
/// <nodoc />
public static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
{
HandleUnhandledFailure(eventArgs.ExceptionObject as Exception);
};
var parsedArgs = ParseArgs(args);
Environment.ExitCode = RunTool(parsedArgs.toolOptions, parsedArgs.procArgs);
}
private static void HandleUnhandledFailure(Exception exception)
{
PrintToStderr(exception.Message ?? exception.InnerException.Message);
// Log the exception to telemetry
Tracing.Logger.Log.SandboxExecCrashReport(s_loggingContext, s_loggingContext.Session.ToString(), exception.ToString());
Telemetry.TelemetryShutdown();
Environment.Exit(ExitCode.FromExitKind(ExitKind.InternalError));
}
private static int RunTool(Options options, string[] procArgs)
{
if (procArgs.Length < 1)
{
var macOSUsageDescription = OperatingSystemHelper.IsUnixOS ? $" [/{ArgReportQueueSizeMB}:<1-1024>] [/{ArgEnableReportBatching}[+,-]]" : "";
PrintToStderr($"Usage: SandboxExec [[/{ArgVerbose}[+,-]] [/{ArgLogToStdOut}[+,-]] [/{ArgProcessTimeout}:seconds] [/{ArgTrackDirectoryCreation}] [/{ArgEnableStatistics}[+,-]] [/{ArgSandboxKindUsed}:MacOsKext]{macOSUsageDescription} --] executable [arg1 arg2 ...]");
return 1;
}
if (!Path.IsPathRooted(procArgs[0]))
{
PrintToStderr("The path to the executable must be specified as an absolute path. Exiting.");
return 1;
}
Telemetry.TelemetryStartup(options.EnableTelemetry);
CleanupOutputs();
var instance = new SandboxExecRunner(options);
SandboxedProcessResult result;
Stopwatch sandboxingTime;
var overallTime = Stopwatch.StartNew();
using (var process = ExecuteAsync(instance, procArgs, workingDirectory: Directory.GetCurrentDirectory()).GetAwaiter().GetResult())
{
sandboxingTime = Stopwatch.StartNew();
result = process.GetResultAsync().GetAwaiter().GetResult();
sandboxingTime.Stop();
PrintToStdout($"Process {procArgs[0]}:{process.ProcessId} exited with exit code: {result.ExitCode}");
}
var dedupeTime = Stopwatch.StartNew();
var accessReportCount = result.FileAccesses.Count;
// Dedupe reported file accesses
var distinctAccessReports = instance.DedupeAccessReports(
result.FileAccesses,
result.ExplicitlyReportedFileAccesses,
result.AllUnexpectedFileAccesses);
dedupeTime.Stop();
var outputTime = Stopwatch.StartNew();
if (options.LogToStdOut)
{
foreach (var report in distinctAccessReports)
{
PrintToStdout(report);
}
}
else
{
var path = Path.Combine(Directory.GetCurrentDirectory(), AccessOutput);
try
{
File.WriteAllLines(path, distinctAccessReports);
}
catch (IOException ex)
{
PrintToStderr($"Could not write file access report file to {path}. Got excecption instead: " + ex.ToString());
}
}
outputTime.Stop();
var disposeTime = Stopwatch.StartNew();
if (instance.m_sandboxConnection != null)
{
// Take care of releasing sandbox kernel extension resources on macOS
instance.m_sandboxConnection.Dispose();
}
disposeTime.Stop();
overallTime.Stop();
if (options.EnableTelemetry)
{
PrintToStdout("\nTime unit is: ms\n");
PrintToStdout($"Overall execution time: {overallTime.ElapsedMilliseconds}");
PrintToStdout($"Time spent executing process with sandboxing: {sandboxingTime.ElapsedMilliseconds}");
PrintToStdout($"Time spent deduping access reports: {dedupeTime.ElapsedMilliseconds}");
PrintToStdout($"Time spent outputting access reports: {outputTime.ElapsedMilliseconds}");
PrintToStdout($"Time spent disposing kernel connection: {disposeTime.ElapsedMilliseconds}");
PrintToStdout($"Number of access reports before deduping: {accessReportCount}");
PrintToStdout($"Number of access reports after deduping: {distinctAccessReports.Count}");
PrintToStdout("");
PrintToStdout("Statistics ::");
foreach (KeyValuePair<string, long> kvp in SandboxedProcessFactory.Counters.AsStatistics())
{
PrintToStdout($"{kvp.Key} = {kvp.Value}");
}
PrintToStdout("");
}
CollectAndUploadCrashReports(options.EnableTelemetry);
Telemetry.TelemetryShutdown();
return result.ExitCode;
}
/// <summary>
/// Dedupes a variable list of reported file accesses and returns the result as an enumerable collection of strings containing
/// the short description of all the input file access reports
/// </summary>
/// <param name="accessReports">Variable number of sets of reported file accesses</param>
/// <returns>A deduped List of strings containing the short description of all file access reports</returns>
public List<string> DedupeAccessReports(params ISet<ReportedFileAccess>[] accessReports)
{
return accessReports
.SelectMany(set => set.Select(RenderReport))
.Distinct()
.ToList();
}
private static long s_pipIdCounter = 1;
/// <summary>
/// Creates a SandboxedProcessInfo object that can be used to run a target program within the BuildXL Sandbox and
/// forces the Sandbox to explicitly report all file accesses observed
/// </summary>
/// <param name="processFileName">The fully qualifying file name of the process to run inside the sandbox</param>
/// <param name="instance">A SandboxExecRunner instance</param>
/// <returns>SandboxedProcessInfo object that is configured to explicitly report all observed file accesses</returns>
public static SandboxedProcessInfo CreateSandboxedProcessInfo(string processFileName, SandboxExecRunner instance)
{
var sandboxProcessInfo = new SandboxedProcessInfo(
new PathTable(),
fileStorage: instance,
fileName: processFileName,
disableConHostSharing: false,
sandboxConnection: instance.m_sandboxConnection,
loggingContext: s_loggingContext);
sandboxProcessInfo.PipDescription = processFileName;
sandboxProcessInfo.StandardOutputEncoding = Encoding.UTF8;
sandboxProcessInfo.StandardOutputObserver = PrintToStdout;
sandboxProcessInfo.StandardErrorEncoding = Encoding.UTF8;
sandboxProcessInfo.StandardErrorObserver = PrintToStderr;
// track directory creation
sandboxProcessInfo.FileAccessManifest.EnforceAccessPoliciesOnDirectoryCreation = instance.m_options.TrackDirectoryCreation;
// Enable explicit file access reporting
sandboxProcessInfo.FileAccessManifest.ReportFileAccesses = true;
sandboxProcessInfo.FileAccessManifest.ReportUnexpectedFileAccesses = true;
sandboxProcessInfo.FileAccessManifest.MonitorNtCreateFile = true;
sandboxProcessInfo.FileAccessManifest.IgnoreFullReparsePointResolving = false;
sandboxProcessInfo.FileAccessManifest.FailUnexpectedFileAccesses = false;
sandboxProcessInfo.FileAccessManifest.PipId = Interlocked.Increment(ref s_pipIdCounter);
return sandboxProcessInfo;
}
/// <summary>
/// Runs a target process inside the sandbox asynchronously
/// </summary>
/// <param name="instance">A SandboxExecRunner instance</param>
/// <param name="exec">The programs full command line including its arguments</param>
/// <param name="workingDirectory">Working directory in which to start the process</param>
/// <returns>Task that will execute the process and its arguments inside the BuildXL Sandbox</returns>
public static Task<ISandboxedProcess> ExecuteAsync(SandboxExecRunner instance, string[] exec, string workingDirectory)
{
var processFileName = exec[0];
var processInfo = CreateSandboxedProcessInfo(processFileName, instance);
processInfo.Timeout = TimeSpan.FromSeconds(instance.m_options.ProcessTimeout);
processInfo.Arguments = ExtractAndEscapeCommandLineArguments(exec);
processInfo.WorkingDirectory = workingDirectory;
processInfo.EnvironmentVariables = BuildParameters.GetFactory(null).PopulateFromEnvironment();
instance.m_pathTable = processInfo.PathTable;
return SandboxedProcessFactory.StartAsync(processInfo, forceSandboxing: true);
}
/// <summary>
/// Sanitizes and returns the process command line arguments
/// </summary>
/// <param name="exec">The complete process command line list</param>
/// <returns>Space separated execution arguments for the process to be run inside the Sandbox, escaped with single quotes</returns>
public static string ExtractAndEscapeCommandLineArguments(string[] exec)
{
var args = exec.Skip(1);
// Take care of escaping process arguments on Unix systems via wrapping them in single quotation marks and escpaing existing single quotes to maintain original intent
return string.Join(" ", OperatingSystemHelper.IsUnixOS ? args.Select(s => "'" + s.Replace("'", "'\\''") + "'") : args);
}
/// <summary>
/// Deletes report files from previous runs if they exist
/// </summary>
private static void CleanupOutputs()
{
// SandboxExec redirects stdout or stderr output to a file for every run as defined in SandboxedProcessFile (out.txt and err.txt repsectively)
// Clean up any files from previous runs if they exist so the new output contains only information of the current run
var stdout = Path.Combine(Directory.GetCurrentDirectory(), SandboxedProcessFile.StandardOutput.DefaultFileName());
var stderr = Path.Combine(Directory.GetCurrentDirectory(), SandboxedProcessFile.StandardError.DefaultFileName());
try
{
File.Delete(stdout);
File.Delete(stderr);
}
#pragma warning disable ERP022 // Unobserved exception in generic exception handler
catch
{
}
#pragma warning restore ERP022 // Unobserved exception in generic exception handler
}
/// <nodoc />
public string GetFileName(SandboxedProcessFile file)
{
return Path.Combine(Directory.GetCurrentDirectory(), file.DefaultFileName());
}
private static int IndexOf(string[] arr, string elem)
{
for (int i = 0; i < arr.Length; i++)
{
if (arr[i].Equals(elem, StringComparison.Ordinal))
return i;
}
return -1;
}
private const string ArgVerbose = "verbose";
private const string ArgLogToStdOut = "logToStdOut";
private const string ArgReportQueueSizeMB = "reportQueueSizeMB";
private const string ArgEnableReportBatching = "enableReportBatching";
private const string ArgEnableStatistics = "enableStatistics";
private const string ArgProcessTimeout = "processTimeout";
private const string ArgTrackDirectoryCreation = "trackDirectoryCreation";
private const string ArgSandboxKindUsed = "sandboxKind";
private static Options ParseOptions(string[] toolArgs)
{
var opts = new OptionsBuilder(Options.Defaults);
var cli = new CommandLineUtilities(toolArgs);
foreach (var opt in cli.Options)
{
switch (opt.Name.TrimEnd('-', '+'))
{
case ArgVerbose:
case "v":
opts.Verbose = CommandLineUtilities.ParseBooleanOption(opt);
break;
case ArgLogToStdOut:
case "o":
opts.LogToStdOut = CommandLineUtilities.ParseBooleanOption(opt);
break;
case "numKextConnections":
case "c":
Console.WriteLine($"*** WARNING *** option /{opt.Name} has no effect any longer");
break;
case ArgReportQueueSizeMB:
case "r":
opts.ReportQueueSizeMB = CommandLineUtilities.ParseUInt32Option(opt, 1, 1024);
break;
case ArgEnableReportBatching:
case "b":
opts.EnableReportBatching = CommandLineUtilities.ParseBooleanOption(opt);
break;
case ArgEnableStatistics:
case "s":
opts.EnableTelemetry = CommandLineUtilities.ParseBooleanOption(opt);
break;
case ArgProcessTimeout:
case "t":
// Max is currently set to 4 hours and should suffice
opts.ProcessTimeout = CommandLineUtilities.ParseInt32Option(opt, (int)s_defaultProcessTimeOut, (int)s_defaultProcessTimeOutMax);
break;
case ArgTrackDirectoryCreation:
case "d":
opts.TrackDirectoryCreation = CommandLineUtilities.ParseBooleanOption(opt);
break;
case ArgSandboxKindUsed:
case "k":
opts.SandboxKindUsed = CommandLineUtilities.ParseEnumOption<SandboxKind>(opt);
Console.WriteLine("BVla " + opts.SandboxKindUsed);
break;
default:
throw new InvalidArgumentException($"Unrecognized option {opt.Name}");
}
}
return opts.Finish();
}
private static void PrintToStdout(string message)
{
Console.WriteLine(message);
}
private static void PrintToStderr(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine(message);
Console.ResetColor();
}
private string RenderReport(ReportedFileAccess report)
{
return m_options.Verbose
? $"Path = {report.GetPath(m_pathTable)}{Environment.NewLine}{report.Describe()}"
: report.ShortDescribe(m_pathTable);
}
private static void CollectAndUploadCrashReports(bool remoteTelemetryEnabled)
{
if (s_crashCollector != null)
{
// Put the state file at the root of the sandbox exec directory
var stateFileDirectory = Directory.GetCurrentDirectory();
CrashCollectorMacOS.Upload upload = (IReadOnlyList<CrashReport> reports, string sessionId) =>
{
if (!remoteTelemetryEnabled)
{
return false;
}
foreach (var report in reports)
{
Tracing.Logger.Log.DominoMacOSCrashReport(s_loggingContext, sessionId, report.Content, report.Type.ToString(), report.FileName);
}
return true;
};
try
{
s_crashCollector.UploadCrashReportsFromLastSession(s_loggingContext.Session.Id, stateFileDirectory, out var stateFilePath, upload);
}
catch (Exception exception)
{
PrintToStderr(exception.Message ?? exception.InnerException.Message);
}
}
}
}
}

Просмотреть файл

@ -1,30 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import { NetFx } from "Sdk.BuildXL";
import * as Managed from "Sdk.Managed";
import * as BuildXLSdk from "Sdk.BuildXL";
namespace SandboxExec {
export declare const qualifier: BuildXLSdk.DefaultQualifier;
@@public
export const exe = BuildXLSdk.executable({
assemblyName: "SandboxExec",
generateLogs: true,
allowUnsafeBlocks: true,
sources: globR(d`.`, "*.cs"),
references: [
importFrom("BuildXL.Utilities").dll,
importFrom("BuildXL.Utilities").Native.dll,
importFrom("BuildXL.Utilities").ToolSupport.dll,
importFrom("BuildXL.Utilities").Configuration.dll,
importFrom("BuildXL.Utilities").Utilities.Core.dll,
importFrom("BuildXL.Utilities.Instrumentation").AriaCommon.dll,
importFrom("BuildXL.Utilities.Instrumentation").Tracing.dll,
importFrom("BuildXL.Engine").Processes.dll,
importFrom("BuildXL.Engine").ProcessPipExecutor.dll,
],
});
}

Просмотреть файл

@ -1,26 +0,0 @@
using System;
using System.Diagnostics;
using BuildXL.Utilities.Instrumentation.Common;
using BuildXL.Utilities.Tracing;
namespace BuildXL.SandboxExec
{
internal static class Telemetry
{
internal static void TelemetryStartup(bool enableRemoteTelemetry)
{
if (!Debugger.IsAttached && enableRemoteTelemetry)
{
AriaV2StaticState.Enable(global::BuildXL.Tracing.AriaTenantToken.Key);
}
}
internal static void TelemetryShutdown()
{
if (AriaV2StaticState.IsEnabled)
{
AriaV2StaticState.TryShutDown(TimeSpan.FromSeconds(10), out _);
}
}
}
}

Просмотреть файл

@ -1,40 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using BuildXL.Utilities.Instrumentation.Common;
#pragma warning disable 1591
#nullable enable
namespace BuildXL.SandboxExec.Tracing
{
/// <summary>
/// Logging for SandboxExec.
/// There are no log files, so messages for events with <see cref="EventGenerators.LocalOnly"/> will be lost.
/// </summary>
[EventKeywordsType(typeof(Keywords))]
[EventTasksType(typeof(Tasks))]
[LoggingDetails("SandboxExecLogger")]
public abstract partial class Logger : LoggerBase
{
/// <summary>
/// Returns the logger instance
/// </summary>
public static Logger Log => m_log;
[GeneratedEvent(
(ushort)LogEventId.SandboxExecMacOSCrashReport,
EventGenerators = EventGenerators.TelemetryOnly,
EventLevel = Level.Critical,
Message = "Telemetry Only")]
public abstract void SandboxExecCrashReport(LoggingContext context, string crashSessionId, string message);
[GeneratedEvent(
(ushort)LogEventId.DominoMacOSCrashReport,
EventGenerators = EventGenerators.TelemetryOnly,
EventLevel = Level.Critical,
Message = "Telemetry Only")]
public abstract void DominoMacOSCrashReport(LoggingContext context, string crashSessionId, string content, string type, string filename);
}
}

Просмотреть файл

@ -1,21 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
namespace BuildXL.SandboxExec.Tracing
{
// disable warning regarding 'missing XML comments on public API'. We don't need docs for these values
#pragma warning disable 1591
/// <summary>
/// Defines event IDs corresponding to events in <see cref="Logger" />
/// </summary>
public enum LogEventId
{
None = 0,
DominoMacOSCrashReport = 412,
SandboxExecMacOSCrashReport = 413,
}
}

Просмотреть файл

@ -34,7 +34,6 @@ namespace BuildXL.SandboxedProcessExecutor
private OutputErrorObserver? m_outputErrorObserver;
private readonly ConsoleLogger m_logger = new ();
private ISandboxConnection? m_sandboxConnection = null;
private const int ReportQueueSizeForKextMB = 1024;
private readonly bool m_isRunningInCloudBuildVm = false;
/// <summary>
@ -432,21 +431,6 @@ namespace BuildXL.SandboxedProcessExecutor
{
m_sandboxConnection = new SandboxConnectionLinuxDetours(SandboxConnectionFailureCallback);
}
else if (OperatingSystemHelper.IsMacOS)
{
m_sandboxConnection = new SandboxConnectionKext(
new SandboxConnectionKext.Config
{
FailureCallback = SandboxConnectionFailureCallback,
KextConfig = new Interop.Unix.Sandbox.KextConfig
{
ReportQueueSizeMB = ReportQueueSizeForKextMB,
#if PLATFORM_OSX
EnableCatalinaDataPartitionFiltering = OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport
#endif
}
});
}
info.SandboxConnection = m_sandboxConnection;
}

Просмотреть файл

@ -1,205 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BuildXL.Native.IO;
using BuildXL.Processes;
using BuildXL.SandboxExec;
using BuildXL.Utilities.Core;
using Test.BuildXL.TestUtilities.Xunit;
using Xunit;
using Xunit.Abstractions;
namespace Test.Tool.SandboxExec
{
/// <summary>
/// Tests for <see cref="SandboxExec"/>.
/// /// </summary>
public class SandboxExecTests : BuildXL.TestUtilities.Xunit.XunitBuildXLTest
{
public SandboxExecTests(ITestOutputHelper output)
: base(output) { }
[Fact]
public void DedupeReportedFileAccesses()
{
var process = new ReportedProcess(1000, "/usr/bin/touch");
var writeReport = new ReportedFileAccess(ReportedFileOperation.CreateFile,
process,
RequestedAccess.Write,
FileAccessStatus.Allowed,
true,
0,
Usn.Zero,
DesiredAccess.GENERIC_WRITE,
ShareMode.FILE_SHARE_WRITE,
CreationDisposition.CREATE_NEW,
FlagsAndAttributes.FILE_ATTRIBUTE_NORMAL,
AbsolutePath.Invalid,
"/tmp/out1",
"*");
var readReport = new ReportedFileAccess(ReportedFileOperation.GetFileAttributes,
process,
RequestedAccess.Read,
FileAccessStatus.Allowed,
true,
0,
Usn.Zero,
DesiredAccess.GENERIC_READ,
ShareMode.FILE_SHARE_READ,
CreationDisposition.OPEN_EXISTING,
FlagsAndAttributes.FILE_ATTRIBUTE_NORMAL,
AbsolutePath.Invalid,
"/tmp/out1",
"*");
HashSet<ReportedFileAccess> allFileAccesses = new HashSet<ReportedFileAccess>() { writeReport, readReport };
HashSet<ReportedFileAccess> explicitFileAccesses = new HashSet<ReportedFileAccess>() { readReport, writeReport };
var runner = CreateSandboxExecRunner();
var dedupedReports = runner.DedupeAccessReports(allFileAccesses, explicitFileAccesses);
XAssert.IsTrue(dedupedReports.Count == 2);
var result = new string[2];
dedupedReports.CopyTo(result);
XAssert.AreArraysEqual(new string[] { " W /tmp/out1", " R /tmp/out1" }, result, true);
}
private static readonly SandboxExecRunner.Options Defaults = SandboxExecRunner.Options.Defaults;
[Theory]
[InlineData(true)]
[InlineData(false)]
public void TestParseArgsNoToolArgs(bool withSeparator)
{
var procArgs = new string[] { "/bin/ls", "-l" };
var args = withSeparator
? new[] { "--" }.Concat(procArgs).ToArray()
: procArgs;
var result = SandboxExecRunner.ParseArgs(args);
XAssert.AreEqual(Defaults, result.toolOptions);
XAssert.ArrayEqual(procArgs, result.procArgs);
}
[Fact]
public void TestParseArgsWithDoubleDashInProcArgs()
{
var procArgs = new string[] { "/bin/cat", "--" };
var args = new[] { "--" }.Concat(procArgs).ToArray();
var result = SandboxExecRunner.ParseArgs(args);
XAssert.AreEqual(Defaults, result.toolOptions);
XAssert.ArrayEqual(procArgs, result.procArgs);
}
[Theory]
[InlineData("/verbose", true)]
[InlineData("/verbose-", false)]
[InlineData("/v", true)]
[InlineData("/v+", true)]
[InlineData("/v-", false)]
public void TestParseArgsWithToolVerboseArgs(string arg, bool expectedVerboseValue)
{
var procArgs = new string[] { "/bin/cat", "--" };
var args = new[] { arg, "--" }.Concat(procArgs).ToArray();
var result = SandboxExecRunner.ParseArgs(args);
XAssert.AreEqual(expectedVerboseValue, result.toolOptions.Verbose);
XAssert.AreEqual(Defaults.ReportQueueSizeMB, result.toolOptions.ReportQueueSizeMB);
XAssert.ArrayEqual(procArgs, result.procArgs);
}
[Theory]
[InlineData("/verbose", "/verbose-", false, null)]
[InlineData("/verbose-", "/v", true, null)]
[InlineData("/v", "/r:400", true, 400)]
[InlineData("/r:300", "/r:500", null, 500)]
[InlineData("/r:300", "/verbose", true, 300)]
public void TestParseArgsWithTwoToolArgs(string arg1, string arg2, bool? expectedVerboseValue, int? expectedQueueSizeValue)
{
var procArgs = new string[] { "/bin/cat", "--" };
var args = new[] { arg1, arg2, "--" }.Concat(procArgs).ToArray();
var result = SandboxExecRunner.ParseArgs(args);
XAssert.AreEqual(expectedVerboseValue ?? Defaults.Verbose, result.toolOptions.Verbose);
XAssert.AreEqual(expectedQueueSizeValue ?? (int)Defaults.ReportQueueSizeMB, (int)result.toolOptions.ReportQueueSizeMB);
XAssert.ArrayEqual(procArgs, result.procArgs);
}
[Fact]
public void CommandLineArgumentsGetParsedCorrectly()
{
var input = new string[] { "/usr/bin/clang", "test.c", "-o", "test", "'a b c'", "/a/b/c/d e f.app"};
var arguments = SandboxExecRunner.ExtractAndEscapeCommandLineArguments(input);
if (OperatingSystemHelper.IsUnixOS)
{
XAssert.AreEqual("'test.c' '-o' 'test' ''\\''a b c'\\''' '/a/b/c/d e f.app'", arguments);
}
else
{
XAssert.AreEqual("test.c -o test 'a b c' /a/b/c/d e f.app", arguments);
}
}
[Fact]
public void CreateSandboxedProcessInfoWithExplicitReporting()
{
var instance = CreateSandboxExecRunner();
var processInfo = SandboxExecRunner.CreateSandboxedProcessInfo("/usr/bin/touch", instance);
// Make sure the SandboxExec sandboxed process info is always generated to explicitly report all file accesses
XAssert.IsTrue(processInfo.FileAccessManifest.ReportFileAccesses);
XAssert.IsFalse(processInfo.FileAccessManifest.FailUnexpectedFileAccesses);
}
[Fact]
public void TestSandboxConnectionNotInTestMode()
{
if (OperatingSystemHelper.IsMacOS)
{
return; // doesn't work with the kext
}
using var sandboxConnection = CreateSandboxConnection(isInTestMode: false);
var instance = new SandboxExecRunner(sandboxConnection);
var processInfo = SandboxExecRunner.CreateSandboxedProcessInfo("/usr/bin/touch", instance);
// Make sure the SandboxExec sandboxed process info is always generated to explicitly report all file accesses
XAssert.IsTrue(processInfo.FileAccessManifest.ReportFileAccesses);
XAssert.IsFalse(processInfo.FileAccessManifest.FailUnexpectedFileAccesses);
}
// Run this on Unix only as our CI pipeline makes sure the sandbox is running prior to execution of this test
[FactIfSupported(requiresMacOperatingSystem: true)]
public async Task CheckForFileAccessReportsWhenRunningProcessWithKextLoaded()
{
try
{
var instance = CreateSandboxExecRunner();
using (var process = await SandboxExecRunner.ExecuteAsync(instance, new string[] { "/bin/ls", "." }, TestOutputDirectory))
{
var result = await process.GetResultAsync();
var distinctAccessReports = instance.DedupeAccessReports(
result.FileAccesses,
result.ExplicitlyReportedFileAccesses,
result.AllUnexpectedFileAccesses);
XAssert.IsTrue(distinctAccessReports.Count > 0);
XAssert.Contains(distinctAccessReports, " R /bin/ls");
}
}
catch (Exception ex)
{
// This should not happen if the sandbox is loaded and non of the report processing did throw
XAssert.Fail("CheckForFileAccessReportsWhenRunningProcessWithKextLoaded, threw an exception: {0}", ex);
}
}
private SandboxExecRunner CreateSandboxExecRunner()
{
return new SandboxExecRunner(GetSandboxConnection());
}
}
}

Просмотреть файл

@ -1,23 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import * as Managed from "Sdk.Managed";
import * as Deployment from "Sdk.Deployment";
namespace Test.Tool.SandboxExec {
export declare const qualifier: BuildXLSdk.DefaultQualifier;
@@public
export const dll = BuildXLSdk.test({
assemblyName: "Test.Tool.SandboxExec",
sources: globR(d`.`, "*.cs"),
references: [
importFrom("BuildXL.Tools").SandboxExec.exe,
importFrom("BuildXL.Engine").Processes.dll,
importFrom("BuildXL.Utilities").dll,
importFrom("BuildXL.Utilities").Native.dll,
importFrom("BuildXL.Utilities").ToolSupport.dll,
importFrom("BuildXL.Utilities").Utilities.Core.dll,
],
});
}

Просмотреть файл

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<!-- Even though this file is not used by Visual Studio to fetch NuGet
packages, it is necessary to tell the VS test discoverer it nees to look for
xunit based unit tests. -->
<package id="xunit.runner.visualstudio" version="2.1.0" targetFramework="net45" />
</packages>

Просмотреть файл

@ -176,31 +176,6 @@ namespace BuildXL.Utilities.Configuration
/// </summary>
bool MeasureProcessCpuTimes { get; }
/// <summary>
/// Indicates how big a single sandbox kernel extension report queue is in MB when it is allocated in the sandbox kernel extension
/// </summary>
uint KextReportQueueSizeMb { get; }
/// <summary>
/// Tells the sandbox kernel extension whether or not to batch reports.
/// </summary>
bool KextEnableReportBatching { get; }
/// <summary>
/// Throttling can be triggered when CPU usage is above this value.
/// </summary>
uint KextThrottleCpuUsageBlockThresholdPercent { get; }
/// <summary>
/// A blocked process can be awakened only when CPU usage is below this value.
/// </summary>
uint KextThrottleCpuUsageWakeupThresholdPercent { get; }
/// <summary>
/// Throttling can be triggered only when available RAM drops below this value.
/// </summary>
uint KextThrottleMinAvailableRamMB { get; }
/// <summary>
/// Execution mode for processes that require admin privilege.
/// </summary>

Просмотреть файл

@ -37,11 +37,6 @@ namespace BuildXL.Utilities.Configuration.Mutable
AllowInternalDetoursErrorNotificationFile = true;
EnforceAccessPoliciesOnDirectoryCreation = false;
MeasureProcessCpuTimes = true; // always measure process times + ram consumption
KextReportQueueSizeMb = 0; // let the sandbox kernel extension apply defaults
KextEnableReportBatching = true; // use lock-free queue for batching access reports
KextThrottleCpuUsageBlockThresholdPercent = 0; // no throttling by default
KextThrottleCpuUsageWakeupThresholdPercent = 0; // no throttling by default
KextThrottleMinAvailableRamMB = 0; // no throttling by default
AdminRequiredProcessExecutionMode = AdminRequiredProcessExecutionMode.Internal;
RedirectedTempFolderRootForVmExecution = AbsolutePath.Invalid;
RetryOnAzureWatsonExitCode = false;
@ -94,11 +89,6 @@ namespace BuildXL.Utilities.Configuration.Mutable
AllowInternalDetoursErrorNotificationFile = template.AllowInternalDetoursErrorNotificationFile;
EnforceAccessPoliciesOnDirectoryCreation = template.EnforceAccessPoliciesOnDirectoryCreation;
MeasureProcessCpuTimes = template.MeasureProcessCpuTimes;
KextReportQueueSizeMb = template.KextReportQueueSizeMb;
KextEnableReportBatching = template.KextEnableReportBatching;
KextThrottleCpuUsageBlockThresholdPercent = template.KextThrottleCpuUsageBlockThresholdPercent;
KextThrottleCpuUsageWakeupThresholdPercent = template.KextThrottleCpuUsageWakeupThresholdPercent;
KextThrottleMinAvailableRamMB = template.KextThrottleMinAvailableRamMB;
AdminRequiredProcessExecutionMode = template.AdminRequiredProcessExecutionMode;
RedirectedTempFolderRootForVmExecution = pathRemapper.Remap(template.RedirectedTempFolderRootForVmExecution);
RetryOnAzureWatsonExitCode = template.RetryOnAzureWatsonExitCode;

Просмотреть файл

@ -1,17 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace BuildXL.Native.Processes.Unix
{
/// <summary>
/// Static class with some constant definitions pertinent to the sandbox kext.
/// </summary>
public static class KextInfo
{
/// <summary>
/// Until some automation for kernel extension building and deployment is in place, this number has to be kept in sync with the 'CFBundleVersion'
/// inside the Info.plist file of the kernel extension code base. BuildXL will not work if a version mismatch is detected!
/// </summary>
public const string RequiredKextVersionNumber = "3.3.99";
}
}

Просмотреть файл

@ -8,6 +8,7 @@ using System.Runtime.InteropServices;
using System.Text;
using BuildXL.Interop.Unix;
using BuildXL.Native.IO;
using BuildXL.Utilities.Core;
using Microsoft.Win32.SafeHandles;
#if FEATURE_SAFE_PROCESS_HANDLE

Просмотреть файл

@ -33,36 +33,11 @@ namespace Test.BuildXL.TestUtilities.Xunit
Justification = "Test follow different pattern with Initialize and Cleanup.")]
public abstract class XunitBuildXLTest : BuildXLTestBase, IDisposable
{
private static readonly Lazy<ISandboxConnection> s_macOSSandboxConnection = new Lazy<ISandboxConnection>(() => CreateSandboxConnection(isInTestMode: true));
/// <summary>
/// Creates a new sandbox connection.
/// </summary>
public static ISandboxConnection CreateSandboxConnection(bool isInTestMode)
{
if (OperatingSystemHelper.IsMacOS)
{
var kind = ReadSandboxKindFromEnvVars();
if (kind == SandboxKind.MacOsKext)
{
return new SandboxConnectionKext(
skipDisposingForTests: true,
config: new SandboxConnectionKext.Config
{
MeasureCpuTimes = true,
FailureCallback = FailureCallback,
KextConfig = new KextConfig
{
EnableCatalinaDataPartitionFiltering = OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport
}
});
}
else
{
return new SandboxConnection(kind, isInTestMode: isInTestMode);
}
}
return null;
}
@ -71,30 +46,9 @@ namespace Test.BuildXL.TestUtilities.Xunit
XAssert.Fail($"Sandbox failed. Status: {status}. Description: {description}");
}
private static SandboxKind ReadSandboxKindFromEnvVars()
{
var kind = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("BUILDXL_MACOS_ES_SANDBOX"))
? SandboxKind.MacOsEndpointSecurity
: !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("BUILDXL_MACOS_DETOURS_SANDBOX"))
? SandboxKind.MacOsDetours
: !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("BUILDXL_MACOS_HYBRID_SANDBOX"))
? SandboxKind.MacOsHybrid
: SandboxKind.MacOsKext;
if (!OperatingSystemHelperExtension.IsMacWithoutKernelExtensionSupport &&
(kind == SandboxKind.MacOsEndpointSecurity || kind == SandboxKind.MacOsHybrid))
{
throw new NotSupportedException("EndpointSecurity and Hybrid sandbox types can't be run on system older than macOS Catalina (10.15+).");
}
return kind;
}
/// <summary>
/// MacOS: Returns a static kernel connection object on macos. Unit tests would spam the kernel extension if they need sandboxing, so we
/// tunnel all requests through the same object to keep kernel memory and CPU utilization low.
/// Windows: Always returns null and causes no overhead for testing.
/// Linux: Returns a new sandboxed connection on each call for the Linux sandbox because it does not use a kernel extension.
/// Linux: Returns a new sandboxed connection on each call for the Linux sandbox
/// </summary>
public static ISandboxConnection GetSandboxConnection()
{
@ -103,7 +57,7 @@ namespace Test.BuildXL.TestUtilities.Xunit
return new SandboxConnectionLinuxDetours(FailureCallback, isInTestMode: true);
}
return s_macOSSandboxConnection.Value;
return null;
}
/// <summary>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -21,6 +21,8 @@ namespace BuildXL.Interop.Unix
public static readonly int ReportQueueSuccessCode = 0x1000;
public static readonly int SandboxSuccess = 0x0;
private static readonly Encoding s_accessReportStringEncoding = Encoding.UTF8;
public static unsafe int NormalizePathAndReturnHash(byte[] pPath, byte[] normalizedPath)
{
if (IsMacOS)
@ -36,145 +38,6 @@ namespace BuildXL.Interop.Unix
}
}
public enum Configuration : int
{
EndpointSecuritySandboxType = 0,
DetoursSandboxType,
HybridSandboxType,
KextType
}
[StructLayout(LayoutKind.Sequential)]
public struct SandboxConnectionInfo
{
public Configuration Config;
public int Error;
}
[DllImport(Libraries.BuildXLInteropLibMacOS, SetLastError = true)]
public static extern void InitializeSandbox(ref SandboxConnectionInfo info, int host);
[DllImport(Libraries.BuildXLInteropLibMacOS, SetLastError = true)]
public static extern void DeinitializeSandbox();
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void ObserverFileAccessReports(
ref SandboxConnectionInfo info,
[MarshalAs(UnmanagedType.FunctionPtr)] AccessReportCallback callbackPointer,
long accessReportSize);
[StructLayout(LayoutKind.Sequential)]
public struct KextConnectionInfo
{
public int Error;
public uint Connection;
/// <summary>
/// The end of the struct is used for handles to raw memory directly, so we can save and pass CoreFoundation types around
/// between managed and unmanaged code
/// </summary>
private readonly ulong m_restricted1; // IONotificationPortRef
}
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention = CallingConvention.Cdecl)]
private static extern void InitializeKextConnection(ref KextConnectionInfo info, long infoSize);
public static void InitializeKextConnection(ref KextConnectionInfo info)
=> InitializeKextConnection(ref info, Marshal.SizeOf(info));
[StructLayout(LayoutKind.Sequential)]
public struct KextSharedMemoryInfo
{
public int Error;
public ulong Address;
public uint Port;
}
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention = CallingConvention.Cdecl)]
private static extern void InitializeKextSharedMemory(ref KextSharedMemoryInfo memoryInfo, long memoryInfoSize, KextConnectionInfo info);
public static void InitializeKextSharedMemory(KextConnectionInfo connectionInfo, ref KextSharedMemoryInfo memoryInfo)
=> InitializeKextSharedMemory(ref memoryInfo, Marshal.SizeOf(memoryInfo), connectionInfo);
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention = CallingConvention.Cdecl)]
public static extern void DeinitializeKextConnection(KextConnectionInfo info);
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention = CallingConvention.Cdecl)]
public static extern void DeinitializeKextSharedMemory(KextSharedMemoryInfo memoryInfo, KextConnectionInfo info);
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.LPStr)]
public static extern void KextVersionString(StringBuilder s, int size);
[Flags]
public enum ConnectionType
{
Kext,
EndpointSecurity
}
[DllImport(Libraries.BuildXLInteropLibMacOS, EntryPoint = "SendPipStarted")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SendPipStarted(int processId, long pipId, byte[] famBytes, int famBytesLength, ConnectionType type, ref KextConnectionInfo info);
[DllImport(Libraries.BuildXLInteropLibMacOS, EntryPoint = "SendPipProcessTerminated")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SendPipProcessTerminated(long pipId, int processId, ConnectionType type, ref KextConnectionInfo info);
[DllImport(Libraries.BuildXLInteropLibMacOS, EntryPoint = "CheckForDebugMode")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CheckForDebugMode(ref bool isDebug, KextConnectionInfo info);
[StructLayout(LayoutKind.Sequential)]
public struct ResourceThresholds
{
/// <summary>
/// A process can be blocked if the current CPU usage becomes higher than this value.
/// </summary>
public uint CpuUsageBlockPercent;
/// <summary>
/// A blocked process can be awakened only when CPU usage is below this value.
/// Defaults to <see cref="CpuUsageBlockPercent"/> when set to 0.
/// </summary>
public uint CpuUsageWakeupPercent;
/// <summary>
/// Always block processes when available RAM drops below this threshold (takes precedence over CPU usage).
/// </summary>
public uint MinAvailableRamMB;
/// <summary>
/// Returns whether these resource threshold parameters enable process throttling or not.
/// </summary>
public bool IsProcessThrottlingEnabled()
{
return
MinAvailableRamMB > 0 ||
isThresholdEnabled(CpuUsageBlockPercent);
static bool isThresholdEnabled(uint percent) => percent > 0 && percent < 100;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct KextConfig
{
public uint ReportQueueSizeMB;
public bool EnableReportBatching;
public ResourceThresholds ResourceThresholds;
public bool EnableCatalinaDataPartitionFiltering;
}
[DllImport(Libraries.BuildXLInteropLibMacOS, EntryPoint = "Configure")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool Configure(KextConfig config, KextConnectionInfo info);
[DllImport(Libraries.BuildXLInteropLibMacOS, EntryPoint = "UpdateCurrentResourceUsage")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UpdateCurrentResourceUsage(uint cpuUsageBasisPoints, uint availableRamMB, KextConnectionInfo info);
private static readonly Encoding s_accessReportStringEncoding = Encoding.UTF8;
[StructLayout(LayoutKind.Sequential)]
public struct AccessReportStatistics
@ -184,50 +47,6 @@ namespace BuildXL.Interop.Unix
public ulong DequeueTime;
}
/// <summary>
/// Struct sent in place of <see cref="AccessReport.PathOrPipStats"/> in case of a
/// <see cref="FileOperation.OpProcessTreeCompleted"/> operation.
/// </summary>
/// <remarks>
/// CODESYNC: Public/Src/Sandbox/MacOs/Sandbox/Src/BuildXLSandboxShared.hpp
/// </remarks>
[StructLayout(LayoutKind.Explicit, Size = 1024)]
public struct PipKextStats
{
[MarshalAs(UnmanagedType.U4)][FieldOffset(0)]
public uint LastPathLookupElemCount;
[MarshalAs(UnmanagedType.U4)][FieldOffset(4)]
public uint LastPathLookupNodeCount;
[MarshalAs(UnmanagedType.U4)][FieldOffset(8)]
public uint LastPathLookupNodeSize;
[MarshalAs(UnmanagedType.U4)][FieldOffset(12)]
public uint NumCacheHits;
[MarshalAs(UnmanagedType.U4)][FieldOffset(16)]
public uint NumCacheMisses;
[MarshalAs(UnmanagedType.U4)][FieldOffset(20)]
public uint CacheRecordCount;
[MarshalAs(UnmanagedType.U4)][FieldOffset(24)]
public uint CacheRecordSize;
[MarshalAs(UnmanagedType.U4)][FieldOffset(28)]
public uint CacheNodeCount;
[MarshalAs(UnmanagedType.U4)][FieldOffset(32)]
public uint CacheNodeSize;
[MarshalAs(UnmanagedType.U4)][FieldOffset(36)]
public uint NumForks;
[MarshalAs(UnmanagedType.U4)][FieldOffset(40)]
public uint NumHardLinkRetries;
}
/// <remarks>
/// CODESYNC: Public/Src/Sandbox/MacOs/Sandbox/Src/BuildXLSandboxShared.hpp
/// </remarks>
@ -251,11 +70,10 @@ namespace BuildXL.Interop.Unix
/// <summary>
/// Corresponds to a <c>union { char path[MAXPATHLEN]; PipCompletionStats pipStats; }</c> C type.
/// Use <see cref="DecodePath"/> and <see cref="DecodePipKextStats"/> method to decode this value
/// into either a path string or a <see cref="PipKextStats"/> structure.
/// Use <see cref="DecodePath"/> method to decode this into either a path string
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst=Constants.MaxPathLength)]
public byte[] PathOrPipStats;
public byte[] PathOrPipStats; // TODO [maly]: we only use this for path, so we can optimize the pipstats thing away (I think!)
public AccessReportStatistics Statistics;
@ -268,15 +86,6 @@ namespace BuildXL.Interop.Unix
public string DecodeOperation() => Operation.GetName();
/// <summary>
/// Interprets <see cref="PathOrPipStats"/> as a 0-terminated UTF8-encoded string.
/// </summary>
public string DecodePath()
{
Contract.Requires(PathOrPipStats != null);
return s_accessReportStringEncoding.GetString(PathOrPipStats).TrimEnd('\0');
}
/// <summary>
/// Encodes a given string into a byte array of a given size.
/// </summary>
@ -288,43 +97,15 @@ namespace BuildXL.Interop.Unix
}
/// <summary>
/// Interprets <see cref="PathOrPipStats"/> as an instance of the <see cref="PipKextStats"/> struct.
/// Interprets <see cref="PathOrPipStats"/> as a 0-terminated UTF8-encoded string.
/// </summary>
public PipKextStats DecodePipKextStats()
public string DecodePath()
{
var handle = GCHandle.Alloc(PathOrPipStats, GCHandleType.Pinned);
try
{
return (PipKextStats)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(PipKextStats));
}
finally
{
handle.Free();
}
Contract.Requires(PathOrPipStats != null);
return s_accessReportStringEncoding.GetString(PathOrPipStats).TrimEnd('\0');
}
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void AccessReportCallback(AccessReport report, int error);
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern void ListenForFileAccessReports(
[MarshalAs(UnmanagedType.FunctionPtr)] AccessReportCallback callbackPointer,
long accessReportSize,
ulong address,
uint port);
/// <summary>
/// Callback the kernel extension can use to report any unrecoverable failures.
///
/// This callback adheres to the IOAsyncCallback0 signature (see https://developer.apple.com/documentation/iokit/ioasynccallback0?language=objc)
/// We don't transfer any data from the sandbox kernel extension to the managed code when an unrecoverable error happens for now, this can potentially be extended later.
/// </summary>
/// <param name="refCon">The pointer to the callback itself</param>
/// <param name="status">Error code indicating what failure happened</param>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void NativeFailureCallback(void *refCon, int status);
/// <summary>
/// Callback the SandboxConnection uses to report any unrecoverable failure back to
/// the scheduler (which, in response, should then terminate the build).
@ -332,12 +113,6 @@ namespace BuildXL.Interop.Unix
/// <param name="status">Error code indicating what failure happened</param>
/// <param name="description">Arbitrary description</param>
public delegate void ManagedFailureCallback(int status, string description);
[DllImport(Libraries.BuildXLInteropLibMacOS, CallingConvention = CallingConvention.Cdecl)]
public static extern bool SetFailureNotificationHandler([MarshalAs(UnmanagedType.FunctionPtr)] NativeFailureCallback callback, KextConnectionInfo info);
[DllImport(Libraries.BuildXLInteropLibMacOS)]
public static extern ulong GetMachAbsoluteTime();
}
}

Просмотреть файл

@ -23,31 +23,6 @@ namespace BuildXL.Utilities.Core
/// </summary>
WinDetours,
/// <summary>
/// macOs-specific: using kernel extension
/// </summary>
MacOsKext,
/// <summary>
/// Like <see cref="MacOsKext"/> except that it ignores all reported file accesses.
/// </summary>
MacOsKextIgnoreFileAccesses,
/// <summary>
/// macOs-specific: Using the EndpointSecurity subsystem for sandboxing (available from 10.15+)
/// </summary>
MacOsEndpointSecurity,
/// <summary>
/// macOs-specific: Using DYLD interposing for sandboxing
/// </summary>
MacOsDetours,
/// <summary>
/// macOs-specific: Using the EndpointSecurity subsystem (available from 10.15+) and DYLD interposing together for sandboxing
/// </summary>
MacOsHybrid,
/// <summary>
/// Linux-specific: using LD_PRELOAD interposing
/// </summary>

Просмотреть файл

@ -26,11 +26,6 @@ namespace BuildXL.Utilities
private static readonly Tuple<string, string> ProcessorNameAndIdentifierMacOS =
OperatingSystemHelper.IsMacOS ? GetProcessorNameAndIdentifierMacOS() : Tuple.Create(string.Empty, string.Empty);
/// <summary>
/// Indicates if BuildXL can only run with non-kernel extension sandboxing on macOS - that is the case on Catalina (10.15+) or newer
/// </summary>
public static readonly bool IsMacWithoutKernelExtensionSupport = OperatingSystemHelper.IsMacOS && (CurrentMacOSVersion.Value.Major >= 11 || (CurrentMacOSVersion.Value.Major == 10 && CurrentMacOSVersion.Value.Minor >= 15));
// Sysctl constants to query CPU information
private static readonly string MACHDEP_CPU_BRAND_STRING = "machdep.cpu.brand_string";
private static readonly string MACHDEP_CPU_MODEL = "machdep.cpu.model";