596 lines
21 KiB
Plaintext
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;
|
|
}
|
|
}
|
|
|
|
}
|
|
|