singrdk/base/Services/Smb/ClientService/SmbClientManager.sg

596 lines
21 KiB
Plaintext

////////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: Services/Smb/ClientManager/SmbClientManager.sg
//
// Note:
//
// This file contains most of the implementation of the SMB Client Manager Service.
// This service is a system-wide singleton process, started during system boot by
// the Service Manager Service (SMS). (In the future, it may make sense for one
// instance of this service to exist per session. However, that's for future study.)
//
// This service registers a single control channel, at /dev/smb-client-manager.
// Clients, such as the command-line tool net.exe (Services/Smb/Control) open
// this channel and use it to send control requests and queries to this service.
// These requests allow the user to mount remote SMB filesystems at local namespaces
// (creates a mapping), unmount them, enumerate them, and query their status.
//
// This service is entirely single-threaded. Some requests received from the control
// channel are processed immediately (synchronously); the results are sent back to
// the originator of the request without performing any blocking I/O or waiting for
// any long period of time. A few other requests are asynchronous; when the service
// receives such a control request, it allocates memory in the local (non-exchange)
// heap to track the status of the request, and future channel messages advance the
// state of the request.
//
using System;
using System.Collections;
using System.Diagnostics;
using Microsoft.Contracts;
using Microsoft.Singularity;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Io;
using Microsoft.SingSharp;
using Smb.PublicChannels;
using Smb.PrivateChannels;
[assembly: Microsoft.Singularity.Security.ApplicationPublisherAttribute("singularity.microsoft.com")]
[assembly: Microsoft.Singularity.Security.AssertPrivilegeAttribute("$register-privilege.localhost")]
namespace Smb.ClientManager
{
class Manager
{
public static int Main(string[] args)
{
//
// Register the control endpoint for this service.
//
ServiceProviderContract.Imp! provider_imp;
ServiceProviderContract.Exp! controlprovider;
ServiceProviderContract.NewChannel(out provider_imp, out controlprovider);
DirectoryServiceContract.Imp! rootds = DirectoryService.NewClientEndpoint();
rootds.SendRegister(Bitter.FromString2(SmbClientManagerContract.ManagerControlPath), provider_imp);
switch receive
{
case rootds.AckRegister():
delete rootds;
break;
case rootds.NakRegister(ServiceProviderContract.Imp:Start imp, ErrorCode error):
DebugLine("The service failed to register its control channel.");
DebugLine(" Control channel path: " + SmbClientManagerContract.ManagerControlPath);
DebugLine(" Error code: " + error.ToString());
DebugLine("Service will now terminate.");
delete rootds;
delete imp;
delete controlprovider;
return -1;
}
ESet<SmbClientManagerContract.Exp:Ready>! managers = new ESet<SmbClientManagerContract.Exp:Ready>();
EMap<SmbClientControllerContract.Imp:Configuring, ClientProcess!>! clientsConfiguring =
new EMap<SmbClientControllerContract.Imp:Configuring, ClientProcess!>();
EMap<SmbMuxNotifier.Exp:Ready, ClientProcess!>! notifiers =
new EMap<SmbMuxNotifier.Exp:Ready, ClientProcess!>();
DebugLine("SMB service is ready, listening on control channel: " + SmbClientManagerContract.ManagerControlPath);
for (;;)
{
switch receive
{
case controlprovider.Connect(ServiceContract.Exp:Start! exp):
SmbClientManagerContract.Exp manager = exp as SmbClientManagerContract.Exp;
if (manager != null) {
manager.SendSuccess();
managers.Add(manager);
controlprovider.SendAckConnect();
} else {
controlprovider.SendNackConnect(exp);
}
break;
case manager.CreateClient(SmbClientConfig exconfig) in managers:
// This is a request to create a new SMB client process and start it.
string! mountPath = Bitter.ToString2(exconfig.MountPath);
if (TraceSwitches.ShowManagerMessages) {
DebugLine("CreateClient: mountpath = '{0}'", mountPath);
DebugLine(" UncPath: " + Bitter.ToString2(exconfig.UncPath));
}
// DebugStub.Break();
// Check to see if we already have a client using the same mount point.
// It's much better to detect this now, rather than to spin up a client
// process and have it discover the error.
ClientProcess client = FindClientByMountPath(mountPath);
if (client != null) {
exconfig.Dispose();
manager.SendRequestFailed(SmbErrorCode.MountPathAlreadyExists, null);
managers.Add(manager);
break;
}
SmbClientControllerContract.Imp! controller;
SmbClientControllerContract.Exp! controller_exp;
SmbClientControllerContract.NewChannel(out controller, out controller_exp);
SmbMuxNotifier.Imp! notifier_imp;
SmbMuxNotifier.Exp! notifier_exp;
SmbMuxNotifier.NewChannel(out notifier_imp, out notifier_exp);
Process process;
try {
string[] process_args = { "smbclient" };
process = new Process(process_args);
} catch (Exception ex) {
DebugLine("FAILED to create smbclient process: " + ex.Message);
delete controller;
delete controller_exp;
delete notifier_imp;
delete notifier_exp;
manager.SendRequestFailed(SmbErrorCode.InternalError, Bitter.FromString2("Failed to create process: " + ex.Message));
managers.Add(manager);
break;
}
process.SetStartupEndpoint(0, controller_exp);
process.SetStartupEndpoint(1, notifier_imp);
if (TraceSwitches.ShowProcessActivity)
DebugLine("Starting client process...");
process.Start();
if (TraceSwitches.ShowProcessActivity)
DebugLine("Process started. Sending startup parameters...");
client = new ClientProcess(mountPath, exconfig, process);
client.State = ClientState.Starting;
client.StartingManagerEndpoint = new TRef<SmbClientManagerContract.Exp:CreatingClient>(manager);
client.UncPath = Bitter.ToString2(exconfig.UncPath);
client.CredentialsName = Bitter.ToString2(exconfig.CredentialsName);
client.Tag = Bitter.ToString2(exconfig.Tag);
client.ConnectionStatus = SmbClientConnectionStatus.Disconnected;
client.ReasonDisconnected = SmbReasonDisconnected.Idle;
client.ServerOperatingSystem = null;
client.ServerDomainName = null;
client.ServerMachineName = null;
controller.SendConfigure(exconfig);
// Further progress will be made when we receive AckConnect or NakConnect from the
// client process (or a ChannelClosed).
notifiers.Add(notifier_exp, client);
clientsConfiguring.Add(controller, client);
_clients[mountPath] = client;
break;
case manager.QueryClientConfig(char[]! in ExHeap exclientId) in managers:
{
string! clientId = Bitter.ToString2(exclientId);
delete exclientId;
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received QueryClientConfig - id = " + clientId);
ClientProcess client = FindClientByMountPath(clientId);
if (client != null) {
SmbClientConfig config = new SmbClientConfig();
config.MountPath = Bitter.FromString2(client.MountPath);
config.UncPath = Bitter.FromString2(client.UncPath);
config.CredentialsName = Bitter.FromString2(client.CredentialsName);
config.Tag = Bitter.FromString2(client.Tag);
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Sending config report.");
manager.SendClientConfigReport(config);
} else {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" No such client.");
manager.SendRequestFailed(SmbErrorCode.MountPathNotFound, null);
}
managers.Add(manager);
break;
}
case manager.QueryClientStatus(char[]! in ExHeap exclientId) in managers:
{
string! clientId = Bitter.ToString2(exclientId);
delete exclientId;
if (TraceSwitches.ShowManagerMessages)
DebugLine("Received QueryClientStatus - id = " + clientId);
ClientProcess client = FindClientByMountPath(clientId);
if (client != null) {
SmbClientStatus status = new SmbClientStatus();
status.ConnectionStatus = client.ConnectionStatus;
status.ReasonDisconnected = client.ReasonDisconnected;
status.ServerOperatingSystem = Bitter.FromString(client.ServerOperatingSystem);
status.ServerDomainName = Bitter.FromString(client.ServerDomainName);
status.ServerMachineName = Bitter.FromString(client.ServerMachineName);
status.ActiveCredentialsName = Bitter.FromString(client.ActiveCredentialsName);
if (TraceSwitches.ShowManagerMessages)
DebugLine(" Sending status report.");
manager.SendClientStatusReport(status);
} else {
if (TraceSwitches.ShowManagerMessages)
DebugLine(" No such client.");
manager.SendRequestFailed(SmbErrorCode.MountPathNotFound, null);
}
managers.Add(manager);
break;
}
case controller.Ok() in clientsConfiguring ~> client:
if (TraceSwitches.ShowManagerMessages)
DebugLine(client, "Received AckConfigure from client process");
assert client.StartingManagerEndpoint != null;
SmbClientManagerContract.Exp! manager = client.StartingManagerEndpoint.Acquire();
client.StartingManagerEndpoint = null;
client.State = ClientState.Running;
client.Controller = new TRef<SmbClientControllerContract.Imp:Running>(controller);
if (TraceSwitches.ShowManagerMessages)
DebugLine("Sending Ok (for CreateClient) to manager.");
manager.SendOk();
managers.Add(manager);
break;
case controller.RequestFailed(error, errortext) in clientsConfiguring ~> client:
// One of the client processes that we started has indicated that it
// did not start successfully. Tear down the client process and send an
// error message to the manager that requested that the process to be created.
if (TraceSwitches.ShowManagerMessages || true)
DebugLine(client, "Received NakConfigure from client process: {0} {1}",
error.ToString(),
Bitter.ToString(errortext));
delete controller;
assert client.StartingManagerEndpoint != null;
SmbClientManagerContract.Exp! manager = client.StartingManagerEndpoint.Acquire();
client.StartingManagerEndpoint = null;
client.State = ClientState.Running;
client.Process.Stop();
client.Process.Join();
client.Process.Dispose(false);
manager.SendRequestFailed(error, errortext);
managers.Add(manager);
break;
case controller.ChannelClosed() in clientsConfiguring ~> client:
// A client process terminated before responding to the NakConnect message.
// Treat this like NakConnect without an error code.
if (TraceSwitches.ShowManagerMessages || true)
DebugLine(client, "Client process has closed its controller channel.");
assert client.StartingManagerEndpoint != null;
SmbClientManagerContract.Exp! manager = client.StartingManagerEndpoint.Acquire();
client.StartingManagerEndpoint = null;
client.State = ClientState.Running;
client.Process.Stop();
client.Process.Join();
client.Process.Dispose(false);
manager.SendRequestFailed(SmbErrorCode.InternalError,
Bitter.FromString2("The SMB client process failed to initialize."));
managers.Add(manager);
delete controller;
break;
case manager.StopClient(char[]! in ExHeap exmountPath) in managers:
string! mountPath = Util.ToStringDelete(exmountPath);
if (TraceSwitches.ShowManagerMessages)
DebugLine("StopClient - mount path = " + mountPath);
ClientProcess client = FindClientByMountPath(mountPath);
if (client != null)
{
switch (client.State)
{
case ClientState.Starting:
// This is extremely rude, of course.
DebugLine(" Client is Starting - will simply terminate the process.");
break;
case ClientState.Running:
assert client.Controller != null;
SmbClientControllerContract.Imp! controller = client.Controller.Acquire();
controller.SendStop();
// -XXX- This is synchronous for now!
DebugLine("Waiting to SMB client to respond to Stop request");
switch receive {
case controller.Ok():
break;
case controller.ChannelClosed():
break;
}
client.Controller.Release(controller);
break;
default:
DebugLine("ILLEGAL STATE FOR CLIENT PROCESS!");
break;
}
TerminateClient(client, true);
manager.SendOk();
} else {
if (TraceSwitches.ShowManagerMessages)
DebugLine("Cannot delete client -- there is no client with mount path = '{0}'", mountPath);
manager.SendRequestFailed(SmbErrorCode.MountPathNotFound, null);
}
managers.Add(manager);
break;
case controlprovider.ChannelClosed():
DebugLine("Control provider channel has closed.");
goto terminate;
case manager.EnumClients() in managers:
{
int count = _clients.Values.Count;
char[][]! in ExHeap mountPaths = new[ExHeap] char[][count];
int i = 0;
foreach (ClientProcess! client in _clients.Values)
{
if (i >= count)
break;
char[]! in ExHeap exmount = Bitter.FromString2(client.MountPath);
expose (mountPaths[i]) {
delete mountPaths[i];
mountPaths[i] = exmount;
}
i++;
}
manager.SendClientList(mountPaths);
managers.Add(manager);
break;
}
case manager.BindClient(char[]! in ExHeap exmountPath, ServiceContract.Exp:Start! exp) in managers:
{
string! mountPath = Util.ToStringDelete(exmountPath);
if (TraceSwitches.ShowManagerMessages)
DebugLine("BindClient - mount path = " + mountPath);
ClientProcess client = FindClientByMountPath(mountPath);
if (client != null)
{
switch (client.State)
{
case ClientState.Running:
assert client.Controller != null;
SmbClientControllerContract.Imp! controller = client.Controller.Acquire();
try {
controller.SendBind(exp);
// -XXX- Yes, I know this prevents SmbClientManager from receiving
// -XXX- any other messages. Need to deal with that.
switch receive {
case controller.Ok():
if (TraceSwitches.ShowManagerMessages)
DebugLine("Bind successful");
manager.SendOk();
break;
case controller.RequestFailed(error, optionalErrorText):
if (TraceSwitches.ShowManagerMessages)
DebugLine("SmbClient process rejected bind request");
manager.SendRequestFailed(error, optionalErrorText);
break;
case controller.ChannelClosed():
if (TraceSwitches.ShowManagerMessages)
DebugLine("SmbClient channel closed!");
manager.SendRequestFailed(SmbErrorCode.InternalError, null);
break;
}
} finally {
client.Controller.Release(controller);
}
break;
default:
if (TraceSwitches.ShowManagerMessages)
DebugLine("Client is not in a state that allows binding");
manager.SendRequestFailed(SmbErrorCode.InvalidState, null);
delete exp;
break;
}
}
else
{
delete exp;
manager.SendRequestFailed(SmbErrorCode.MountPathNotFound, null);
}
managers.Add(manager);
break;
}
case notifier.StatusChanged(SmbClientStatus status) in notifiers ~> client:
notifier.SendAck();
if (TraceSwitches.ShowStatusChanges)
DebugLine(client, "Received StatusChanged from client");
client.ConnectionStatus = status.ConnectionStatus;
client.ReasonDisconnected = status.ReasonDisconnected;
client.ServerOperatingSystem = Bitter.ToString(status.ServerOperatingSystem);
client.ServerMachineName = Bitter.ToString(status.ServerMachineName);
client.ServerDomainName = Bitter.ToString(status.ServerDomainName);
client.ActiveCredentialsName = Bitter.ToString(status.ActiveCredentialsName);
status.Dispose();
notifiers.Add(notifier, client);
break;
} // switch receive
}
terminate:
managers.Dispose();
delete controlprovider;
notifiers.Dispose();
clientsConfiguring.Dispose();
foreach (ClientProcess! client in _clients.Values)
{
TerminateClient(client, false);
}
_clients.Clear();
return 0;
}
/// <summary>
/// <para>
/// Contains the set of all ClientProcess instances that are managed by this service.
/// Aside from brief thread-local references to ClientProcess instances, this table
/// contains the only reference to those instances.
/// </para>
///
/// <para>
/// Each key is a string, and is the value of the MountPath field of the client
/// process; the keys are case sensitive. Each value is the corresponding
/// ClientProcess instance.
/// </para>
/// </summary>
static readonly Hashtable/*<String!,ClientProcess!>*/! _clients = new Hashtable();
static ClientProcess FindClientByMountPath(string! mountPath)
{
ClientProcess client = (ClientProcess)_clients[mountPath];
return client;
}
static void TerminateClient(ClientProcess! client, bool remove)
{
DebugLine("Terminating child process, pid=" + client.Process.Id);
client.Process.Stop();
client.Process.Join();
client.Process.Dispose(false);
switch (client.State)
{
case ClientState.Starting:
assert client.Controller == null;
break;
case ClientState.Running:
case ClientState.Stopping:
assert client.Controller != null;
SmbClientControllerContract.Imp! controller = client.Controller.Acquire();
delete controller;
break;
default:
DebugLine("INVALID STATE for client process! state=" + client.State.ToString());
break;
}
// This is conditional, because we don't want to modify the _clients collection
// while we're enumerating it.
if (remove)
_clients.Remove(client.MountPath);
}
static void DebugLine(string! line)
{
DebugStub.WriteLine("SMBMGR: " + line);
}
static void DebugLine(string! format, params object[]! args)
{
DebugLine(String.Format(format, args));
}
static void DebugLine(ClientProcess! client, string! line)
{
DebugStub.WriteLine(client.DebugPrefix + line);
}
static void DebugLine(ClientProcess! client, string! format, params object[]! args)
{
DebugLine(client, String.Format(format, args));
}
}
// This class mirrors the exchange type SmbClientConfig.
// It is for use within the process heap, rather than the exchange heap.
#if false
class ClientConfig
{
public string! MountPath;
public string! UncPath;
public string! UserName;
public string! Password;
}
#endif
class Util
{
public static void DumpException(Exception chain)
{
for (Exception ex = chain; ex != null; ex = ex.InnerException)
{
DebugStub.WriteLine(String.Format("{0}: {1}", ex.GetType().FullName, ex.Message));
}
}
public static string! ToStringDelete([Claims]char[]! in ExHeap exstring)
{
string! localstring = Bitter.ToString2(exstring);
delete exstring;
return localstring;
}
}
}