aboutsummaryrefslogtreecommitdiff
path: root/xorg-server
diff options
context:
space:
mode:
authormarha <marha@users.sourceforge.net>2012-12-04 16:43:46 +0100
committermarha <marha@users.sourceforge.net>2012-12-04 17:46:16 +0100
commit33f84f71784699f4b6d914f986efdcc08a8be0f7 (patch)
tree671a0f4cde5182a0c87c4126637ee33988e2ebbb /xorg-server
parent02962c29643fe621830ef79667bff7630e357f88 (diff)
downloadvcxsrv-33f84f71784699f4b6d914f986efdcc08a8be0f7.tar.gz
vcxsrv-33f84f71784699f4b6d914f986efdcc08a8be0f7.tar.bz2
vcxsrv-33f84f71784699f4b6d914f986efdcc08a8be0f7.zip
Now you are able to enter the password when using a private key with a
password
Diffstat (limited to 'xorg-server')
-rw-r--r--xorg-server/hw/xwin/xlaunch/main.cc348
1 files changed, 194 insertions, 154 deletions
diff --git a/xorg-server/hw/xwin/xlaunch/main.cc b/xorg-server/hw/xwin/xlaunch/main.cc
index 476f8c2bc..0d2e76f90 100644
--- a/xorg-server/hw/xwin/xlaunch/main.cc
+++ b/xorg-server/hw/xwin/xlaunch/main.cc
@@ -44,6 +44,69 @@
#include <sstream>
+static bool ContainPrintableChars(const char *Buf, unsigned Nbr)
+{
+ for (int i=0; i<Nbr; i++)
+ {
+ if (Buf[i]>0x20)
+ return true;
+ }
+ return false;
+}
+
+static bool CheckOutput(HANDLE hChildStdoutRd, int hStdOut, HANDLE hConsoleOutput, HWND hConsoleWnd)
+{
+ DWORD NbrAvail=0;
+ PeekNamedPipe(hChildStdoutRd, NULL, NULL, NULL, &NbrAvail, NULL);
+ if (NbrAvail)
+ {
+ char Buf[128];
+ size_t Nbr = _read(hStdOut, Buf, sizeof(Buf));
+ DWORD NbrWritten;
+ WriteConsole(hConsoleOutput, Buf, Nbr, &NbrWritten, NULL);
+ if (hConsoleWnd && ContainPrintableChars(Buf,NbrWritten))
+ {
+ // Only show console again when there are new characters printed
+ ShowWindow(hConsoleWnd, SW_SHOW ); // make it visible again
+ return true;
+ }
+ }
+ return false;
+}
+
+#define NRINPUTS 50
+
+static int CheckInput(HANDLE hConsoleInput, char *Buf, HWND hConsoleWnd)
+{
+ INPUT_RECORD Input[NRINPUTS];
+ DWORD NbrAvail=0;
+ GetNumberOfConsoleInputEvents(hConsoleInput, &NbrAvail);
+ int w=0;
+ if (NbrAvail)
+ {
+ DWORD NbrRead=0;
+ ReadConsoleInput(hConsoleInput, Input, NRINPUTS, &NbrRead);
+ for (int i=0; i<NbrRead; i++)
+ {
+ if (Input[i].EventType==KEY_EVENT && Input[i].Event.KeyEvent.bKeyDown)
+ {
+ char Char=Input[i].Event.KeyEvent.uChar.AsciiChar;
+ if (Char)
+ {
+ Buf[w++]=Char;
+ if (Char==0xd)
+ {
+ Buf[w++]=0xa; // Convert cariage return to cariage return + line feed
+ if (hConsoleWnd) ShowWindow(hConsoleWnd, SW_HIDE ); // make it invisible again
+ }
+ }
+ }
+ }
+ }
+ return w;
+}
+
+
/// @brief Send WM_ENDSESSION to all program windows.
/// This will shutdown the started xserver
BOOL CALLBACK KillWindowsProc(HWND hwnd, LPARAM lParam)
@@ -54,7 +117,7 @@ BOOL CALLBACK KillWindowsProc(HWND hwnd, LPARAM lParam)
/// @brief Actual wizard implementation.
/// This is based on generic CWizard but handles the special dialogs
-class CMyWizard : public CWizard
+class CMyWizard : public CWizard
{
public:
private:
@@ -62,7 +125,7 @@ class CMyWizard : public CWizard
public:
/// @brief Constructor.
/// Set wizard pages.
- CMyWizard() : CWizard()
+ CMyWizard() : CWizard()
{
AddPage(IDD_DISPLAY, IDS_DISPLAY_TITLE, IDS_DISPLAY_SUBTITLE);
AddPage(IDD_CLIENTS, IDS_CLIENTS_TITLE, IDS_CLIENTS_SUBTITLE);
@@ -88,7 +151,7 @@ class CMyWizard : public CWizard
/// @brief Handle the PSN_WIZNEXT message.
/// @param hwndDlg Handle to active page dialog.
/// @param index Index of current page.
- /// @return TRUE if the message was handled. FALSE otherwise.
+ /// @return TRUE if the message was handled. FALSE otherwise.
virtual BOOL WizardNext(HWND hwndDlg, unsigned index)
{
#ifdef _DEBUG
@@ -209,7 +272,7 @@ class CMyWizard : public CWizard
// Check for valid input
if (!config.broadcast && config.xdmcp_host.empty())
SetWindowLong(hwndDlg, DWL_MSGRESULT, -1);
- else
+ else
SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_EXTRA);
if (IsDlgButtonChecked(hwndDlg, IDC_XDMCP_TERMINATE))
config.xdmcpterminate = true;
@@ -254,7 +317,7 @@ class CMyWizard : public CWizard
/// @brief Handle PSN_WIZFINISH message.
/// @param hwndDlg Handle to active page dialog.
/// @param index Index of current page.
- /// @return TRUE if the message was handled. FALSE otherwise.
+ /// @return TRUE if the message was handled. FALSE otherwise.
virtual BOOL WizardFinish(HWND hwndDlg, unsigned index)
{
#ifdef _DEBUG
@@ -267,7 +330,7 @@ class CMyWizard : public CWizard
/// if required).
/// @param hwndDlg Handle to active page dialog.
/// @param index Index of current page.
- /// @return TRUE if the message was handled. FALSE otherwise.
+ /// @return TRUE if the message was handled. FALSE otherwise.
virtual BOOL WizardBack(HWND hwndDlg, unsigned index)
{
switch (PageID(index))
@@ -280,7 +343,7 @@ class CMyWizard : public CWizard
case IDD_EXTRA: // temporary. fontpath is disabled
switch (config.client)
{
- case CConfig::NoClient:
+ case CConfig::NoClient:
SetWindowLong(hwndDlg, DWL_MSGRESULT, IDD_CLIENTS);
return TRUE;
case CConfig::StartProgram:
@@ -297,7 +360,7 @@ class CMyWizard : public CWizard
/// @brief Handle PSN_SETACTIVE message.
/// @param hwndDlg Handle to active page dialog.
/// @param index Index of current page.
- /// @return TRUE if the message was handled. FALSE otherwise.
+ /// @return TRUE if the message was handled. FALSE otherwise.
virtual BOOL WizardActivate(HWND hwndDlg, unsigned index)
{
#ifdef _DEBUG
@@ -370,27 +433,27 @@ class CMyWizard : public CWizard
char szFileTitle[512];
char szFile[MAX_PATH];
HINSTANCE hInst = GetModuleHandle(NULL);
-
+
LoadString(hInst, IDS_SAVE_TITLE, szTitle, sizeof(szTitle));
LoadString(hInst, IDS_SAVE_FILETITLE, szFileTitle, sizeof(szFileTitle));
LoadString(hInst, IDS_SAVE_FILTER, szFilter, sizeof(szFilter));
- for (unsigned i=0; szFilter[i]; i++)
- if (szFilter[i] == '%')
- szFilter[i] = '\0';
+ for (unsigned i=0; szFilter[i]; i++)
+ if (szFilter[i] == '%')
+ szFilter[i] = '\0';
strcpy(szFile, "config.xlaunch");
OPENFILENAME ofn;
memset(&ofn, 0, sizeof(OPENFILENAME));
- ofn.lStructSize = sizeof(OPENFILENAME);
- ofn.hwndOwner = parent;
- ofn.lpstrFilter = szFilter;
- ofn.lpstrFile= szFile;
- ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile);
- ofn.lpstrFileTitle = szFileTitle;
- ofn.nMaxFileTitle = sizeof(szFileTitle);
- ofn.lpstrInitialDir = (LPSTR)NULL;
- ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
+ ofn.lStructSize = sizeof(OPENFILENAME);
+ ofn.hwndOwner = parent;
+ ofn.lpstrFilter = szFilter;
+ ofn.lpstrFile= szFile;
+ ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile);
+ ofn.lpstrFileTitle = szFileTitle;
+ ofn.nMaxFileTitle = sizeof(szFileTitle);
+ ofn.lpstrInitialDir = (LPSTR)NULL;
+ ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
ofn.lpstrTitle = szTitle;
if (GetSaveFileName(&ofn))
@@ -403,16 +466,16 @@ class CMyWizard : public CWizard
sprintf(Message,"Failure: %s\n", e.what());
MessageBox(NULL,Message,"Exception",MB_OK);
}
- }
+ }
}
public:
-
+
/// @brief Handle messages fo the dialog pages.
/// @param hwndDlg Handle of active dialog.
/// @param uMsg Message code.
/// @param wParam Message parameter.
/// @param lParam Message parameter.
- /// @param psp Handle to sheet paramters.
+ /// @param psp Handle to sheet paramters.
virtual INT_PTR PageDispatch(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam, PROPSHEETPAGE *psp)
{
HWND hwnd;
@@ -459,7 +522,7 @@ class CMyWizard : public CWizard
}
break;
case IDD_PROGRAM:
- // Init program dialog. Check local and remote buttons
+ // Init program dialog. Check local and remote buttons
CheckRadioButton(hwndDlg, IDC_CLIENT_LOCAL, IDC_CLIENT_REMOTE, config.local?IDC_CLIENT_LOCAL:IDC_CLIENT_REMOTE);
EnableRemoteProgramGroup(hwndDlg, config.local?FALSE:TRUE);
// Fill combo boxes
@@ -502,7 +565,7 @@ class CMyWizard : public CWizard
CheckRadioButton(hwndDlg, IDC_MULTIWINDOW, IDC_NODECORATION, LOWORD(wParam)-4);
SetFocus(GetDlgItem(hwndDlg, LOWORD(wParam)-4));
break;
- // Disable unavailable controls
+ // Disable unavailable controls
case IDC_CLIENT_REMOTE:
case IDC_CLIENT_LOCAL:
EnableRemoteProgramGroup(hwndDlg, LOWORD(wParam) == IDC_CLIENT_REMOTE);
@@ -519,7 +582,7 @@ class CMyWizard : public CWizard
// pass messages to parent
return CWizard::PageDispatch(hwndDlg, uMsg, wParam, lParam, psp);
}
-
+
/// @brief Try to connect to server.
/// Repeat until successful, server died or maximum number of retries
/// reached.
@@ -540,7 +603,7 @@ class CMyWizard : public CWizard
}
return NULL;
}
-
+
/// @brief Do the actual start of VCXsrv and clients
void StartUp()
{
@@ -575,7 +638,7 @@ class CMyWizard : public CWizard
{
if (config.broadcast)
buffer += "-broadcast ";
- else
+ else
{
if (config.indirect)
buffer += "-indirect ";
@@ -631,7 +694,6 @@ class CMyWizard : public CWizard
remotepassword=std::string(" -pw ")+config.remotepassword;
if (!config.privatekey.empty())
remotepassword+=std::string(" -i \"")+config.privatekey+"\"";
- // Need to use -console since certain commands will not execute when no console
_snprintf(cmdline,512,"plink -ssh -X%s %s %s",
remotepassword.c_str(), host.c_str(),config.remoteprogram.c_str());
client += cmdline;
@@ -645,8 +707,7 @@ class CMyWizard : public CWizard
// Prepare program startup
STARTUPINFO si, sic;
PROCESS_INFORMATION pi, pic;
- HANDLE handles[2];
- DWORD hcount = 0;
+ DWORD hcount = 0;
Display *dpy = NULL;
ZeroMemory( &si, sizeof(si) );
@@ -656,18 +717,17 @@ class CMyWizard : public CWizard
sic.cb = sizeof(sic);
ZeroMemory( &pic, sizeof(pic) );
- // Start VCXsrv process.
+ // Start VCXsrv process.
#ifdef _DEBUG
printf("%s\n", buffer.c_str());
#endif
char CurDir[MAX_PATH];
GetModuleFileName(NULL,CurDir,MAX_PATH);
*strrchr(CurDir,'\\')=0;
-
- if( !CreateProcess( NULL, (CHAR*)buffer.c_str(), NULL, NULL,
- TRUE, 0, NULL, CurDir, &si, &pi ))
+
+ if( !CreateProcess( NULL, (CHAR*)buffer.c_str(), NULL, NULL,
+ TRUE, 0, NULL, CurDir, &si, &pi ))
throw win32_error("CreateProcess failed");
- handles[hcount++] = pi.hProcess;
if (!client.empty())
{
@@ -693,28 +753,21 @@ class CMyWizard : public CWizard
dpy = WaitForServer(pi.hProcess);
if (dpy == NULL)
{
- while (hcount--)
- TerminateProcess(handles[hcount], (DWORD)-1);
+ TerminateProcess(pi.hProcess, (DWORD)-1);
throw std::runtime_error("Connection to server failed");
}
-
+
#ifdef _DEBUG
printf("%s\n", client.c_str());
#endif
- // Hide a console window
- // FIXME: This may make it impossible to enter the password
- sic.dwFlags = STARTF_USESHOWWINDOW;
- sic.wShowWindow = SW_HIDE;
-
- // Start the child process.
+ // Start the child process.
+ #if 1
// Create a console, otherwise some commands will not execute with plink
- #ifdef WITH_FLASH
- AllocConsole();
- #else
HWINSTA h=GetProcessWindowStation();
HWINSTA horig=h;
+ HWND hConsoleWnd=NULL;
if (h)
{
h=CreateWindowStationW(NULL, 0, STANDARD_RIGHTS_READ, NULL);
@@ -723,104 +776,98 @@ class CMyWizard : public CWizard
AllocConsole();
SetProcessWindowStation(horig);
CloseWindowStation(h);
+ hConsoleWnd=GetConsoleWindow();
+ ShowWindow(hConsoleWnd, SW_HIDE ); // make it hidden, the disadvantage of this method is that the console window flashes
}
- #endif
- #if 1
- {
- HANDLE hChildStdinRd;
- HANDLE hChildStdinWr;
- HANDLE hChildStdoutRd;
- HANDLE hChildStdoutWr;
- HANDLE hChildStdinWrDup;
- HANDLE hChildStdoutRdDup;
- SECURITY_ATTRIBUTES saAttr;
- BOOL fSuccess;
- STARTUPINFO StartupInfo;
- memset(&StartupInfo,0,sizeof(StartupInfo));
- StartupInfo.cb=sizeof(STARTUPINFO);
- PROCESS_INFORMATION ProcessInfo;
-
- saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
- saAttr.bInheritHandle = TRUE;
- saAttr.lpSecurityDescriptor = NULL;
-
- if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
- throw win32_error("CreatePipe failed", GetLastError());
-
- /* Create new output read handle and the input write handle. Set
- * the inheritance properties to FALSE. Otherwise, the child inherits
- * the these handles; resulting in non-closeable handles to the pipes
- * being created. */
- fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr,
- GetCurrentProcess(), &hChildStdinWrDup, 0,
- FALSE, DUPLICATE_SAME_ACCESS);
- if (!fSuccess)
- throw win32_error("DuplicateHandle failed", GetLastError());
- /* Close the inheritable version of ChildStdin that we're using. */
- CloseHandle(hChildStdinWr);
-
- if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
- throw win32_error("CreatePipe failed", GetLastError());
-
- fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
- GetCurrentProcess(), &hChildStdoutRdDup, 0,
- FALSE, DUPLICATE_SAME_ACCESS);
- if (!fSuccess)
- throw win32_error("DuplicateHandle failed", GetLastError());
- CloseHandle(hChildStdoutRd);
-
- int hStdIn = _open_osfhandle((long)hChildStdinWrDup, _O_WRONLY|_O_TEXT);
- FILE *pStdIn = _fdopen(hStdIn, "w");
- int hStdOut = _open_osfhandle((long)hChildStdoutRdDup, _O_RDONLY|_O_TEXT);
- FILE *pStdOut = _fdopen(hStdOut, "r");
-
- StartupInfo.dwFlags = STARTF_USESTDHANDLES;
- StartupInfo.hStdInput = hChildStdinRd;
- StartupInfo.hStdOutput = hChildStdoutWr;
- StartupInfo.hStdError = hChildStdoutWr;
-
- if (!CreateProcess(NULL,(CHAR*)client.c_str(),NULL,NULL,TRUE,CREATE_NO_WINDOW,NULL, CurDir, &StartupInfo, &ProcessInfo))
- {
- DWORD err = GetLastError();
- while (hcount--)
- TerminateProcess(handles[hcount], (DWORD)-1);
- throw win32_error("CreateProcess failed", err);
- }
- CloseHandle(hChildStdinRd);
- CloseHandle(hChildStdoutWr);
-
- CloseHandle(ProcessInfo.hThread);
- char Buf[256];
- size_t Nbr;
- std::string output;
- while ( (Nbr=fread(Buf,1,sizeof(Buf)-1,pStdOut)) > 0)
- {
- output+=Buf;
- }
- if (!output.empty())
- {
- MessageBox(NULL,output.c_str(),"Child output",MB_OK);
- }
- }
+ HANDLE hChildStdinRd;
+ HANDLE hChildStdinWr;
+ HANDLE hChildStdoutRd;
+ HANDLE hChildStdoutWr;
+ SECURITY_ATTRIBUTES saAttr;
+ BOOL fSuccess;
+
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
+ throw win32_error("CreatePipe failed", GetLastError());
+
+ // Ensure the write handle to the pipe for STDIN is not inherited.
+ if ( ! SetHandleInformation(hChildStdinWr, HANDLE_FLAG_INHERIT, 0) )
+ throw win32_error("SetHandleInformation failed", GetLastError());
+
+ if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
+ throw win32_error("CreatePipe failed", GetLastError());
+
+ // Ensure the read handle to the pipe for STDOUT is not inherited.
+ if ( ! SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0) )
+ throw win32_error("SetHandleInformation failed", GetLastError());
+
+ sic.dwFlags = STARTF_USESTDHANDLES;
+ sic.hStdInput = hChildStdinRd;
+ sic.hStdOutput = hChildStdoutWr;
+ sic.hStdError = hChildStdoutWr;
+
+ if (!CreateProcess(NULL,(CHAR*)client.c_str(),NULL,NULL,TRUE,0,NULL, CurDir, &sic, &pic))
+ {
+ DWORD err = GetLastError();
+ TerminateProcess(pi.hProcess, (DWORD)-1);
+ throw win32_error("CreateProcess failed", err);
+ }
+ CloseHandle(hChildStdinRd);
+ CloseHandle(hChildStdoutWr);
+ CloseHandle(pic.hThread);
+
+ int hStdIn = _open_osfhandle((long)hChildStdinWr, _O_WRONLY|_O_BINARY);
+ int hStdOut = _open_osfhandle((long)hChildStdoutRd, _O_RDONLY|_O_BINARY);
+ HANDLE hConsoleInput=GetStdHandle(STD_INPUT_HANDLE);
+ HANDLE hConsoleOutput=GetStdHandle(STD_OUTPUT_HANDLE);
+ SetConsoleMode(hConsoleInput, 0); // Needed to disable local echo, and return only upon carriage return of read function
+ while (1)
+ {
+ char Buf[NRINPUTS];
+ if (!WaitForSingleObject(pic.hProcess, 20 ))
+ {
+ // Child does not exist anymore, but it could be that there is still error output in the pipes
+ // So wait some time, that then check the output again
+ Sleep(500);
+ if (CheckOutput(hChildStdoutRd, hStdOut, hConsoleOutput, hConsoleWnd))
+ {
+ // Wait some input to close the window
+ while (!CheckInput(hConsoleInput, Buf, hConsoleWnd)) Sleep(10);
+ }
+ break;
+ }
+ CheckOutput(hChildStdoutRd, hStdOut, hConsoleOutput, hConsoleWnd);
+
+ int w=CheckInput(hConsoleInput, Buf, hConsoleWnd);
+ if (w)
+ { // Write it to the client
+ _write(hStdIn, Buf, w);
+ }
+ }
#else
+ // Hide a console window
+ // FIXME: This may make it impossible to enter the password
+ sic.dwFlags = STARTF_USESHOWWINDOW;
+ sic.wShowWindow = SW_HIDE;
+
if( !CreateProcess( NULL, (CHAR*)client.c_str(), NULL, NULL,
- FALSE, 0, NULL, CurDir, &sic, &pic ))
+ FALSE, 0, NULL, CurDir, &sic, &pic ))
{
- DWORD err = GetLastError();
- while (hcount--)
- TerminateProcess(handles[hcount], (DWORD)-1);
- throw win32_error("CreateProcess failed", err);
+ DWORD err = GetLastError();
+ while (hcount--)
+ TerminateProcess(handles[hcount], (DWORD)-1);
+ throw win32_error("CreateProcess failed", err);
}
- #endif
- handles[hcount++] = pic.hProcess;
- #ifdef WITHFLASH
- ShowWindow( GetConsoleWindow(), SW_HIDE ); // make it hidden
+ CloseHandle( pic.hThread );
#endif
}
- // Wait until any child process exits.
- DWORD ret = WaitForMultipleObjects(hcount, handles, FALSE, INFINITE );
+ // Wait until child process exits.
+ DWORD ret = WaitForSingleObject(pic.hProcess, INFINITE );
#ifdef _DEBUG
printf("killing process!\n");
@@ -828,11 +875,11 @@ class CMyWizard : public CWizard
// Check if Xsrv is still running, but only when we started a local program
if (config.local)
{
- DWORD exitcode;
- GetExitCodeProcess(pi.hProcess, &exitcode);
- unsigned counter = 0;
- while (exitcode == STILL_ACTIVE)
- {
+ DWORD exitcode;
+ GetExitCodeProcess(pi.hProcess, &exitcode);
+ unsigned counter = 0;
+ while (exitcode == STILL_ACTIVE)
+ {
if (++counter > 10)
TerminateProcess(pi.hProcess, (DWORD)-1);
else
@@ -840,16 +887,13 @@ class CMyWizard : public CWizard
EnumThreadWindows(pi.dwThreadId, KillWindowsProc, 0);
Sleep(500);
GetExitCodeProcess(pi.hProcess, &exitcode);
- }
- // Kill the client
- TerminateProcess(pic.hProcess, (DWORD)-1);
+ }
}
- // Close process and thread handles.
+ // Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
CloseHandle( pic.hProcess );
- CloseHandle( pic.hThread );
}
};
@@ -865,7 +909,7 @@ int main(int argc, char **argv)
{
if (argv[i] == NULL)
continue;
-
+
std::string arg(argv[i]);
if (arg == "-load" && i + 1 < argc)
{
@@ -882,7 +926,7 @@ int main(int argc, char **argv)
}
}
- int ret = 0;
+ int ret = 0;
if (skip_wizard || (ret =dialog.ShowModal()) != 0)
dialog.StartUp();
#ifdef _DEBUG
@@ -897,7 +941,3 @@ int main(int argc, char **argv)
return -1;
}
}
-
-
-
-