/// <summary> /// This method can be called by the state machine methods to request /// the state machine to run at Deadline. If Deadline is MinValue, the /// state machine will be run again immediately. /// </summary> public static void ScheduleRun(String reason, DateTime deadline) { // Remember the previous next run date. DateTime oldNextRunDate = m_nextRunDate; // Update the next run date. String ls = "WM run scheduled: " + reason + "."; if (deadline != DateTime.MinValue) { ls += " When: " + deadline + "."; if (deadline < m_nextRunDate) { m_nextRunDate = deadline; } } else { ls += " When: now."; m_nextRunDate = DateTime.MinValue; } KLogging.Log(ls); // If we modified the next run date, notify the timer thread, // unless we're running inside the SM, in which case we'll call // ScheduleTimerEvent() after the state machine has stabilized. if (!m_runningFlag && oldNextRunDate != m_nextRunDate) { ScheduleTimerEvent(); } }
/// <summary> /// Called to process the messages received by this window. /// </summary> protected override void WndProc(ref Message m) { if (m.Msg == KSyscalls.WM_COPYDATA) { KSyscalls.COPYDATASTRUCT cds = (KSyscalls.COPYDATASTRUCT)m.GetLParam(typeof(KSyscalls.COPYDATASTRUCT)); int msgID = cds.dwData.ToInt32(); // This is for us. if (msgID == Program.ImportKwsMsgID || msgID == Program.ForegroundMsgID) { // Retrieve the path argument. Clone the string to avoid // any potential trouble. String path = cds.lpData.Clone() as String; if (msgID == Program.ForegroundMsgID) { // FIXME. KLogging.Log("Received request to put process in the foreground."); } // This is our import workspace credentials message. else if (msgID == Program.ImportKwsMsgID) { KLogging.Log("Received request to import workspace from window procedure."); // Perform the import. Program.ImportKwsList(path); } } } base.WndProc(ref m); }
/// <summary> /// This method should be called when an unexpected failure occurs /// in the workspace. /// </summary> public void HandleMiscFailure(Exception ex) { WmSm.LockNotif(); KLogging.LogException(ex); // We cannot handle failures during task switches. We need the // task switches to succeed to recover from failures. if (m_taskSwitchFlag) { KBase.HandleException(ex, true); } // Increase the severity of the rebuild required if possible. if (m_cd.CurrentTask == KwsTask.Rebuild) { WorsenRebuild(KwsRebuildFlag.FlushKcdData | KwsRebuildFlag.FlushLocalData); } // Stop the workspace if required. SetUserTask(KwsTask.Stop); RequestTaskSwitch(KwsTask.Stop, ex); // Let the state machine sort it out. RequestRun("application failure"); WmSm.UnlockNotif(); }
/// <summary> /// Handle an ANP event received from the KCD. /// </summary> public void HandleKcdEvent(AnpMsg msg) { KLogging.Log("HandleAnpEvent() in kws " + m_kws.InternalID + ", status " + m_cd.MainStatus); // This is a permanent event. if (msg.ID > 0) { // Logic problem detected. if (msg.ID < m_ks.LastReceivedEventId) { BlameKcd(new Exception("received ANP event with bogus ID")); return; } // Store the event in the database. Mark it as unprocessed. m_kws.StoreKAnpEventInDb(msg, KwsAnpEventStatus.Unprocessed); // Update the information about the events. m_ks.NbUnprocessedEvent++; m_ks.LastReceivedEventId = msg.ID; m_kws.OnStateChange(WmStateChange.Permanent); } // If this is a transient event or the only unprocessed event, // dispatch it right away if possible. This is done so that single // incoming events are processed very quickly instead of waiting // for a future workspace state machine run. if (msg.ID == 0 || (m_ks.NbUnprocessedEvent == 1 && !WmKcdState.QuenchFlag && m_kws.IsOfflineCapable())) { DispatchKcdEvent(msg); } }
/// <summary> /// Process the messages received from the KCD, if any. /// </summary> private static void ProcessKcdMessages() { // If we were not notified, bail out quickly. if (!WmKcdState.EventFlag) { return; } // Clear the notification flag. WmKcdState.EventFlag = false; // Get the messages. List <KcdControlMsg> controlList = new List <KcdControlMsg>(); List <KcdAnpMsg> anpList = new List <KcdAnpMsg>(); Wm.KcdBroker.GetMessagesForWm(out controlList, out anpList); KLogging.Log("ProcessKcdMessages(), anpList.Count = " + anpList.Count); // Process the messages. foreach (KcdControlMsg m in controlList) { ProcessKcdControlMsg(m); } foreach (KcdAnpMsg m in anpList) { ProcessKcdAnpMsg(m); } }
/// <summary> /// This method is called when the workspace manager has stopped. /// The WM is serialized and the application is told to quit. /// </summary> private static void HandlePostStop() { KLogging.Log("WmSm: " + KwmStrings.Kwm + " has stopped."); Wm.MainStatus = WmMainStatus.Stopped; Wm.Serialize(); Program.RequestAppExit(); }
/// <summary> /// Called when the login reply is received. /// </summary> private void HandleConnectKwsReply(KcdQuery query) { KLogging.Log("Got login reply, kws " + m_kws.InternalID + ", status " + m_kws.Cd.MainStatus); Debug.Assert(m_ks.LoginStatus == KwsLoginStatus.LoggingIn); // This is the standard login reply. if (query.Res.Type == KAnp.KANP_RES_KWS_CONNECT_KWS) { // Get the provided information. KwsConnectRes r = new KwsConnectRes(query.Res); KLogging.Log(m_currentStep + " login step: " + r.ErrMsg); // Dispatch. if (r.Code == KAnp.KANP_KWS_LOGIN_OK) { HandleConnectKwsSuccess(r); } else if (r.Code == KAnp.KANP_KWS_LOGIN_BAD_PWD_OR_TICKET) { HandleBadPwdOrTicket(r); } else { HandleLoginFailure(TranslateKcdLoginStatusCode(r.Code), new Exception(r.ErrMsg)); } } // This is an unexpected reply. else { HandleLoginFailure(KwsLoginResult.MiscKcdError, EAnpException.FromKAnpReply(query.Res)); } }
/// <summary> /// "Import" a workspace that already exists in the KWM. /// </summary> private static void ImportExistingKws(Workspace kws, KwsCredentials creds) { KwsTask task = kws.Cd.CurrentTask; KLogging.Log("Import of existing workspace " + kws.InternalID + " with task " + task + " requested."); // We can only import workspaces that are stopped or working // offline. if (task != KwsTask.Stop && task != KwsTask.WorkOffline) { KLogging.Log("Skipping import due to incompatible task."); return; } // Update the credentials unless they were already accepted. if (kws.Cd.KcdState.LoginResult != KwsLoginResult.Accepted) { KLogging.Log("Updating workspace credentials."); creds.PublicFlag = kws.Cd.Credentials.PublicFlag; kws.Cd.Credentials = creds; } // Make the workspace work online. kws.Sm.RequestTaskSwitch(KwsTask.WorkOnline); // The workspace state has changed. kws.OnStateChange(WmStateChange.Permanent); }
/// <summary> /// Upgrade the database schema if required. /// </summary> private void UpgradeDb(UInt32 version) { KLogging.Log("Upgrading database schema from version " + version); m_db.BeginTransaction(); UpdateDbVersion(LatestDbVersion); m_db.CommitTransaction(); }
/// <summary> /// Called when the login fails with KANP_KWS_LOGIN_BAD_PWD_OR_TICKET. /// </summary> private void HandleBadPwdOrTicket(KwsConnectRes r) { // Remember that the workspace is secure and if a password is available. m_kws.Cd.Credentials.SecureFlag = r.SecureFlag; m_ks.PwdPresentFlag = r.PwdOnKcdFlag; m_kws.OnStateChange(WmStateChange.Permanent); // The cached step has failed. if (m_currentStep == KwsLoginStep.Cached) { // Only the cached step was allowed. We're done. if (m_loginType == KwsLoginType.Cached) { HandleLoginFailure(KwsLoginResult.BadSecurityCreds, new Exception("security credentials refused")); return; } // We can perform the ticket step. if (KwmCfg.Cur.CanLoginOnKps()) { HandleTicketLoginStep(); return; } } // The ticket step has failed. else if (m_currentStep == KwsLoginStep.Ticket) { // Log the ticket refusal string. KLogging.Log("Ticket refused: " + r.ErrMsg); } // There is no password on the KCD. if (!m_ks.PwdPresentFlag) { HandleLoginFailure(KwsLoginResult.BadSecurityCreds, new Exception("a password must be assigned to you")); return; } // The password provided is bad. if (m_currentStep == KwsLoginStep.Pwd) { // Execute that asynchronously since we want our state to be // predictable. if (OnSetLoginPwdRefused != null) { KBase.ExecInUI(OnSetLoginPwdRefused); } } // We need a password. HandlePwdLoginStep(); }
/// <summary> /// Import or join a workspace that does not exist in the KWM. /// </summary> private static void ImportNewKws(KwsCredentials creds) { KLogging.Log("Importing new workspace " + creds.KwsName + "."); // Create the workspace. Workspace kws = Wm.CreateWorkspace(creds); // Set its main status. kws.Cd.MainStatus = KwsMainStatus.Good; // Make the workspace work online. kws.Sm.RequestTaskSwitch(KwsTask.WorkOnline); }
/// <summary> /// Process an ANP message received from the KCD. /// </summary> private static void ProcessKcdAnpMsg(KcdAnpMsg m) { KLogging.Log("ProcessKcdAnpMsg() called"); // We're stopping. Bail out. if (StopFlag) { return; } // The KCD specified does not exist. Bail out. if (!Wm.KcdTree.ContainsKey(m.KcdID)) { return; } // The KCD is not connected. Bail out. WmKcd kcd = Wm.KcdTree[m.KcdID]; if (kcd.ConnStatus != KcdConnStatus.Connected) { return; } // Process the message according to its type. try { if (m.Msg.Type == KAnp.KANP_RES_FAIL && m.Msg.Elements[0].UInt32 == KAnp.KANP_RES_FAIL_BACKEND) { throw new Exception("backend error: " + m.Msg.Elements[1].String); } else if (m.IsReply()) { ProcessKcdAnpReply(kcd, m.Msg); } else if (m.IsEvent()) { ProcessKcdAnpEvent(kcd, m.Msg); } else { throw new Exception("received unexpected ANP message type (" + m.Msg.Type + ")"); } } catch (Exception ex) { HandleTroublesomeKcd(kcd, ex); } }
/// <summary> /// Stop the KMOD thread and kill all pending and executing /// transactions. /// </summary> private void Killall(Exception ex) { // Get the list of failing transactions and clear the current data // structures. List <KmodTransaction> list = new List <KmodTransaction>(); list.AddRange(m_transactionQueue); m_transactionQueue.Clear(); if (m_curTransaction != null) { list.Add(m_curTransaction); m_curTransaction = null; } // Mark the transactions as failing. foreach (KmodTransaction transaction in list) { transaction.Status = KmodTransactionStatus.Failing; } // Stop the thread if it is running. StopKmodThread(); // Kill all transactions. foreach (KmodTransaction transaction in list) { if (transaction.Status != KmodTransactionStatus.Failing) { continue; } transaction.Status = KmodTransactionStatus.Finished; transaction.Ex = ex; if (ex != null) { KLogging.LogException(ex); } try { transaction.Run(KmodTransactionReason.Error); } catch (Exception ex2) { KBase.HandleException(ex2, true); } } }
/// <summary> /// Mark the KCD as disconnected, add a control message for the KCD in /// the control message list and add the KCD to the disconnected list. /// </summary> private void HandleDisconnectedKcd(KcdThreadHost k, Exception ex) { if (ex != null) { KLogging.Log(2, "KCD " + k.KcdID.Host + " exception: " + ex.Message); } if (k.Tunnel != null) { k.Tunnel.Disconnect(); } k.ConnStatus = KcdThreadConnStatus.Disconnected; k.Ex = new EAnpExKcdConn(); m_toWmControlMsgList.Add(new KcdDisconnectionNotice(k.KcdID, k.Ex)); m_disconnectedList.Add(k); }
/// <summary> /// Create a listening socket and spawn ktlstunnel process. /// </summary> public void BeginTls(string extraParams) { IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 0); Sock = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); Sock.Bind(endPoint); Sock.Listen(1); // Create a logging dir for ktlstunnel, if it does not exist. if (!Directory.Exists(KwmPath.GetKtlstunnelLogFilePath())) { Directory.CreateDirectory(KwmPath.GetKtlstunnelLogFilePath()); } // Start ktlstunnel as such. // ktlstunnel localhost ((IPEndPoint)Listener.LocalEndPoint).Port Host Port [-r host:port] String loggingPath = "-L " + "\"" + KwmPath.GetKtlstunnelLogFilePath() + "ktlstunnel-" + KwmPath.GetLogFileName() + "\" "; String loggingLevel = ""; if (KwmCfg.Cur.KtlstunnelLoggingLevel == 1) { loggingLevel = "-l minimal "; loggingLevel += loggingPath; } else if (KwmCfg.Cur.KtlstunnelLoggingLevel == 2) { loggingLevel = "-l debug "; loggingLevel += loggingPath; } String startupLine = "\"" + KwmPath.KwmKtlstunnelPath + "\" " + loggingLevel + "localhost " + ((IPEndPoint)Sock.LocalEndPoint).Port.ToString() + " " + Host + " " + Port + " " + extraParams; KLogging.Log("Starting ktlstunnel.exe : " + startupLine); TunnelProcess = new KProcess(startupLine); TunnelProcess.InheritHandles = false; TunnelProcess.CreationFlags = (uint)KSyscalls.CREATION_FLAGS.CREATE_NO_WINDOW; TunnelProcess.Start(); }
/// <summary> /// Create the initial database schema. /// </summary> private void CreateSchema() { KLogging.Log("Creating database schema."); m_db.BeginTransaction(); String s = "CREATE TABLE 'db_version' ('version' INT PRIMARY KEY); " + "INSERT INTO db_version (version) VALUES (" + LatestDbVersion + "); " + "CREATE TABLE 'serialization' ('name' VARCHAR PRIMARY KEY, 'data' BLOB); " + "CREATE TABLE 'kws_list' ('kws_id' INT PRIMARY KEY, 'name' VARCHAR); " + "CREATE TABLE 'kanp_events' ('kws_id' INT, 'evt_id' INT, 'evt_data' BLOB, 'status' INT); " + "CREATE TABLE 'eanp_events' ('kws_id' INT, 'evt_id' INT, 'uuid' BLOB, 'evt_data' BLOB); " + "CREATE UNIQUE INDEX 'kanp_events_index_1' ON 'kanp_events' ('kws_id', 'evt_id'); " + "CREATE UNIQUE INDEX 'kanp_events_index_2' ON 'kanp_events' ('kws_id', 'status', 'evt_id'); " + "CREATE UNIQUE INDEX 'eanp_events_index_1' ON 'eanp_events' ('kws_id', 'evt_id'); "; m_db.ExecNQ(s); m_db.CommitTransaction(); }
/// <summary> /// Run once through the bowels of the state machine. /// </summary> private static void RunPass() { // Reset the next run date to the maximum. During the processing of this // method, the next run date will lower itself as needed. m_nextRunDate = DateTime.MaxValue; // We're stopped, nothing to do. if (Wm.MainStatus == WmMainStatus.Stopped) { KLogging.Log("WmSm: " + KwmStrings.Kwm + " is stopped, nothing to do."); } // Try to stop. else if (Wm.MainStatus == WmMainStatus.Stopping && TryStop()) { HandlePostStop(); } // Perform regular processing. else { // Serialize the WM if needed. SerializeWmIfNeeded(); // Process the workspace state machines. ProcessKwsStateMachines(); // Process the workspaces to remove. ProcessKwsToRemove(); // Process the KCDs state changes. ProcessKcdState(); // Recompute the KCD event processing quench. RecomputeKcdQuenching(); // Process the KCD messages. ProcessKcdMessages(); } }
/// <summary> /// Run the workspace manager state machine. /// </summary> private static void Run(String who) { KLogging.Log("WmSm: Run() called by " + who); try { // Avoid reentrance. if (m_runningFlag) { KLogging.Log("WmSm: already running, bailing out."); return; } m_runningFlag = true; // Loop until our state stabilize. LockNotif(); while (WantToRunNow()) { RunPass(); } UnlockNotif(); // Schedule the next timer event appropriately. ScheduleTimerEvent(); // We're no longer running the WM. m_runningFlag = false; } // We cannot recover from these errors. catch (Exception ex) { KBase.HandleException(ex, true); } }
/// <summary> /// Serialize the object specified and store it under the name /// specified in the database. /// </summary> private static void SerializeObject(String name, Object obj) { KLogging.Log("Serializing " + name + "."); }
/// <summary> /// Start kmod and connect to it. /// </summary> private void StartKmod() { FileStream file = null; Socket listenSock = null; RegistryKey kwmRegKey = null; try { // Get the path to the kmod executable in the registry. kwmRegKey = KwmReg.GetKwmLMRegKey(); String Kmod = "\"" + (String)kwmRegKey.GetValue("InstallDir", @"C:\Program Files\Teambox\Teambox Manager") + "\\kmod\\kmod.exe\""; // The directory where KMOD will save logs and its database for use with the kwm. String KmodDir = KwmPath.GetKmodDirPath(); Directory.CreateDirectory(KmodDir); // Start listening for kmod to connect when it'll be started. IPEndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 0); listenSock = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); listenSock.Bind(endPoint); listenSock.Listen(1); int port = ((IPEndPoint)listenSock.LocalEndPoint).Port; // Start KMOD in debugging mode if our settings say so. String debug = KwmCfg.Cur.KtlstunnelLoggingLevel > 0 ? " -l 3" : ""; String args = " -C kmod_connect -p " + port + debug + " -m 20000 -k \"" + KmodDir + "\""; String cmdLine = Kmod + args; KLogging.Log("About to start kmod.exe: " + cmdLine); m_kmodProc = new KProcess(cmdLine); m_kmodProc.InheritHandles = false; m_kmodProc.CreationFlags = (uint)KSyscalls.CREATION_FLAGS.CREATE_NO_WINDOW; m_kmodProc.Start(); // Wait for KMOD to connect for about 10 seconds. DateTime startTime = DateTime.Now; while (true) { SelectSockets select = new SelectSockets(); select.AddRead(listenSock); SetSelectTimeout(startTime, 10000, select, "no KMOD connection received"); Block(select); if (select.InRead(listenSock)) { m_kmodSock = listenSock.Accept(); m_kmodSock.Blocking = false; break; } } // Read the authentication data. byte[] authSockData = new byte[32]; byte[] authFileData = new byte[32]; startTime = DateTime.Now; int nbRead = 0; while (nbRead != 32) { SelectSockets select = new SelectSockets(); select.AddRead(m_kmodSock); SetSelectTimeout(startTime, 2000, select, "no authentication data received"); Block(select); int r = KSocket.SockRead(m_kmodSock, authSockData, nbRead, 32 - nbRead); if (r > 0) { nbRead += r; } } file = File.Open(KmodDir + "\\connect_secret", FileMode.Open); file.Read(authFileData, 0, 32); if (!KUtil.ByteArrayEqual(authFileData, authSockData)) { throw new Exception("invalid authentication data received"); } // Set the transport. m_transport = new K3pTransport(m_kmodSock); } finally { if (file != null) { file.Close(); } if (listenSock != null) { listenSock.Close(); } if (kwmRegKey != null) { kwmRegKey.Close(); } } }
/// <summary> /// Process workspace rebuild if required. /// </summary> private void ProcessKwsRebuildIfNeeded() { Debug.Assert(m_rebuildStep == KwsRebuildTaskStep.None); if (m_cd.CurrentTask != KwsTask.Rebuild) { return; } // Sanity check. if (m_cd.MainStatus != KwsMainStatus.RebuildRequired) { KLogging.Log("cannot execute rebuild task, " + KwmStrings.Kws + " status is not RebuildRequired"); RequestTaskSwitch(KwsTask.Stop); return; } // We cannot rebuild until the applications are stopped and we're // logged out. if (m_cd.AppStatus != KwsAppStatus.Stopped || m_ks.LoginStatus != KwsLoginStatus.LoggedOut) { return; } // Protect against spurious state changes. m_rebuildStep = KwsRebuildTaskStep.InProgress; try { // Ask the workspace to prepare for rebuild. m_kws.PrepareToRebuild(); if (m_rebuildStep != KwsRebuildTaskStep.InProgress) { return; } // Tell the applications to prepare for rebuild. foreach (KwsApp app in m_kws.AppTree.Values) { app.PrepareToRebuild(); if (m_rebuildStep != KwsRebuildTaskStep.InProgress) { return; } } } catch (Exception ex) { HandleMiscFailure(ex); return; } // We have "rebuilt" the workspace. Update the state. m_rebuildStep = KwsRebuildTaskStep.None; m_cd.MainStatus = KwsMainStatus.Good; m_cd.RebuildFlags = 0; m_cd.Uuid = Wm.MakeUuid(); SetLoginType(KwsLoginType.All); m_kws.OnStateChange(WmStateChange.Permanent); // Switch to the user task. SwitchToUserTask(); }
/// <summary> /// Request a switch to the task specified, if possible. 'Ex' is /// non-null if the task switch is occurring because an error occurred. /// </summary> public void RequestTaskSwitch(KwsTask task, Exception ex) { WmSm.LockNotif(); try { // Validate. if (m_taskSwitchFlag || task == KwsTask.Stop && !CanStop() || task == KwsTask.Spawn && !CanSpawn() || task == KwsTask.Rebuild && !CanRebuild() || task == KwsTask.WorkOffline && !CanWorkOffline() || task == KwsTask.WorkOnline && !CanWorkOnline() || task == KwsTask.DeleteLocally && !CanDeleteLocally() || task == KwsTask.DeleteRemotely && !CanDeleteRemotely()) { KLogging.Log("Request to switch to task " + task + " ignored."); return; } KLogging.Log("Switching to task " + task + "."); // Update some state prior to the task switch. if (task == KwsTask.Rebuild) { m_cd.MainStatus = KwsMainStatus.RebuildRequired; m_rebuildStep = KwsRebuildTaskStep.None; } else if (task == KwsTask.WorkOnline) { ResetKcdFailureState(); } else if (task == KwsTask.DeleteLocally) { m_cd.MainStatus = KwsMainStatus.OnTheWayOut; m_kws.AddToKwsRemoveTree(); m_kws.OnStateChange(WmStateChange.Permanent); } else if (task == KwsTask.DeleteRemotely) { ResetKcdFailureState(); m_deleteRemotelyStep = KwsDeleteRemotelyStep.ConnectedAndLoggedOut; } if (task == KwsTask.Spawn || task == KwsTask.Rebuild || task == KwsTask.WorkOffline || task == KwsTask.WorkOnline) { m_cd.LastException = null; m_kws.OnStateChange(WmStateChange.Permanent); } // Perform the task switch, if required. if (task != m_cd.CurrentTask) { SwitchTask(task, ex); } } catch (Exception ex2) { KBase.HandleException(ex2, true); } finally { WmSm.UnlockNotif(); } }