mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Merge #6295
6295: Implement FIFO on Windows using Named Pipes r=def- a=Robyt3 Reimplement the Linux FIFO file server and client controls on Windows by using Named Pipes. The DDNet server/client acts as a named pipe server and receives messages. Messages can be posted to the named pipe server by connecting to it as a client. The named pipe client can for instance be controlled from the command line with PowerShell. The PowerShell script `scripts/send_named_pipe.ps1` is added for this purpose. For example the PowerShell command `./send_named_pipe.ps1 "testpipe" "echo a"` sends the command `echo a` to the pipe named `testpipe`. Multiple commands can be sent at the same time by separating them with semicolons or newlines. ## Checklist - [X] Tested the change ingame - [ ] Provided screenshots if it is a visual change - [ ] Tested in combination with possibly related configuration options - [ ] Written a unit test (especially base/) or added coverage to integration test - [ ] Considered possible null pointers and out of bounds array indexing - [ ] Changed no physics that affect existing maps - [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
commit
3ffd4205ef
33
scripts/send_named_pipe.ps1
Normal file
33
scripts/send_named_pipe.ps1
Normal file
|
@ -0,0 +1,33 @@
|
|||
# This PowerShell script connects to a Named Pipe server,
|
||||
# sends one message and then disconnects again.
|
||||
# The first argument is the name of the pipe.
|
||||
# The second argument is the message to send.
|
||||
if ($args.length -lt 2) {
|
||||
Write-Output "Usage: ./send_named_pipe.ps1 <pipename> <message> [message] ... [message]"
|
||||
return
|
||||
}
|
||||
|
||||
$Wrapper = [pscustomobject]@{
|
||||
Pipe = new-object System.IO.Pipes.NamedPipeClientStream(
|
||||
".",
|
||||
$args[0],
|
||||
[System.IO.Pipes.PipeDirection]::InOut,
|
||||
[System.IO.Pipes.PipeOptions]::None,
|
||||
[System.Security.Principal.TokenImpersonationLevel]::Impersonation
|
||||
)
|
||||
Reader = $null
|
||||
Writer = $null
|
||||
}
|
||||
$Wrapper.Pipe.Connect(5000)
|
||||
if (!$?) {
|
||||
return
|
||||
}
|
||||
$Wrapper.Reader = New-Object System.IO.StreamReader($Wrapper.Pipe)
|
||||
$Wrapper.Writer = New-Object System.IO.StreamWriter($Wrapper.Pipe)
|
||||
$Wrapper.Writer.AutoFlush = $true
|
||||
for ($i = 1; $i -lt $args.length; $i++) {
|
||||
$Wrapper.Writer.WriteLine($args[$i])
|
||||
}
|
||||
# We need to wait because the lines will not be written if we close the pipe immediately
|
||||
Start-Sleep -Seconds 1.5
|
||||
$Wrapper.Pipe.Close()
|
|
@ -1437,7 +1437,7 @@ static int priv_net_close_all_sockets(NETSOCKET sock)
|
|||
}
|
||||
|
||||
#if defined(CONF_FAMILY_WINDOWS)
|
||||
static char *windows_format_system_message(unsigned long error)
|
||||
char *windows_format_system_message(unsigned long error)
|
||||
{
|
||||
WCHAR *wide_message;
|
||||
const DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK;
|
||||
|
|
|
@ -1147,6 +1147,20 @@ void net_unix_set_addr(UNIXSOCKETADDR *addr, const char *path);
|
|||
*/
|
||||
void net_unix_close(UNIXSOCKET sock);
|
||||
|
||||
#elif defined(CONF_FAMILY_WINDOWS)
|
||||
|
||||
/**
|
||||
* Formats a Windows error code as a human-readable string.
|
||||
*
|
||||
* @param error The Windows error code.
|
||||
*
|
||||
* @return A new string representing the error code.
|
||||
*
|
||||
* @remark Guarantees that result will contain zero-termination.
|
||||
* @remark The result must be freed after it has been used.
|
||||
*/
|
||||
char *windows_format_system_message(unsigned long error);
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
|
|
@ -3078,9 +3078,7 @@ void CClient::Run()
|
|||
// process pending commands
|
||||
m_pConsole->StoreCommands(false);
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
m_Fifo.Init(m_pConsole, g_Config.m_ClInputFifo, CFGFLAG_CLIENT);
|
||||
#endif
|
||||
|
||||
InitChecksum();
|
||||
m_pConsole->InitChecksum(ChecksumData());
|
||||
|
@ -3341,9 +3339,7 @@ void CClient::Run()
|
|||
break;
|
||||
}
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
m_Fifo.Update();
|
||||
#endif
|
||||
|
||||
// beNice
|
||||
auto Now = time_get_nanoseconds();
|
||||
|
@ -3395,9 +3391,7 @@ void CClient::Run()
|
|||
m_GlobalTime = (time_get() - m_GlobalStartTime) / (float)time_freq();
|
||||
}
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
m_Fifo.Shutdown();
|
||||
#endif
|
||||
|
||||
GameClient()->OnShutdown();
|
||||
Disconnect();
|
||||
|
|
|
@ -281,9 +281,7 @@ class CClient : public IClient, public CDemoPlayer::IListener
|
|||
|
||||
std::vector<SWarning> m_vWarnings;
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
CFifo m_Fifo;
|
||||
#endif
|
||||
|
||||
IOHANDLE m_BenchmarkFile;
|
||||
int64_t m_BenchmarkStopTime;
|
||||
|
|
|
@ -2598,9 +2598,7 @@ int CServer::Run()
|
|||
|
||||
m_Econ.Init(Config(), Console(), &m_ServerBan);
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
m_Fifo.Init(Console(), Config()->m_SvInputFifo, CFGFLAG_SERVER);
|
||||
#endif
|
||||
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf), "server name is '%s'", Config()->m_SvName);
|
||||
|
@ -2798,9 +2796,7 @@ int CServer::Run()
|
|||
|
||||
UpdateClientRconCommands();
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
m_Fifo.Update();
|
||||
#endif
|
||||
}
|
||||
|
||||
// master server stuff
|
||||
|
@ -2879,9 +2875,7 @@ int CServer::Run()
|
|||
|
||||
m_Econ.Shutdown();
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
m_Fifo.Shutdown();
|
||||
#endif
|
||||
|
||||
GameServer()->OnShutdown();
|
||||
m_pMap->Unload();
|
||||
|
|
|
@ -221,9 +221,7 @@ public:
|
|||
CSnapIDPool m_IDPool;
|
||||
CNetServer m_NetServer;
|
||||
CEcon m_Econ;
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
CFifo m_Fifo;
|
||||
#endif
|
||||
CServerBan m_ServerBan;
|
||||
|
||||
IEngineMap *m_pMap;
|
||||
|
|
|
@ -356,7 +356,7 @@ MACRO_CONFIG_INT(SvVotePause, sv_vote_pause, 1, 0, 1, CFGFLAG_SERVER, "Allow vot
|
|||
MACRO_CONFIG_INT(SvVotePauseTime, sv_vote_pause_time, 10, 0, 360, CFGFLAG_SERVER, "The time (in seconds) players have to wait in pause when paused by vote")
|
||||
MACRO_CONFIG_INT(SvTuneReset, sv_tune_reset, 1, 0, 1, CFGFLAG_SERVER, "Whether tuning is reset after each map change or not")
|
||||
MACRO_CONFIG_STR(SvResetFile, sv_reset_file, 128, "reset.cfg", CFGFLAG_SERVER, "File to execute on map change or reload to set the default server settings")
|
||||
MACRO_CONFIG_STR(SvInputFifo, sv_input_fifo, 128, "", CFGFLAG_SERVER, "Fifo file to use as input for server console")
|
||||
MACRO_CONFIG_STR(SvInputFifo, sv_input_fifo, 128, "", CFGFLAG_SERVER, "Fifo file (non-Windows) or Named Pipe (Windows) to use as input for server console")
|
||||
MACRO_CONFIG_INT(SvDDRaceTuneReset, sv_ddrace_tune_reset, 1, 0, 1, CFGFLAG_SERVER, "Whether DDRace tuning (sv_hit, sv_endless_drag and sv_old_laser) is reset after each map change or not")
|
||||
MACRO_CONFIG_INT(SvNamelessScore, sv_nameless_score, 1, 0, 1, CFGFLAG_SERVER, "Whether nameless tee has a score or not")
|
||||
MACRO_CONFIG_INT(SvTimeInBroadcastInterval, sv_time_in_broadcast_interval, 1, 0, 60, CFGFLAG_SERVER, "How often to update the broadcast time")
|
||||
|
@ -412,7 +412,7 @@ MACRO_CONFIG_INT(ClConfirmQuitTime, cl_confirm_quit_time, 20, -1, 1440, CFGFLAG_
|
|||
MACRO_CONFIG_STR(ClTimeoutCode, cl_timeout_code, 64, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Timeout code to use")
|
||||
MACRO_CONFIG_STR(ClDummyTimeoutCode, cl_dummy_timeout_code, 64, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Dummy Timeout code to use")
|
||||
MACRO_CONFIG_STR(ClTimeoutSeed, cl_timeout_seed, 64, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Timeout seed")
|
||||
MACRO_CONFIG_STR(ClInputFifo, cl_input_fifo, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Fifo file to use as input for client console")
|
||||
MACRO_CONFIG_STR(ClInputFifo, cl_input_fifo, 128, "", CFGFLAG_SAVE | CFGFLAG_CLIENT, "Fifo file (non-Windows) or Named Pipe (Windows) to use as input for client console")
|
||||
MACRO_CONFIG_INT(ClConfigVersion, cl_config_version, 0, 0, 0, CFGFLAG_CLIENT | CFGFLAG_SAVE, "The config version. Helps newer clients fix bugs with older configs.")
|
||||
|
||||
// demo editor
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
#include <base/system.h>
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
|
||||
#include <engine/shared/config.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
@ -36,8 +34,8 @@ void CFifo::Init(IConsole *pConsole, char *pFifoFile, int Flag)
|
|||
|
||||
if(!S_ISFIFO(Attribute.st_mode))
|
||||
{
|
||||
dbg_msg("fifo", "can't remove file '%s', quitting", m_aFilename);
|
||||
exit(2);
|
||||
dbg_msg("fifo", "can't remove file '%s'", m_aFilename);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,11 +46,11 @@ void CFifo::Init(IConsole *pConsole, char *pFifoFile, int Flag)
|
|||
|
||||
void CFifo::Shutdown()
|
||||
{
|
||||
if(m_File >= 0)
|
||||
{
|
||||
close(m_File);
|
||||
fs_remove(m_aFilename);
|
||||
}
|
||||
if(m_File < 0)
|
||||
return;
|
||||
|
||||
close(m_File);
|
||||
fs_remove(m_aFilename);
|
||||
}
|
||||
|
||||
void CFifo::Update()
|
||||
|
@ -78,4 +76,127 @@ void CFifo::Update()
|
|||
if(pCur < aBuf + Length) // missed the last line
|
||||
m_pConsole->ExecuteLineFlag(pCur, m_Flag, -1);
|
||||
}
|
||||
|
||||
#elif defined(CONF_FAMILY_WINDOWS)
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
void CFifo::Init(IConsole *pConsole, char *pFifoFile, int Flag)
|
||||
{
|
||||
m_pConsole = pConsole;
|
||||
if(pFifoFile[0] == '\0')
|
||||
{
|
||||
m_pPipe = INVALID_HANDLE_VALUE;
|
||||
return;
|
||||
}
|
||||
|
||||
str_copy(m_aFilename, "\\\\.\\pipe\\");
|
||||
str_append(m_aFilename, pFifoFile, sizeof(m_aFilename));
|
||||
m_Flag = Flag;
|
||||
|
||||
const int WLen = MultiByteToWideChar(CP_UTF8, 0, m_aFilename, -1, NULL, 0);
|
||||
dbg_assert(WLen > 0, "MultiByteToWideChar failure");
|
||||
wchar_t *pWide = static_cast<wchar_t *>(malloc(WLen * sizeof(*pWide)));
|
||||
dbg_assert(MultiByteToWideChar(CP_UTF8, 0, m_aFilename, -1, pWide, WLen) == WLen, "MultiByteToWideChar failure");
|
||||
m_pPipe = CreateNamedPipeW(pWide,
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT | PIPE_REJECT_REMOTE_CLIENTS,
|
||||
PIPE_UNLIMITED_INSTANCES,
|
||||
8192,
|
||||
8192,
|
||||
NMPWAIT_USE_DEFAULT_WAIT,
|
||||
NULL);
|
||||
free(pWide);
|
||||
if(m_pPipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
const DWORD LastError = GetLastError();
|
||||
char *pErrorMsg = windows_format_system_message(LastError);
|
||||
dbg_msg("fifo", "failed to create named pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
|
||||
free(pErrorMsg);
|
||||
}
|
||||
else
|
||||
dbg_msg("fifo", "created named pipe '%s'", m_aFilename);
|
||||
}
|
||||
|
||||
void CFifo::Shutdown()
|
||||
{
|
||||
if(m_pPipe == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
|
||||
DisconnectNamedPipe(m_pPipe);
|
||||
CloseHandle(m_pPipe);
|
||||
m_pPipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
void CFifo::Update()
|
||||
{
|
||||
if(m_pPipe == INVALID_HANDLE_VALUE)
|
||||
return;
|
||||
|
||||
if(!ConnectNamedPipe(m_pPipe, NULL))
|
||||
{
|
||||
const DWORD LastError = GetLastError();
|
||||
if(LastError == ERROR_PIPE_LISTENING) // waiting for clients to connect
|
||||
return;
|
||||
if(LastError == ERROR_NO_DATA) // pipe was disconnected from the other end, also disconnect it from this end
|
||||
{
|
||||
// disconnect the previous client so we can connect to a new one
|
||||
DisconnectNamedPipe(m_pPipe);
|
||||
return;
|
||||
}
|
||||
if(LastError != ERROR_PIPE_CONNECTED) // pipe already connected, not an error
|
||||
{
|
||||
char *pErrorMsg = windows_format_system_message(LastError);
|
||||
dbg_msg("fifo", "failed to connect named pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
|
||||
free(pErrorMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
while(true) // read all messages from the pipe
|
||||
{
|
||||
DWORD BytesAvailable;
|
||||
if(!PeekNamedPipe(m_pPipe, NULL, 0, NULL, &BytesAvailable, NULL))
|
||||
{
|
||||
const DWORD LastError = GetLastError();
|
||||
if(LastError != ERROR_BAD_PIPE) // pipe not connected, not an error
|
||||
{
|
||||
char *pErrorMsg = windows_format_system_message(LastError);
|
||||
dbg_msg("fifo", "failed to peek at pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
|
||||
free(pErrorMsg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(BytesAvailable == 0) // pipe connected but no data available
|
||||
return;
|
||||
|
||||
char *pBuf = static_cast<char *>(malloc(BytesAvailable + 1));
|
||||
DWORD Length;
|
||||
if(!ReadFile(m_pPipe, pBuf, BytesAvailable, &Length, NULL))
|
||||
{
|
||||
const DWORD LastError = GetLastError();
|
||||
char *pErrorMsg = windows_format_system_message(LastError);
|
||||
dbg_msg("fifo", "failed to read from pipe '%s' (%ld %s)", m_aFilename, LastError, pErrorMsg);
|
||||
free(pErrorMsg);
|
||||
free(pBuf);
|
||||
return;
|
||||
}
|
||||
pBuf[Length] = '\0';
|
||||
|
||||
char *pCur = pBuf;
|
||||
for(DWORD i = 0; i < Length; ++i)
|
||||
{
|
||||
if(pBuf[i] != '\n')
|
||||
continue;
|
||||
pBuf[i] = '\0';
|
||||
m_pConsole->ExecuteLineFlag(pCur, m_Flag, -1);
|
||||
pCur = pBuf + i + 1;
|
||||
}
|
||||
if(pCur < pBuf + Length) // missed the last line
|
||||
m_pConsole->ExecuteLineFlag(pCur, m_Flag, -1);
|
||||
|
||||
free(pBuf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
|
||||
#include <engine/console.h>
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
|
||||
class CFifo
|
||||
{
|
||||
IConsole *m_pConsole;
|
||||
char m_aFilename[IO_MAX_PATH_LENGTH];
|
||||
int m_Flag;
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
int m_File;
|
||||
#elif defined(CONF_FAMILY_WINDOWS)
|
||||
void *m_pPipe;
|
||||
#endif
|
||||
|
||||
public:
|
||||
void Init(IConsole *pConsole, char *pFifoFile, int Flag);
|
||||
|
@ -19,5 +21,3 @@ public:
|
|||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue