Add WinPty and ConPty

This commit is contained in:
KingToolbox 2020-08-24 14:47:21 +08:00
parent 21662a08f6
commit 23570ead27
8 changed files with 781 additions and 0 deletions

261
src/Pty/ConPty.cpp Normal file
View File

@ -0,0 +1,261 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ConPty.h"
#include <QThread>
#ifdef Q_OS_WIN
#include <process.h>
#endif
#define CONPTY_MINIMAL_WINDOWS_VERSION 18309
class PipeThread : public QThread {
public:
PipeThread(ConPty *conpty, LPVOID pipe)
: QThread(conpty)
, m_conpty(conpty)
, m_pipe(pipe)
{}
void run() final {
constexpr DWORD BUFF_SIZE = 512;
char szBuffer[BUFF_SIZE];
while (isInterruptionRequested() == false) {
if (isInterruptionRequested()) {
return;
}
DWORD bytesRead;
bool readSuccess = ReadFile(m_pipe, szBuffer, BUFF_SIZE, &bytesRead, NULL);
if (readSuccess == false) {
m_conpty->setErrorCode(GetLastError());
return;
}
if (isInterruptionRequested()) {
return;
}
if (readSuccess && bytesRead > 0) {
m_conpty->appendBuffer(QByteArray(szBuffer, bytesRead));
}
}
}
private:
ConPty *m_conpty;
LPVOID m_pipe;
};
ConPty::ConPty(QObject *parent /*= nullptr*/)
: m_inPipe(INVALID_HANDLE_VALUE)
, m_outPipe(INVALID_HANDLE_VALUE)
, m_pipeThread(nullptr)
, m_ptyHandler(INVALID_HANDLE_VALUE)
{}
ConPty::~ConPty() {
stop();
}
void ConPty::appendBuffer(const QByteArray &buffer) {
if (buffer.isEmpty() == false) {
{
ThreadLocker<SpinMutex> locker(m_mutex);
m_buffer.append(buffer);
}
emit readyRead();
}
}
HRESULT ConPty::createPseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut,
qint16 rows, qint16 columns) {
HRESULT hr = E_UNEXPECTED;
HANDLE hPipePTYIn = INVALID_HANDLE_VALUE;
HANDLE hPipePTYOut = INVALID_HANDLE_VALUE;
if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) && CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0)) {
#if COMPILE_CONPTY_ENABLED
hr = CreatePseudoConsole({columns, rows}, hPipePTYIn, hPipePTYOut, 0, phPC);
#endif
if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut);
if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn);
}
return hr;
}
HRESULT ConPty::initStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC) {
HRESULT hr = E_UNEXPECTED;
if (pStartupInfo) {
SIZE_T attrListSize;
pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX);
InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
pStartupInfo->lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attrListSize));
if (pStartupInfo->lpAttributeList
&& InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize)) {
hr = UpdateProcThreadAttribute(
pStartupInfo->lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
sizeof(HPCON),
NULL,
NULL
) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
} else {
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
return hr;
}
bool ConPty::createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) {
if (isAvailable() == false) {
setErrorString(tr("Windows 10 version below 1809 is not supported."));
return false;
}
stop();
HRESULT hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_inPipe, &m_outPipe, rows, columns);
if (hr == S_OK) {
m_startupInfo = std::make_unique<STARTUPINFOEX>();
m_processInformation = std::make_unique<PROCESS_INFORMATION>();
m_pipeThread = new PipeThread(this, m_inPipe);
m_pipeThread->start();
if (initStartupInfoAttachedToPseudoConsole(m_startupInfo.get(), m_ptyHandler) == S_OK) {
std::wstring env = environment.join(QChar('\0')).append(QChar('\0')).toStdWString();
if (arguments.isEmpty() == false) {
command.append(" ").append(arguments);
}
LPWSTR szCommand = new wchar_t[command.size() + 1];
int commandLength = command.toWCharArray(szCommand);
szCommand[commandLength] = '\0';
hr = CreateProcess(
NULL,
szCommand,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
LPWSTR(env.data()),
workingDirectory.isEmpty() ? NULL : workingDirectory.toStdWString().c_str(),
&m_startupInfo->StartupInfo,
m_processInformation.get()
) ? S_OK : GetLastError();
delete szCommand;
szCommand = nullptr;
}
}
if (hr == S_OK) {
m_rows = rows;
m_columns = columns;
installWinProcessEventNotifier(m_processInformation->hProcess);
} else {
setErrorCode(GetLastError());
}
return true;
}
bool ConPty::isAvailable() {
qint32 buildNumber = QSysInfo::kernelVersion().split(".").last().toInt();
return (buildNumber >= CONPTY_MINIMAL_WINDOWS_VERSION) ? true : false;
}
QByteArray ConPty::readAll() {
ThreadLocker<SpinMutex> locker(m_mutex);
return std::move(m_buffer);
}
bool ConPty::resizeWindow(qint16 rows, qint16 columns) {
bool success = true;
if (rows != m_rows && columns != m_columns) {
#if COMPILE_CONPTY_ENABLED
HRESULT hr = (m_ptyHandler != INVALID_HANDLE_VALUE)
? ResizePseudoConsole(m_ptyHandler, { columns, rows })
: S_FALSE;
success = (hr == S_OK) ? true : false;
#endif
if (success) {
rows = m_rows;
columns = m_columns;
}
}
Q_ASSERT(success);
return success;
}
void ConPty::stop() {
if (m_pipeThread) {
m_pipeThread->requestInterruption();
}
if (m_processInformation) {
uninstallWinProcessEventNotifier(m_processInformation->hProcess);
CloseHandle(m_processInformation->hThread);
CloseHandle(m_processInformation->hProcess);
}
if (m_startupInfo) {
DeleteProcThreadAttributeList(m_startupInfo->lpAttributeList);
free(m_startupInfo->lpAttributeList);
}
if (m_ptyHandler != INVALID_HANDLE_VALUE) {
#if COMPILE_CONPTY_ENABLED
ClosePseudoConsole(m_ptyHandler);
#endif
}
if (m_inPipe != INVALID_HANDLE_VALUE) {
CloseHandle(m_inPipe);
}
if (m_outPipe != INVALID_HANDLE_VALUE) {
CloseHandle(m_outPipe);
}
if (m_pipeThread) {
m_pipeThread->wait(1000);
m_pipeThread->deleteLater();
}
}
qint64 ConPty::write(const QByteArray &text) {
DWORD bytesWritten;
WriteFile(m_outPipe, text.data(), text.size(), &bytesWritten, NULL);
return bytesWritten;
}

66
src/Pty/ConPty.h Normal file
View File

@ -0,0 +1,66 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef CONPTY_H
#define CONPTY_H
#pragma once
#include "Pty.h"
#ifdef Q_OS_WIN
#include <windows.h>
#endif // Q_OS_WIN
class PipeThread;
class ConPty
: public Pty
{
Q_OBJECT
public:
ConPty(QObject *parent = nullptr);
virtual ~ConPty();
void appendBuffer(const QByteArray &buffer);
bool createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) final;
static bool isAvailable();
QByteArray readAll() final;
bool resizeWindow(qint16 rows, qint16 columns) final;
qint64 write(const QByteArray &text) final;
private:
HRESULT createPseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut, qint16 rows, qint16 columns);
HRESULT initStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC);
void stop();
private:
Q_DISABLE_COPY(ConPty)
QByteArray m_buffer;
PipeThread *m_pipeThread;
HANDLE m_inPipe;
HANDLE m_outPipe;
HPCON m_ptyHandler;
std::unique_ptr<PROCESS_INFORMATION> m_processInformation;
std::unique_ptr<STARTUPINFOEX> m_startupInfo;
};
#endif // CONPTY_H

95
src/Pty/Pty.cpp Normal file
View File

@ -0,0 +1,95 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Pty.h"
#ifdef Q_OS_WIN
#include <windows.h>
#include <QWinEventNotifier>
#endif // Q_OS_WIN
Pty::Pty()
: m_columns(-1)
, m_errorCode(0)
, m_rows(-1)
#ifdef Q_OS_WIN
, m_winProcessEventNotifier(nullptr)
#endif
{}
QString Pty::errorString() {
ThreadLocker<SpinMutex> locker(m_mutex);
return std::move(m_errorString);
}
#ifdef Q_OS_WIN
void Pty::installWinProcessEventNotifier(void *handle) {
if (m_winProcessEventNotifier == nullptr) {
m_winProcessEventNotifier = new QWinEventNotifier(handle, this);
connect(m_winProcessEventNotifier, &QWinEventNotifier::activated, this, [this](HANDLE handle) {
if (handle) {
DWORD exitCode;
if (GetExitCodeProcess(handle, &exitCode)) {
setErrorString(QString("Process exited with code %1").arg(
QString::number(exitCode, (exitCode >= 0xFF) ? 16 : 10).prepend((exitCode >= 0xFF) ? "0x" : "")
));
}
m_winProcessEventNotifier->setEnabled(false);
}
});
}
if (m_winProcessEventNotifier->handle() != handle) {
m_winProcessEventNotifier->setHandle(handle);
m_winProcessEventNotifier->setEnabled(true);
}
}
#endif
void Pty::setErrorCode(int errorCode) {
constexpr int bufferLength = 512;
wchar_t buffer[bufferLength];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,
LANG_NEUTRAL, buffer, bufferLength, NULL);
QString lastError = QString::fromWCharArray(buffer);
setErrorString(lastError);
m_errorCode = errorCode;
}
void Pty::setErrorString(const QString &errorString) {
if (errorString.isEmpty() == false) {
{
ThreadLocker<SpinMutex> locker(m_mutex);
m_errorString = errorString;
}
emit errorOccurred();
}
}
#ifdef Q_OS_WIN
void Pty::uninstallWinProcessEventNotifier(void *handle) {
if (m_winProcessEventNotifier != nullptr
&& m_winProcessEventNotifier->handle() == handle) {
m_winProcessEventNotifier->deleteLater();
m_winProcessEventNotifier = nullptr;
}
}
#endif

73
src/Pty/Pty.h Normal file
View File

@ -0,0 +1,73 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef PTY_H
#define PTY_H
#include <QObject>
#include "Public/Spin.h"
class QWinEventNotifier;
class Pty
: public QObject
{
Q_OBJECT
public:
Pty();
virtual ~Pty() = default;
virtual bool createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) = 0;
int errorCode() const { return m_errorCode; }
QString errorString();
virtual QByteArray readAll() = 0;
virtual bool resizeWindow(qint16 rows, qint16 columns) = 0;
void setErrorCode(int errorCode);
void setErrorString(const QString &errorString);
virtual qint64 write(const QByteArray &text) = 0;
protected:
#ifdef Q_OS_WIN
void installWinProcessEventNotifier(void *handle);
void uninstallWinProcessEventNotifier(void *handle);
#endif
Q_SIGNALS:
void errorOccurred();
void readyRead();
protected:
SpinMutex m_mutex;
qint16 m_columns;
qint16 m_rows;
private:
Q_DISABLE_COPY(Pty)
int m_errorCode;
QString m_errorString;
#ifdef Q_OS_WIN
QWinEventNotifier *m_winProcessEventNotifier;
#endif
};
#endif // PTY_H

200
src/Pty/WinPty.cpp Normal file
View File

@ -0,0 +1,200 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "WinPty.h"
#include <QFileInfo>
#include <QLocalSocket>
#include <QCoreApplication>
#include <sstream>
const char *WINPTY_AGENT_NAME = "winpty-agent.exe";
const char *WINPTY_DLL_NAME = "winpty.dll";
QString castErrorToString(winpty_error_ptr_t error_ptr) {
return QString::fromStdWString(winpty_error_msg(error_ptr));
}
WinPty::WinPty(QObject *parent /*= nullptr*/)
: m_ptyHandler(nullptr)
, m_innerHandle(INVALID_HANDLE_VALUE)
, m_outSocket(nullptr)
, m_inSocket(nullptr)
{}
WinPty::~WinPty() {
stop();
}
bool WinPty::createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) {
bool success = false;
winpty_error_ptr_t errorPtr = nullptr;
QString errorString;
do {
stop();
if (isAvailable() == false) {
errorString = tr("Winpty-agent.exe or winpty.dll not found!.");
break;
}
QString commandWithArguments = command;
if (arguments.isEmpty() == false) {
commandWithArguments.append(" ").append(arguments);
}
std::wstring env = environment.join(QChar('\0')).append(QChar('\0')).toStdWString();
winpty_config_t* startConfig = winpty_config_new(0, &errorPtr);
if (startConfig == nullptr) {
errorString = QString("WinPty Error: create start config -> %1").arg(castErrorToString(errorPtr));
break;
}
winpty_config_set_initial_size(startConfig, columns, rows);
winpty_config_set_mouse_mode(startConfig, WINPTY_MOUSE_MODE_AUTO);
m_ptyHandler = winpty_open(startConfig, &errorPtr);
winpty_config_free(startConfig);
if (m_ptyHandler == nullptr) {
errorString = QString("WinPty Error: start agent -> %1").arg(castErrorToString(errorPtr));
break;
}
QString m_conInName = QString::fromWCharArray(winpty_conin_name(m_ptyHandler));
QString m_conOutName = QString::fromWCharArray(winpty_conout_name(m_ptyHandler));
m_outSocket = std::make_unique<QLocalSocket>();
m_inSocket = std::make_unique<QLocalSocket>();
m_outSocket->connectToServer(m_conInName, QIODevice::WriteOnly);
m_outSocket->waitForConnected();
m_inSocket->connectToServer(m_conOutName, QIODevice::ReadOnly);
m_inSocket->waitForConnected();
if (m_outSocket->state() != QLocalSocket::ConnectedState
&& m_inSocket->state() != QLocalSocket::ConnectedState) {
errorString = QString("WinPty Error: Unable to connect local sockets -> %1 / %2")
.arg(m_outSocket->errorString())
.arg(m_inSocket->errorString());
m_inSocket.reset(nullptr);
m_outSocket.reset(nullptr);
break;
}
connect(m_inSocket.get(), &QLocalSocket::readyRead, this, [this]() {
emit readyRead();
});
winpty_spawn_config_t* spawnConfig = winpty_spawn_config_new(
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
command.toStdWString().c_str(),
commandWithArguments.toStdWString().c_str(),
workingDirectory.isEmpty() ? NULL : workingDirectory.toStdWString().c_str(),
env.c_str(),
&errorPtr
);
if (spawnConfig == nullptr) {
errorString = QString("WinPty Error: create spawn config -> %1").arg(castErrorToString(errorPtr));
break;
}
BOOL spawnSuccess = winpty_spawn(m_ptyHandler, spawnConfig, &m_innerHandle, nullptr, nullptr, &errorPtr);
winpty_spawn_config_free(spawnConfig);
if (spawnSuccess == FALSE) {
errorString = QString("WinPty Error: start terminal process -> %1").arg(castErrorToString(errorPtr));
break;
}
success = true;
} while (0);
if (errorString.isEmpty() == false) {
Q_ASSERT(success == false);
winpty_error_free(errorPtr);
setErrorString(errorString);
}
if (success) {
m_columns = columns;
m_rows = rows;
installWinProcessEventNotifier(m_innerHandle);
}
return success;
}
QByteArray WinPty::readAll() {
QByteArray buffer;
if (m_inSocket) {
buffer = m_inSocket->readAll();
Q_ASSERT(buffer.isEmpty() == false);
}
return buffer;
}
bool WinPty::resizeWindow(qint16 rows, qint16 columns) {
bool success = true;
if (rows != m_rows && columns != m_columns) {
success = m_ptyHandler ? winpty_set_size(m_ptyHandler, columns, rows, nullptr) : false;
if (success) {
m_rows = rows;
m_columns = columns;
}
}
Q_ASSERT(success);
return success;
}
void WinPty::stop() {
if (m_ptyHandler != nullptr) {
winpty_free(m_ptyHandler);
m_ptyHandler = nullptr;
}
if (m_innerHandle != INVALID_HANDLE_VALUE) {
uninstallWinProcessEventNotifier(m_innerHandle);
CloseHandle(m_innerHandle);
m_innerHandle = INVALID_HANDLE_VALUE;
}
m_outSocket.reset(nullptr);
m_inSocket.reset(nullptr);
}
qint64 WinPty::write(const QByteArray &text) {
qint64 bytesWritten = -1;
if (m_outSocket) {
bytesWritten = m_outSocket->write(text);
Q_ASSERT(bytesWritten != -1);
}
return bytesWritten;
}
bool WinPty::isAvailable() {
return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME)
&& QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_DLL_NAME);
}

61
src/Pty/WinPty.h Normal file
View File

@ -0,0 +1,61 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef WINPTY_H
#define WINPTY_H
#pragma once
#include "Pty.h"
#ifdef Q_OS_WIN
#include <windows.h>
#endif // Q_OS_WIN
#include "winpty_api.h"
class QLocalSocket;
class WinPty
: public Pty
{
Q_OBJECT
public:
WinPty(QObject *parent = nullptr);
virtual ~WinPty();
bool createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) final;
static bool isAvailable();
QByteArray readAll() final;
bool resizeWindow(qint16 rows, qint16 columns) final;
qint64 write(const QByteArray &text) final;
private:
void stop();
private:
Q_DISABLE_COPY(WinPty)
winpty_t *m_ptyHandler;
HANDLE m_innerHandle;
std::unique_ptr<QLocalSocket> m_inSocket;
std::unique_ptr<QLocalSocket> m_outSocket;
};
#endif // WINPTY_H

21
src/Pty/ptyqt LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Vitaly Petrov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -16,6 +16,10 @@ A very safe encryption class using the PBKDF2-algorithm as defined in RFC 8018.
An improved version based on Onigmo 5.13.5. In particular, **the addition of iterator makes it possible to match gap buffer or nonadjacent memory blocks.** Please refer to the sample files for how to use.
## Pty
An improved version based on ptyqt[https://github.com/kafeg/ptyqt]. **Almost all the code was rewritten to make the pty more robust and stable.**
## ScopeGuard.h
A class of which the sole purpose is to run the function f in its destructor. This is useful for guaranteeing your cleanup code is executed.