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

646 lines
25 KiB
Plaintext
Raw Normal View History

2008-03-05 09:52:00 -05:00
////////////////////////////////////////////////////////////////////////////////
//
// 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;
2008-11-17 18:29:00 -05:00
using Microsoft.SingSharp;
2008-03-05 09:52:00 -05:00
using Microsoft.Singularity;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Io;
2008-11-17 18:29:00 -05:00
using Microsoft.Singularity.ServiceManager;
using Microsoft.Singularity.Services;
using Microsoft.Singularity.Configuration;
2008-03-05 09:52:00 -05:00
using Smb.PublicChannels;
using Smb.PrivateChannels;
namespace Smb.ClientManager
{
2008-11-17 18:29:00 -05:00
[Category("Service")]
internal class ServiceParameters
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
[CustomEndpoint]
public readonly TRef<ServiceProcessContract.Exp:Starting> ControlEndpointRef;
[CustomEndpoint]
public readonly TRef<ServiceEventContract.Imp:Ready> EventEndpointRef;
reflective private ServiceParameters();
}
class Manager : ITracked
{
internal static int AppMain(ServiceParameters! parameters)
2008-03-05 09:52:00 -05:00
{
2008-11-17 18:29:00 -05:00
assert parameters.ControlEndpointRef != null;
assert parameters.EventEndpointRef != null;
ServiceProcessContract.Exp:Starting! svcontrol = parameters.ControlEndpointRef.Acquire();
ServiceEventContract.Imp:Ready! svevent = parameters.EventEndpointRef.Acquire();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
Manager! manager = new Manager(svcontrol, svevent);
try {
manager.Run();
}
finally {
manager.Dispose();
}
return 0;
}
Manager(
[Claims]ServiceProcessContract.Exp:Starting! svcontrol,
[Claims]ServiceEventContract.Imp:Ready! svevent)
{
this.svcontrol = svcontrol;
this.svevent = svevent;
}
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
#region Data Fields
readonly ESet<SmbClientManagerContract.Exp:Ready>! managers = new ESet<SmbClientManagerContract.Exp:Ready>();
readonly EMap<SmbClientControllerContract.Imp:Configuring, ClientProcess!>! clientsConfiguring =
new EMap<SmbClientControllerContract.Imp:Configuring, ClientProcess!>();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
readonly EMap<SmbMuxNotifier.Exp:Ready, ClientProcess!>! notifiers =
new EMap<SmbMuxNotifier.Exp:Ready, ClientProcess!>();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
readonly ServiceProcessContract.Exp! svcontrol;
readonly ServiceEventContract.Imp! svevent;
#endregion
public void Dispose()
{
DebugLine("Service has terminated.");
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
foreach (ClientProcess! client in _clients.Values)
{
TerminateClient(client, false);
}
_clients.Clear();
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
delete managers;
delete clientsConfiguring;
delete notifiers;
delete svcontrol;
delete svevent;
//managers.Dispose();
//notifiers.Dispose();
//clientsConfiguring.Dispose();
}
void Run()
{
expose (this) {
svcontrol.SendStartSucceeded();
for (;;)
{
switch receive
{
#region Event handlers for service control channel, ServiceProcessContract
case svcontrol.Knock():
DebugLine("Received 'Knock' from Service Manager");
svcontrol.SendAlive();
break;
case svcontrol.Stop():
DebugLine("Received 'Stop' from Service Manager");
svcontrol.SendAckStop();
return;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
case svcontrol.Connect(char[] in ExHeap expath, ServiceContract.Exp:Start! exp):
{
if (expath != null) {
string! path = Bitter.ToString2(expath);
delete expath;
DebugLine("A client wants to connect, using subpath '{0}'. This service does not support subpaths; rejecting client.", path);
delete exp;
svcontrol.SendNakConnect(ErrorCode.Unknown, null);
break;
}
SmbClientManagerContract.Exp manager = exp as SmbClientManagerContract.Exp;
if (manager != null) {
DebugLine("A client has connected using SmbClientManagerContract.");
manager.SendSuccess();
managers.Add(manager);
svcontrol.SendAckConnect();
}
else {
DebugLine("A client wants to connect, but is using an unsupported contract.");
delete exp;
svcontrol.SendNakConnect(ErrorCode.ContractNotSupported, null);
}
break;
}
case svcontrol.ChannelClosed():
// Uh oh!
DebugLine("Service Manager has closed its control channel! Not good at all!");
return;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
#endregion
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);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
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;
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
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);
2008-03-05 09:52:00 -05:00
2008-11-17 18:29:00 -05:00
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:
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 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:
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;
}
}
}
2008-03-05 09:52:00 -05:00
}
/// <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>
2008-11-17 18:29:00 -05:00
/// </summary>
2008-03-05 09:52:00 -05:00
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:
2008-11-17 18:29:00 -05:00
SmbClientControllerContract.Imp! controller = ((!)client.Controller).Acquire();
2008-03-05 09:52:00 -05:00
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.
2008-11-17 18:29:00 -05:00
if (remove)
2008-03-05 09:52:00 -05:00
_clients.Remove(client.MountPath);
}
static void DebugLine(string! line)
{
2008-11-17 18:29:00 -05:00
DebugStub.WriteLine(line);
2008-03-05 09:52:00 -05:00
}
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));
}
2008-11-17 18:29:00 -05:00
void ITracked.Expose() {}
void ITracked.UnExpose() {}
void ITracked.Acquire() {}
void ITracked.Release() {}
2008-03-05 09:52:00 -05:00
}
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;
}
}
}