/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: RamDisk/ClientManager/RamDiskClientManager.sg // // Note: // using Microsoft.SingSharp; using Microsoft.Singularity.Channels; using Microsoft.Singularity.Directory; using Microsoft.Singularity.Configuration; using Microsoft.Singularity.ServiceManager; using Microsoft.Singularity.Services.RamDisk.Contracts; using System; using System.Collections; using System.Collections.Specialized; using System.IO; using System.Text; using FileSystem.Utils; using DirectoryErrorCode = Microsoft.Singularity.Directory.ErrorCode; [assembly: Microsoft.Singularity.Security.ApplicationPublisherAttribute("singularity.microsoft.com")] [assembly: Microsoft.Singularity.Security.AssertPrivilegeAttribute("$register-privilege.localhost")] namespace Microsoft.Singularity.Services.RamDisk.ClientManager { [Category("Service")] internal class ServiceParameters { [CustomEndpoint] public readonly TRef ControlEndpointRef; [CustomEndpoint] public readonly TRef EventEndpointRef; reflective private ServiceParameters(); } public class ClientManager { private static SortedList! clientsByDiskName = new SortedList(); private class RamDiskErrorException : Exception { private RamDiskContractErrorCode errorCode; internal RamDiskErrorException(RamDiskContractErrorCode errorCode) { this.errorCode = errorCode; } internal RamDiskContractErrorCode ErrorCode { get { return errorCode; } } } internal class Client { TRef tref; string! diskName; ulong sizeBytes; internal Client( [Claims] RamDiskClientContract.Imp! imp, string! diskName, ulong sizeBytes ) { this.tref = new TRef(imp); this.diskName = diskName; this.sizeBytes = sizeBytes; } internal RamDiskClientContract.Imp! Acquire() { return tref.Acquire(); } internal void Release([Claims] RamDiskClientContract.Imp! imp) { tref.Release(imp); } internal string! DiskName { get { return this.diskName; } } internal ulong SizeBytes { get { return this.sizeBytes; } } } internal static void AddClient(Client! newClient) { clientsByDiskName.Add(newClient.DiskName, newClient); } internal static Client GetClientByDiskName(string! diskName) { if (clientsByDiskName.Contains(diskName)) { return (Client)clientsByDiskName[diskName]; } else { return null; } } internal static void DeleteClient(Client! closedClient) { clientsByDiskName.Remove(closedClient.DiskName); } internal static bool ClientIsClosed(Client! client) { RamDiskClientContract.Imp:Running! clientImp = client.Acquire(); bool closed = false; switch receive { case clientImp.ChannelClosed(): closed = true; break; case timeout: break; } client.Release(clientImp); return closed; } private static RamDiskContractErrorCode DoCreate(ulong sizeBytes, out string! outDiskName) { outDiskName = ""; string! diskName; ServiceProviderContract.Exp! serviceExp; try { GetNextDiskPath(out diskName, out serviceExp); } catch (RamDiskErrorException ex) { Dbg("FAILED to get next disk path."); return ex.ErrorCode; } RamDiskClientContract.Imp! clientImp; RamDiskClientContract.Exp! clientExp; RamDiskClientContract.NewChannel(out clientImp, out clientExp); string[] args = { "RamDisk" }; Process process; try { process = new Process(args); } catch (Exception ex) { Dbg("FAILED to create worker process: " + ex.Message); return RamDiskContractErrorCode.InternalError; } try { process.SetStartupEndpoint(0, serviceExp); process.SetStartupEndpoint(1, clientExp); process.Start(); } catch (Exception ex) { Dbg("FAILED to start worker process: " + ex.Message); process.Dispose(true); return RamDiskContractErrorCode.InternalError; } clientImp.SendCreate(Bitter.FromString2(diskName), sizeBytes); switch receive { case clientImp.Success(): AddClient(new Client(clientImp, diskName, sizeBytes)); outDiskName = diskName; return RamDiskContractErrorCode.NoError; case clientImp.ChannelClosed(): Dbg("FAILED to create ramdisk. Worker process closed channel."); delete clientImp; return RamDiskContractErrorCode.InternalError; case clientImp.Fail(RamDiskContractErrorCode reason): Dbg("FAILED to create ramdisk. Worker process sent 'Fail' message, error = " + reason); delete clientImp; return reason; } } private static void DoDestroy( RamDiskControlContract.Exp:Ready! controller, [Claims] char[]! in ExHeap diskNameHeap, bool force ) { string! diskName = PathUtils.ToPath(Bitter.ToString2(diskNameHeap)); delete diskNameHeap; Client client = GetClientByDiskName(diskName); if (client == null) { controller.SendFail(RamDiskContractErrorCode.DoesNotExist); return; } else if (ClientIsClosed(client)) { controller.SendSuccess(); return; } RamDiskClientContract.Imp! clientImp = client.Acquire(); clientImp.SendDestroy(force); switch receive { case clientImp.Success(): client.Release(clientImp); DeleteClient(client); try { DeregisterDevice(diskName); } catch (RamDiskErrorException ex) { controller.SendFail(ex.ErrorCode); return; } controller.SendSuccess(); break; case clientImp.Fail(reason): controller.SendFail(reason); client.Release(clientImp); break; case clientImp.ChannelClosed(): controller.SendFail(RamDiskContractErrorCode.InternalError); client.Release(clientImp); DeleteClient(client); break; } } private static void ServiceClients(ServiceProcessContract.Exp! svcontrol) { ESet controllers = new ESet(); svcontrol.SendStartSucceeded(); bool run = true; while (run) { switch receive { case svcontrol.Connect(expath, candidate): if (expath != null) { // This service does not use subpaths. Reject request. delete expath; delete candidate; svcontrol.SendNakConnect(ErrorCode.NotFound, null); break; } RamDiskControlContract.Exp controller = candidate as RamDiskControlContract.Exp; if (controller != null) { controller.SendSuccess(); controllers.Add(controller); svcontrol.SendAckConnect(); } else { delete candidate; svcontrol.SendNakConnect(ErrorCode.ContractNotSupported, null); } break; case svcontrol.Knock(): svcontrol.SendAlive(); break; case svcontrol.Stop(): // -XXX- Need to deal with this. Either send AckStop and exit, or send Busy // -XXX- because you can't cleanly stop right now. DebugStub.WriteLine("RamDiskService: Clean shutdown is not supported -- bailing!"); run = false; svcontrol.SendAckStop(); break; case svcontrol.ChannelClosed(): run = false; break; case controller.Create(diskSizeBytes) in controllers: { string! diskName; RamDiskContractErrorCode result = DoCreate(diskSizeBytes, out diskName); if (result == RamDiskContractErrorCode.NoError) { char[]! in ExHeap exdiskName = Bitter.FromString2(diskName); controller.SendCreateSuccess(exdiskName); } else { controller.SendFail(result); } controllers.Add(controller); break; } case controller.Destroy(diskName, force) in controllers: DoDestroy(controller, diskName, force); controllers.Add(controller); break; case controller.ChannelClosed() in controllers: delete controller; break; } } controllers.Dispose(); } internal static int AppMain(ServiceParameters! parameters) { if (parameters.ControlEndpointRef == null) throw new Exception("The parent process did not provide a ServiceProcessContract channel."); ServiceProcessContract.Exp! svcontrol = parameters.ControlEndpointRef.Acquire(); try { ServiceClients(svcontrol); return 0; } finally { delete svcontrol; } } static int nextDiskNumber = 0; static string ramDiskPathPrefix = "/dev/ramdisk"; private static void GetNextDiskPath(out string! diskName, out ServiceProviderContract.Exp! expService) { // Create a new directory entry for the disk DirectoryServiceContract.Imp ns = DirectoryService.NewClientEndpoint(); try { while (true) { ServiceProviderContract.Imp! imp; ServiceProviderContract.Exp! exp; ServiceProviderContract.NewChannel(out imp, out exp); diskName = ramDiskPathPrefix + nextDiskNumber.ToString(); ns.SendRegister(Bitter.FromString2(diskName), imp); switch receive { case ns.AckRegister(): nextDiskNumber++; expService = exp; return; case ns.NakRegister(nakImp, error): // If the error is AlreadyExists, recover by trying next disk number, else fail if (error == Microsoft.Singularity.Directory.ErrorCode.AlreadyExists) { nextDiskNumber++; } else { throw new RamDiskErrorException(DirectoryErrorToRamDiskError(error)); } delete exp; delete nakImp; break; //case unsatisfiable: // resources.service.Release(exp); // throw new Exception("unsatisfiable registering RAM disk"); } } } finally { delete ns; } } private static void DeregisterDevice(string! diskName) { // Deregister the directory entry DirectoryServiceContract.Imp ns = DirectoryService.NewClientEndpoint(); ns.SendDeregister(Bitter.FromString2(diskName)); try { switch receive { case ns.AckDeregister(serviceImp): delete serviceImp; break; case ns.NakDeregister(error): throw new RamDiskErrorException(DirectoryErrorToRamDiskError(error)); case unsatisfiable: throw new Exception("unsatisfiable deregistering RAM disk"); } } finally { delete ns; } } private static RamDiskContractErrorCode DirectoryErrorToRamDiskError(DirectoryErrorCode error) { switch (error) { case DirectoryErrorCode.NoError: return RamDiskContractErrorCode.NoError; case DirectoryErrorCode.AccessDenied: return RamDiskContractErrorCode.AccessDenied; case DirectoryErrorCode.CapacityReached: return RamDiskContractErrorCode.CapacityReached; case DirectoryErrorCode.InsufficientResources: return RamDiskContractErrorCode.InsufficientResources; case DirectoryErrorCode.DirectoryFull: return RamDiskContractErrorCode.DirectoryFull; case DirectoryErrorCode.IsOpen: return RamDiskContractErrorCode.IsOpen; default: return RamDiskContractErrorCode.InternalError; } } const string dbgprefix = "RamDiskService: "; static void Dbg(string! line) { DebugStub.WriteLine(dbgprefix + line); } static void Dbg(string! format, params object[]! args) { Dbg(String.Format(format, args)); } } }