660 lines
24 KiB
C#
660 lines
24 KiB
C#
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// File: DistroBuilder.cs
|
|
//
|
|
// Note: Main entry point for DistroBuilder tool, used to collect
|
|
// application metadata into a manifest.
|
|
//
|
|
using System;
|
|
using System.IO;
|
|
using System.Xml;
|
|
using System.Collections;
|
|
using System.Security.Cryptography;
|
|
|
|
public class DistroBuilder
|
|
{
|
|
//
|
|
// Fields in the distro build class
|
|
//
|
|
|
|
XmlNode systemPolicy; // the external xml policy
|
|
|
|
// manifest and well-defined nodes
|
|
XmlDocument systemManifest;
|
|
XmlNode manifestRoot;
|
|
XmlNode drivers;
|
|
|
|
// internal node that we build and add to the manifest
|
|
XmlNode fileList;
|
|
|
|
// all files in the distro (full names, plus the separate directory prefix)
|
|
string distroDirectoryName;
|
|
SortedList applicationFiles;
|
|
SortedList manifestFiles;
|
|
SortedList otherFiles;
|
|
|
|
// key filenames
|
|
string outFilePath; // system manifest path+name
|
|
string outFileName; // just the name of the manifest
|
|
string iniFileName; // ini file
|
|
|
|
// const name of the kernel file
|
|
const string kernelFileName = "kernel.dmp";
|
|
|
|
// set up an object that can build the distribution
|
|
public DistroBuilder(string policyFile, string outputFile, string iniFile,
|
|
string distroDirectoryName,
|
|
string distroDescriptionFileName)
|
|
{
|
|
// copy output file names.
|
|
outFilePath = outputFile;
|
|
iniFileName = iniFile;
|
|
|
|
// load the system policy.
|
|
XmlDocument policyDoc = new XmlDocument();
|
|
policyDoc.Load(policyFile);
|
|
systemPolicy = GetChild(policyDoc, "distribution");
|
|
|
|
// set up Xml pieces.
|
|
systemManifest = new XmlDocument();
|
|
manifestRoot = AddElement(systemManifest, "system");
|
|
|
|
fileList = AddElement(null, "files");
|
|
drivers = AddElement(null, "drivers");
|
|
|
|
// now set up the lists of files in the distro:
|
|
applicationFiles = new SortedList();
|
|
manifestFiles = new SortedList();
|
|
otherFiles = new SortedList();
|
|
|
|
FileInfo allFiles = new FileInfo(distroDescriptionFileName);
|
|
StreamReader stream = allFiles.OpenText();
|
|
string s = stream.ReadLine();
|
|
while (s != null) {
|
|
if (s.EndsWith(".x86")) {
|
|
applicationFiles.Add(s, s);
|
|
}
|
|
else if (s.EndsWith(".manifest")) {
|
|
manifestFiles.Add(s, s);
|
|
}
|
|
else {
|
|
otherFiles.Add(s, s);
|
|
}
|
|
s = stream.ReadLine();
|
|
}
|
|
stream.Close();
|
|
|
|
// add the output file to the list
|
|
string [] a = outFilePath.Split('\\');
|
|
outFileName = a[a.Length-1];
|
|
|
|
FileInfo outFile = new FileInfo(outFilePath);
|
|
outFilePath = outFile.FullName;
|
|
if (!otherFiles.Contains(outFilePath)) {
|
|
otherFiles.Add(outFilePath, outFilePath);
|
|
}
|
|
|
|
// now get the fully path for the distro directory name
|
|
FileInfo directoryInfo = new FileInfo(distroDirectoryName);
|
|
this.distroDirectoryName = directoryInfo.FullName;
|
|
}
|
|
|
|
//////////////////////////////////////////////// Methods to help with XML.
|
|
//
|
|
private XmlNode AddElement(XmlNode parent, string name)
|
|
{
|
|
XmlNode element = systemManifest.CreateNode(XmlNodeType.Element, name, "");
|
|
if (parent != null) {
|
|
parent.AppendChild(element);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
private void AddAttribute(XmlNode node, string name, string value)
|
|
{
|
|
XmlAttribute attr = systemManifest.CreateAttribute(name);
|
|
attr.Value = value;
|
|
node.Attributes.Append(attr);
|
|
}
|
|
|
|
private XmlNode AddImported(XmlNode parent, XmlNode root, bool deep)
|
|
{
|
|
XmlNode element = systemManifest.ImportNode(root, true);
|
|
if (parent != null) {
|
|
parent.AppendChild(element);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
// Return an array with every child of the parent whose name matches
|
|
// <name>
|
|
private static XmlNode[] GetChildren(XmlNode parent, string name)
|
|
{
|
|
ArrayList result = new ArrayList();
|
|
|
|
foreach (XmlNode child in parent.ChildNodes) {
|
|
if (child.Name == name) {
|
|
result.Add(child);
|
|
}
|
|
}
|
|
|
|
if (result.Count == 0) {
|
|
return new XmlNode[0];
|
|
}
|
|
|
|
XmlNode[] children = new XmlNode [result.Count];
|
|
for (int i = 0; i < result.Count; i++) {
|
|
children[i] = (XmlNode)result[i];
|
|
}
|
|
return children;
|
|
}
|
|
|
|
// Get the first child named `name'
|
|
private static XmlNode GetChild(XmlNode parent, string name)
|
|
{
|
|
return parent[name];
|
|
}
|
|
|
|
// Get end node along a path of first matches
|
|
private static XmlNode GetDescendant(XmlNode root, string [] path)
|
|
{
|
|
XmlNode parent = root;
|
|
|
|
foreach (string pathelement in path) {
|
|
parent = GetChild(parent, pathelement);
|
|
if (parent == null) {
|
|
return null;
|
|
}
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
// Get the named attribute if it exists.
|
|
private static string GetAttribute(XmlNode node, string attrib)
|
|
{
|
|
XmlAttribute xa = node.Attributes[attrib];
|
|
return xa != null ? xa.Value : null;
|
|
}
|
|
|
|
private static string GetAttribute(XmlNode node, string attrib, string value)
|
|
{
|
|
XmlAttribute xa = node.Attributes[attrib];
|
|
return xa != null ? xa.Value : value;
|
|
}
|
|
|
|
private static int GetAttribute(XmlNode node, string attrib, int value)
|
|
{
|
|
XmlAttribute xa = node.Attributes[attrib];
|
|
return xa != null ? Int32.Parse(xa.Value) : value;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// return true if the node's name is in names.
|
|
private bool TestNodeIs(XmlNode node, string [] names)
|
|
{
|
|
foreach (string name in names) {
|
|
if (node.Name == name) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// [TODO] this attribute create code should be in a function!
|
|
// add a DriverCategory node into the <drivers>
|
|
private void AddDriverNode(string name, string path,
|
|
string signature, string iclass,
|
|
ArrayList values)
|
|
{
|
|
// create the new node
|
|
XmlNode driver = AddElement(null, "driver");
|
|
|
|
// set its name, signature, and path
|
|
AddAttribute(driver, "name", name);
|
|
AddAttribute(driver, "signature", signature);
|
|
AddAttribute(driver, "path", path);
|
|
if (iclass != "") {
|
|
AddAttribute(driver, "class", iclass);
|
|
}
|
|
|
|
// add any enumerates via copy.
|
|
foreach (XmlNode node in values) {
|
|
driver.AppendChild(node.CloneNode(true));
|
|
}
|
|
|
|
// and insert this node into the registry:
|
|
drivers.AppendChild(driver);
|
|
}
|
|
|
|
// given a driverCategory that potentially has many device signatures, split
|
|
// it by device and create an entry in the drivers for each device
|
|
private void CreateRegistryEntriesFromCategory(XmlNode category,
|
|
string name,
|
|
string path)
|
|
{
|
|
ArrayList values = new ArrayList();
|
|
|
|
// first parse the entries in the driver category to fill out the
|
|
// Endpoint, FixedHardware, and DynamicHardware sets
|
|
string iclass = GetAttribute(category, "class", "");
|
|
|
|
// now populate these with children based on a few basic matching rules
|
|
foreach (XmlNode node in category.ChildNodes) {
|
|
if (node.Name == "enumerates" ||
|
|
node.Name == "endpoints" ||
|
|
node.Name == "fixedHardware" ||
|
|
node.Name == "dynamicHardware" ||
|
|
node.Name == "configs") {
|
|
values.Add(AddImported(null, node, true));
|
|
}
|
|
else if (node.Name != "device") {
|
|
//
|
|
}
|
|
}
|
|
|
|
// get every device signature in this driver category
|
|
XmlNode[] signatureTags = GetChildren(category, "device");
|
|
|
|
// there's a chance (for example, with the Hal) that there are no
|
|
// signatures. In this case, we process the category once, with a null
|
|
// signature
|
|
if (signatureTags.Length == 0) {
|
|
AddDriverNode(name, path, "", iclass, values);
|
|
}
|
|
else {
|
|
foreach (XmlNode signature in signatureTags) {
|
|
string value = GetAttribute(signature, "signature", "");
|
|
AddDriverNode(name, path, value, iclass, values);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is the main method for creating a driver registry.
|
|
public void CreateDriverRegistry()
|
|
{
|
|
foreach (DictionaryEntry de in manifestFiles) {
|
|
string filename = (string) de.Key;
|
|
if (filename.EndsWith(".manifest")) {
|
|
// get the manifest as an Xml document
|
|
XmlDocument manifestDoc = new XmlDocument();
|
|
manifestDoc.Load(filename);
|
|
|
|
XmlNode application = GetChild(manifestDoc, "application");
|
|
|
|
// Read the name and path from the manifest
|
|
string name = GetAttribute(application, "name");
|
|
|
|
// Console.WriteLine("{0}:", filename);
|
|
foreach (XmlNode process in GetChildren(application, "process")) {
|
|
string path = GetAttribute(process, "path", "");
|
|
|
|
foreach (XmlNode categories in GetChildren(process, "categories")) {
|
|
foreach (XmlNode category in GetChildren(categories, "category")) {
|
|
if (GetAttribute(category, "name") != "driver") {
|
|
continue;
|
|
}
|
|
|
|
string iclass = GetAttribute(category, "class", "");
|
|
|
|
// now process all DriverCategories
|
|
CreateRegistryEntriesFromCategory(category, name, path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool NamesMatch(string target, string prefix, bool isPrefix)
|
|
{
|
|
int len = isPrefix ? prefix.Length :
|
|
(target.Length > prefix.Length ? target.Length : prefix.Length);
|
|
|
|
return (String.Compare(target, 0, prefix, 0, len, true) == 0);
|
|
}
|
|
|
|
|
|
// Remove unwanted drivers from the rot using the policy.
|
|
private void RemoveNodes(XmlNode root, XmlNode policy)
|
|
{
|
|
foreach (XmlNode rule in policy.ChildNodes) {
|
|
string attrName = "";
|
|
string attrVal = "";
|
|
bool isPrefix = false;
|
|
|
|
if (rule.Name == "driver") {
|
|
attrName = "name";
|
|
}
|
|
else if (rule.Name == "device") {
|
|
attrName = "signature";
|
|
isPrefix = true;
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
ArrayList removals = new ArrayList();
|
|
attrVal = GetAttribute(rule, attrName);
|
|
foreach (XmlNode candidate in root.ChildNodes) {
|
|
string target = GetAttribute(candidate, attrName);
|
|
|
|
if (NamesMatch(target, attrVal, isPrefix)) {
|
|
removals.Add(candidate);
|
|
}
|
|
}
|
|
foreach (XmlNode oldnode in removals) {
|
|
root.RemoveChild(oldnode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// in addition, we use the imperative mechanism to create the "follows"
|
|
// ordering, which lets us specify a more total order on the initialization
|
|
// of drivers
|
|
private void ApplyOrdering(XmlNode root, XmlNode policy)
|
|
{
|
|
foreach (XmlNode rule in policy.ChildNodes) {
|
|
string signature = GetAttribute(rule, "signature");
|
|
string name = GetAttribute(rule, "name");
|
|
foreach (XmlNode candidate in root.ChildNodes) {
|
|
string candidatesignature = GetAttribute(candidate, "signature");
|
|
if (candidatesignature.StartsWith(signature)) {
|
|
XmlNode tag = AddElement(candidate, rule.Name);
|
|
AddAttribute(tag, "name", name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// and this is the master method for applying the imperative policy
|
|
private void ApplyPolicyToRegistry()
|
|
{
|
|
// get its driversConfig:
|
|
XmlNode policy = GetChild(systemPolicy, "driverRegistryConfig");
|
|
|
|
// go through each imperative command
|
|
foreach (XmlNode child in policy.ChildNodes) {
|
|
if (child.Name == "remove") {
|
|
RemoveNodes(drivers, child);
|
|
}
|
|
else if (child.Name == "ordering") {
|
|
ApplyOrdering(drivers, child);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This prints the distribution manifest when all is done
|
|
public void PrintManifestFile()
|
|
{
|
|
XmlTextWriter w = new XmlTextWriter(outFilePath,
|
|
System.Text.Encoding.UTF8);
|
|
w.Formatting = Formatting.Indented;
|
|
systemManifest.Save(w);
|
|
w.Close();
|
|
}
|
|
|
|
// [TODO] there ought to be some policy that decides what should
|
|
// to in the distribution, but for now we just take as
|
|
// input a list of files that have been built, and assume
|
|
// it is the output of this alluded-to step
|
|
|
|
// Every file in the distro must be added to a list, both so that the file
|
|
// can be added to SingBoot.ini, and so that it can have a filename
|
|
// associated with it in the namespace
|
|
private void AddFileNode(string pathName, int index, bool visible,
|
|
int manifestIndex)
|
|
{
|
|
XmlNode node = AddElement(null, "file");
|
|
|
|
// get values for all the attributes of this file:
|
|
bool isExecutable = pathName.EndsWith(".x86");
|
|
bool isManifest = pathName.EndsWith(".manifest");
|
|
|
|
string [] nameSet = pathName.Split('\\');
|
|
string shortName = nameSet[nameSet.Length - 1].ToLower();
|
|
string distroName = pathName.Remove(0, distroDirectoryName.Length);
|
|
distroName = distroName.Replace('\\', '/');
|
|
|
|
// add the attributes:
|
|
AddAttribute(node, "distroName", distroName);
|
|
AddAttribute(node, "name", shortName);
|
|
AddAttribute(node, "id", index.ToString());
|
|
|
|
if (isExecutable) {
|
|
AddAttribute(node, "executable", "true");
|
|
if (manifestIndex > 2) {
|
|
AddAttribute(node, "manifestId", manifestIndex.ToString());
|
|
}
|
|
}
|
|
else if (isManifest) {
|
|
AddAttribute(node, "manifest", "true");
|
|
}
|
|
|
|
// add the file to the master file list
|
|
fileList.AppendChild(node);
|
|
}
|
|
|
|
// This calls the above method to populate the fileList with all files that
|
|
// are to be included in the distribution
|
|
private void BuildFileList()
|
|
{
|
|
int position = 2;
|
|
|
|
// first of all, let's set the kernel.dmp and output manifest file
|
|
// entries
|
|
string kernelPath = null;
|
|
foreach (DictionaryEntry de in otherFiles) {
|
|
string filename = (string) de.Key;
|
|
if (filename.EndsWith("\\" + kernelFileName)) {
|
|
kernelPath = filename;
|
|
break;
|
|
}
|
|
}
|
|
AddFileNode(kernelPath, 0, false, -1);
|
|
AddFileNode(outFilePath, 1, false, -1);
|
|
|
|
// now let's build the file list:
|
|
// get the policy on what goes in the bootscript
|
|
XmlNode policy = GetDescendant(systemPolicy, new string[] {"bootScript",
|
|
"folders"});
|
|
foreach (XmlNode rule in policy.ChildNodes) {
|
|
if (rule.Name != "folder") {
|
|
continue;
|
|
}
|
|
string folder = GetAttribute(rule, "name");
|
|
foreach (DictionaryEntry de in applicationFiles) {
|
|
string applicationName = (string) de.Key;
|
|
int substitutePosition = applicationName.LastIndexOf (".x86");
|
|
string manifestName = applicationName.Substring(0,substitutePosition)
|
|
+ ".manifest";
|
|
string shortAppName =
|
|
applicationName.Replace(distroDirectoryName, "");
|
|
if (shortAppName.StartsWith("\\" + folder)) {
|
|
if (manifestFiles.ContainsKey(manifestName)) {
|
|
AddFileNode(applicationName, position, true,
|
|
++position);
|
|
AddFileNode(manifestName, position, true, -1);
|
|
}
|
|
else {
|
|
AddFileNode(applicationName, position, true, -1);
|
|
}
|
|
position++;
|
|
}
|
|
}
|
|
foreach (DictionaryEntry de in otherFiles) {
|
|
string fileName = (string) de.Key;
|
|
string shortFileName = fileName.Replace(distroDirectoryName,
|
|
"");
|
|
if (shortFileName.StartsWith("\\" + folder) &&
|
|
!shortFileName.EndsWith(kernelFileName)) {
|
|
AddFileNode(fileName, position++, true, -1);
|
|
}
|
|
}
|
|
// NB - we don't copy a manifest unless we've associated it with an
|
|
// app
|
|
}
|
|
}
|
|
|
|
// Given only a list of drivers, a list of files, and some policy, we need
|
|
// to create a system manifest. This is how we do it for now.
|
|
public void InstallPolicies()
|
|
{
|
|
// build the file list that goes into the manifest
|
|
BuildFileList();
|
|
|
|
// set up the initConfig tag and append the file list to it
|
|
XmlNode initConfig = GetChild(systemPolicy, "initConfig");
|
|
XmlNode newInitConfig = systemManifest.ImportNode(initConfig, true);
|
|
newInitConfig.AppendChild(fileList);
|
|
|
|
// get the namingConventions
|
|
XmlNode namingConventions = GetChild(systemPolicy, "namingConventions");
|
|
XmlNode newNaming = systemManifest.ImportNode(namingConventions, true);
|
|
|
|
// get process grouping data
|
|
XmlNode processConfig = GetChild(systemPolicy, "processConfig");
|
|
XmlNode newProcessConfig = null;
|
|
if (processConfig != null) {
|
|
newProcessConfig = systemManifest.ImportNode(processConfig, true);
|
|
}
|
|
|
|
// get service configurations
|
|
XmlNode serviceConfig = GetChild(systemPolicy, "serviceConfig");
|
|
XmlNode newServiceConfig = systemManifest.ImportNode(serviceConfig,
|
|
true);
|
|
|
|
// apply the imperative policy to the Driver Registry (this prunes it)
|
|
ApplyPolicyToRegistry();
|
|
|
|
// add everything to the manifest
|
|
manifestRoot.AppendChild(drivers);
|
|
manifestRoot.AppendChild(newNaming);
|
|
manifestRoot.AppendChild(newServiceConfig);
|
|
if (newProcessConfig != null) {
|
|
manifestRoot.AppendChild(newProcessConfig);
|
|
}
|
|
manifestRoot.AppendChild(newInitConfig);
|
|
}
|
|
|
|
// When everything is set, we still need the Singboot.ini
|
|
// file. This is how we make it:
|
|
public void MakeIniFile(string distrodir)
|
|
{
|
|
StreamWriter stream = new StreamWriter(iniFileName, false);
|
|
|
|
string ruler = "########################################";
|
|
stream.WriteLine(ruler);
|
|
stream.WriteLine("# SingBoot.ini");
|
|
stream.WriteLine("# Build Date: {0}", System.DateTime.Now.ToString());
|
|
stream.WriteLine(ruler);
|
|
|
|
MD5 md5 = MD5.Create();
|
|
|
|
foreach (XmlNode file in fileList) {
|
|
string installedFileName = GetAttribute(file, "distroName");
|
|
string originalFileName =
|
|
distrodir + installedFileName.Replace("/", "\\");
|
|
|
|
FileInfo fileInfo = new FileInfo(originalFileName);
|
|
using (FileStream fs = fileInfo.OpenRead()) {
|
|
byte [] hash = md5.ComputeHash(fs);
|
|
stream.Write("Hash-MD5=");
|
|
foreach (byte b in hash) {
|
|
stream.Write("{0:x2}", b);
|
|
}
|
|
}
|
|
|
|
stream.WriteLine(" Size={0} Path={1}",
|
|
fileInfo.Length, installedFileName);
|
|
}
|
|
// NB Trailing ruler helps identify the end of file
|
|
stream.WriteLine(ruler);
|
|
stream.Close();
|
|
}
|
|
|
|
// Print the correct command line args for the program
|
|
static void PrintUsage()
|
|
{
|
|
Console.WriteLine(
|
|
"Usage:\n" +
|
|
" distrobuilder [options]\n" +
|
|
"Options:\n" +
|
|
" /dir:<path> - Set distribution root directory\n" +
|
|
" /out:<file> - Set name of output system manifest.\n" +
|
|
" /policy:<file> - Set input system policy file.\n" +
|
|
" /desc:<desc> - Distribution description.\n" +
|
|
" /ini:<ini> - Set output boot.ini file.\n" +
|
|
"Summary:\n" +
|
|
" Builds a system manifest for a collection of application manifests and\n" +
|
|
" a file list..\n" +
|
|
"");
|
|
}
|
|
|
|
// main entry point:
|
|
static void Main(string[] args)
|
|
{
|
|
string outfile = null;
|
|
string policyfile = null;
|
|
string distrodir = null;
|
|
string distrodesc = null;
|
|
string inifile = null;
|
|
|
|
// parse the cmdline
|
|
foreach (string arg in args) {
|
|
if (arg.StartsWith("/dir:")) {
|
|
distrodir = arg.Remove(0,5);
|
|
}
|
|
else if (arg.StartsWith("/out:")) {
|
|
outfile = arg.Remove(0,5);
|
|
}
|
|
else if (arg.StartsWith("/policy:")) {
|
|
policyfile = arg.Remove(0,8);
|
|
}
|
|
else if (arg.StartsWith("/desc:")) {
|
|
distrodesc = arg.Remove(0,6);
|
|
}
|
|
else if (arg.StartsWith("/ini:")) {
|
|
inifile = arg.Remove(0,5);
|
|
}
|
|
}
|
|
|
|
if (policyfile == null || outfile == null || distrodesc == null ||
|
|
distrodir == null || inifile == null) {
|
|
Console.WriteLine("Error - Invalid Command Line");
|
|
PrintUsage();
|
|
return;
|
|
}
|
|
|
|
// check all input files
|
|
if (!File.Exists(policyfile)) {
|
|
Console.WriteLine("Error: file '{0}' not found\n", policyfile);
|
|
return;
|
|
}
|
|
if (!File.Exists(distrodesc)) {
|
|
Console.WriteLine("Error: file '{0}' not found\n", distrodesc);
|
|
return;
|
|
}
|
|
if (!Directory.Exists(distrodir)) {
|
|
Console.WriteLine("Error: directory '{0}' not found\n", distrodir);
|
|
return;
|
|
}
|
|
|
|
DistroBuilder p = new DistroBuilder(
|
|
policyfile, outfile, inifile, distrodir, distrodesc);
|
|
|
|
// first aggregate all drivers into a <DriverRegistry>
|
|
p.CreateDriverRegistry();
|
|
// now apply policy to the system config
|
|
p.InstallPolicies();
|
|
// then output the manifest
|
|
p.PrintManifestFile();
|
|
// lastly make the ini file
|
|
p.MakeIniFile(distrodir);
|
|
}
|
|
}
|