root/wconsd.c @ c7c4a6f7
62045e98 | Hamish Coleman | /*
|
|
* wconsd.c - serial port server service for Windows NT
|
|||
*
|
|||
* Copyright (c) 2003 Benjamin Schweizer <gopher at h07 dot org>
|
|||
* 1998 Stephen Early <Stephen.Early@cl.cam.ac.uk>
|
|||
*
|
|||
*
|
|||
* This program is free software; you can redistribute it and/or modify
|
|||
* it under the terms of the GNU General Public License as published by
|
|||
* the Free Software Foundation; either version 2 of the License, or
|
|||
* (at your option) any later version.
|
|||
*
|
|||
* This program is distributed in the hope that it will be useful,
|
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
* GNU General Public License for more details.
|
|||
*
|
|||
* You should have received a copy of the GNU General Public License
|
|||
* along with this program; if not, write to the Free Software
|
|||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|||
*/
|
|||
78d4c0b0 | Hamish Coleman | ||
b6970c98 | Hamish Coleman | /* Note: winsock2.h MUST be included before windows.h */
|
|
#include <winsock2.h>
|
|||
#include <windows.h>
|
|||
#include <winsvc.h>
|
|||
#include <stdio.h>
|
|||
#include <stdlib.h>
|
|||
84d7e6f5 | Hamish Coleman | #define VERSION "0.1.2"
|
|
62045e98 | Hamish Coleman | ||
78d4c0b0 | Hamish Coleman | /* Size of buffers for send and receive */
|
|
#define BUFSIZE 1024
|
|||
62045e98 | Hamish Coleman | #define MAXLEN 1024
|
|
78d4c0b0 | Hamish Coleman | ||
/* Sockets for listening and communicating */
|
|||
SOCKET ls=INVALID_SOCKET,cs=INVALID_SOCKET;
|
|||
/* Event objects */
|
|||
HANDLE stopEvent, connectionCloseEvent, threadTermEvent;
|
|||
HANDLE readEvent, writeEvent;
|
|||
WSAEVENT listenSocketEvent;
|
|||
/* COM port */
|
|||
HANDLE hCom;
|
|||
DCB dcb;
|
|||
62045e98 | Hamish Coleman | COMMTIMEOUTS timeouts;
|
|
61b53eda | Hamish Coleman | ||
/* Port default settings are here */
|
|||
int com_port=1;
|
|||
DWORD com_speed=9600;
|
|||
BYTE com_data=8;
|
|||
BYTE com_parity=NOPARITY;
|
|||
BYTE com_stop=ONESTOPBIT;
|
|||
BOOL com_autoclose=TRUE;
|
|||
ff03d660 | Hamish Coleman | BOOL com_state=FALSE; // FALSE=closed,TRUE=open
|
|
78d4c0b0 | Hamish Coleman | ||
b9a4e090 | Hamish Coleman | int default_tcpport = 23;
|
|
61b53eda | Hamish Coleman | ||
78d4c0b0 | Hamish Coleman | /* Service status: our current status, and handle on service manager */
|
|
SERVICE_STATUS wconsd_status;
|
|||
SERVICE_STATUS_HANDLE wconsd_statusHandle;
|
|||
25fa1d20 | Hamish Coleman | int debug_mode = 0;
|
|
ef78cec5 | Hamish Coleman | #define MAXCONNECTIONS 8
|
|
struct connection {
|
|||
c7c4a6f7 | Hamish Coleman | int connected; /* a connected entry cannot be reused */
|
|
int menuactive; /* dont run the serial pump on a menu */
|
|||
ef78cec5 | Hamish Coleman | HANDLE menuThread;
|
|
SOCKET net;
|
|||
HANDLE netThread;
|
|||
HANDLE serial;
|
|||
HANDLE serialThread;
|
|||
int net_bytes_rx;
|
|||
int net_bytes_tx;
|
|||
};
|
|||
struct connection connection[MAXCONNECTIONS];
|
|||
61b53eda | Hamish Coleman | /*
|
|
e6a4efc5 | Hamish Coleman | * output from OutputDebugStringA can be seen using sysinternals debugview
|
|
61b53eda | Hamish Coleman | * http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx
|
|
*/
|
|||
e6a4efc5 | Hamish Coleman | ||
/*
|
|||
* log a debug message
|
|||
*/
|
|||
int dprintf_level = 1;
|
|||
int dprintf(unsigned char severity, const char *fmt, ...) {
|
|||
va_list args;
|
|||
c7c4a6f7 | Hamish Coleman | char buf[MAXLEN];
|
|
e6a4efc5 | Hamish Coleman | int i;
|
|
if (severity > dprintf_level)
|
|||
return 0;
|
|||
va_start(args,fmt);
|
|||
c0d1e8fe | Hamish Coleman | i=vsnprintf(buf,sizeof(buf),fmt,args);
|
|
e6a4efc5 | Hamish Coleman | va_end(args);
|
|
25fa1d20 | Hamish Coleman | if (debug_mode) {
|
|
printf("%s",buf);
|
|||
} else {
|
|||
OutputDebugStringA(buf);
|
|||
}
|
|||
e6a4efc5 | Hamish Coleman | ||
return i;
|
|||
78d4c0b0 | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | /*
|
|
* format a string and send it to a file descriptor
|
|||
*/
|
|||
int fdprintf(SOCKET fd, const char *fmt, ...) {
|
|||
va_list args;
|
|||
char buf[MAXLEN];
|
|||
int i;
|
|||
va_start(args,fmt);
|
|||
i=vsnprintf(buf,sizeof(buf),fmt,args);
|
|||
va_end(args);
|
|||
send(fd,buf,(i>MAXLEN)?MAXLEN-1:i,0);
|
|||
return i;
|
|||
}
|
|||
62045e98 | Hamish Coleman | /* open the com port */
|
|
DWORD open_com_port(DWORD *specificError) {
|
|||
/* Open the COM port */
|
|||
char portstr[12];
|
|||
sprintf(portstr, "\\\\.\\COM%d", com_port);
|
|||
hCom = CreateFile(portstr,
|
|||
GENERIC_READ | GENERIC_WRITE,
|
|||
0, // Exclusive access
|
|||
NULL,
|
|||
OPEN_EXISTING,
|
|||
FILE_FLAG_OVERLAPPED,
|
|||
NULL);
|
|||
if (hCom == INVALID_HANDLE_VALUE) {
|
|||
*specificError=GetLastError();
|
|||
return 13;
|
|||
}
|
|||
if (!GetCommState(hCom, &dcb)) {
|
|||
*specificError=GetLastError();
|
|||
return 14;
|
|||
}
|
|||
// Fill in the device control block
|
|||
dcb.BaudRate=com_speed;
|
|||
dcb.ByteSize=com_data;
|
|||
dcb.Parity=com_parity; // NOPARITY, ODDPARITY, EVENPARITY
|
|||
dcb.StopBits=com_stop; // ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS
|
|||
dcb.fBinary=TRUE;
|
|||
dcb.fOutxCtsFlow=FALSE;
|
|||
dcb.fOutxDsrFlow=FALSE;
|
|||
dcb.fDtrControl=DTR_CONTROL_ENABLE; // Always on
|
|||
dcb.fDsrSensitivity=FALSE;
|
|||
dcb.fTXContinueOnXoff=FALSE;
|
|||
dcb.fOutX=FALSE;
|
|||
dcb.fInX=FALSE;
|
|||
dcb.fErrorChar=FALSE;
|
|||
dcb.fNull=FALSE;
|
|||
dcb.fRtsControl=RTS_CONTROL_ENABLE; // Always on
|
|||
dcb.fAbortOnError=FALSE;
|
|||
if (!SetCommState(hCom, &dcb)) {
|
|||
*specificError=GetLastError();
|
|||
return 15;
|
|||
}
|
|||
timeouts.ReadIntervalTimeout=20;
|
|||
timeouts.ReadTotalTimeoutMultiplier=0;
|
|||
timeouts.ReadTotalTimeoutConstant=50;
|
|||
timeouts.WriteTotalTimeoutMultiplier=0;
|
|||
timeouts.WriteTotalTimeoutConstant=0;
|
|||
if (!SetCommTimeouts(hCom, &timeouts)) {
|
|||
*specificError=GetLastError();
|
|||
return 16;
|
|||
}
|
|||
com_state=TRUE;
|
|||
return 0;
|
|||
}
|
|||
/* close the com port */
|
|||
void close_com_port() {
|
|||
CloseHandle(hCom);
|
|||
com_state=FALSE;
|
|||
}
|
|||
78d4c0b0 | Hamish Coleman | /* Initialise wconsd: open a listening socket and the COM port, and
|
|
* create lots of event objects. */
|
|||
DWORD wconsd_init(DWORD argc, LPSTR *argv, DWORD *specificError)
|
|||
{
|
|||
struct sockaddr_in sin;
|
|||
WORD wVersionRequested;
|
|||
WSADATA wsaData;
|
|||
int err;
|
|||
/* Start up sockets */
|
|||
wVersionRequested = MAKEWORD( 2, 2 );
|
|||
err = WSAStartup( wVersionRequested, &wsaData );
|
|||
if ( err != 0 ) {
|
|||
*specificError=err;
|
|||
/* Tell the user that we could not find a usable */
|
|||
/* WinSock DLL. */
|
|||
return 1;
|
|||
}
|
|||
/* Confirm that the WinSock DLL supports 2.2.*/
|
|||
/* Note that if the DLL supports versions greater */
|
|||
/* than 2.2 in addition to 2.2, it will still return */
|
|||
/* 2.2 in wVersion since that is the version we */
|
|||
/* requested. */
|
|||
if ( LOBYTE( wsaData.wVersion ) != 2 ||
|
|||
HIBYTE( wsaData.wVersion ) != 2 ) {
|
|||
/* Tell the user that we could not find a usable */
|
|||
/* WinSock DLL. */
|
|||
WSACleanup( );
|
|||
return 2;
|
|||
}
|
|||
/* The WinSock DLL is acceptable. Proceed. */
|
|||
// Create the event object used to signal service shutdown
|
|||
stopEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|||
if (stopEvent==NULL) {
|
|||
*specificError=GetLastError();
|
|||
return 3;
|
|||
}
|
|||
// Create the event object used to signal connection close
|
|||
connectionCloseEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|||
if (connectionCloseEvent==NULL) {
|
|||
*specificError=GetLastError();
|
|||
return 4;
|
|||
}
|
|||
threadTermEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|||
if (threadTermEvent==NULL) {
|
|||
*specificError=GetLastError();
|
|||
return 5;
|
|||
}
|
|||
// Event objects for overlapped IO
|
|||
readEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|||
if (readEvent==NULL) {
|
|||
*specificError=GetLastError();
|
|||
return 6;
|
|||
}
|
|||
writeEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
|
|||
if (writeEvent==NULL) {
|
|||
*specificError=GetLastError();
|
|||
return 7;
|
|||
}
|
|||
// Create the event object for socket operations
|
|||
listenSocketEvent = WSACreateEvent();
|
|||
if (listenSocketEvent==WSA_INVALID_EVENT) {
|
|||
*specificError=WSAGetLastError();
|
|||
return 8;
|
|||
}
|
|||
/* Create a socket to listen for connections. */
|
|||
memset(&sin,0,sizeof(sin));
|
|||
sin.sin_family=AF_INET;
|
|||
62727af2 | Hamish Coleman | sin.sin_port=htons(default_tcpport);
|
|
78d4c0b0 | Hamish Coleman | ls=socket(AF_INET,SOCK_STREAM,0);
|
|
if (ls==INVALID_SOCKET) {
|
|||
*specificError=WSAGetLastError();
|
|||
return 9;
|
|||
}
|
|||
ef78cec5 | Hamish Coleman | #ifndef MS_WINDOWS
|
|
{
|
|||
int one=1;
|
|||
setsockopt(ls,SOL_SOCKET,SO_REUSEADDR,(void*)&one,sizeof(one));
|
|||
}
|
|||
#endif
|
|||
78d4c0b0 | Hamish Coleman | if (bind(ls,(struct sockaddr *)&sin,sizeof(sin))==SOCKET_ERROR) {
|
|
*specificError=WSAGetLastError();
|
|||
ef78cec5 | Hamish Coleman | dprintf(1,"wconsd: wconsd_init: failed to bind socket\n");
|
|
78d4c0b0 | Hamish Coleman | return 10;
|
|
}
|
|||
if (listen(ls,1)==SOCKET_ERROR) {
|
|||
*specificError=WSAGetLastError();
|
|||
return 11;
|
|||
}
|
|||
/* Mark the socket as non-blocking */
|
|||
if (WSAEventSelect(ls,listenSocketEvent,FD_ACCEPT)==SOCKET_ERROR) {
|
|||
*specificError=WSAGetLastError();
|
|||
return 12;
|
|||
}
|
|||
return 0;
|
|||
}
|
|||
DWORD WINAPI wconsd_net_to_com(LPVOID lpParam)
|
|||
{
|
|||
c0d1e8fe | Hamish Coleman | unsigned char buf[BUFSIZE];
|
|
78d4c0b0 | Hamish Coleman | DWORD size,wsize;
|
|
unsigned long zero=0;
|
|||
fd_set s;
|
|||
OVERLAPPED o={0};
|
|||
o.hEvent = writeEvent;
|
|||
while (WaitForSingleObject(threadTermEvent,0)!=WAIT_OBJECT_0) {
|
|||
/* There's a bug in some versions of Windows which leads
|
|||
* to recv() returning -1 and indicating error WSAEWOULDBLOCK,
|
|||
* even on a blocking socket. This select() is here to work
|
|||
* around that bug. */
|
|||
FD_SET(cs,&s);
|
|||
select(0,&s,NULL,NULL,NULL);
|
|||
c0d1e8fe | Hamish Coleman | size=recv(cs,(void*)&buf,BUFSIZE,0);
|
|
78d4c0b0 | Hamish Coleman | if (size==0) {
|
|
SetEvent(connectionCloseEvent);
|
|||
return 0;
|
|||
}
|
|||
if (size==SOCKET_ERROR) {
|
|||
/* General paranoia about blocking sockets */
|
|||
ioctlsocket(cs,FIONBIO,&zero);
|
|||
}
|
|||
if (size!=SOCKET_ERROR) {
|
|||
if (!WriteFile(hCom,buf,size,&wsize,&o)) {
|
|||
if (GetLastError()==ERROR_IO_PENDING) {
|
|||
// Wait for it...
|
|||
if (!GetOverlappedResult(hCom,&o,&wsize,TRUE)) {
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: Error %d (overlapped) writing to COM port\n",GetLastError());
|
|
78d4c0b0 | Hamish Coleman | }
|
|
} else {
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: Error %d writing to COM port\n",GetLastError());
|
|
78d4c0b0 | Hamish Coleman | }
|
|
}
|
|||
if (wsize!=size) {
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: Eeek! WriteFile: wrote %d of %d\n",wsize,size);
|
|
78d4c0b0 | Hamish Coleman | }
|
|
}
|
|||
}
|
|||
return 0;
|
|||
}
|
|||
DWORD WINAPI wconsd_com_to_net(LPVOID lpParam)
|
|||
{
|
|||
c0d1e8fe | Hamish Coleman | unsigned char buf[BUFSIZE];
|
|
78d4c0b0 | Hamish Coleman | DWORD size;
|
|
OVERLAPPED o={0};
|
|||
o.hEvent=readEvent;
|
|||
while (WaitForSingleObject(threadTermEvent,0)!=WAIT_OBJECT_0) {
|
|||
if (!ReadFile(hCom,buf,BUFSIZE,&size,&o)) {
|
|||
if (GetLastError()==ERROR_IO_PENDING) {
|
|||
// Wait for overlapped operation to complete
|
|||
if (!GetOverlappedResult(hCom,&o,&size,TRUE)) {
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: Error %d (overlapped) reading from COM port\n",GetLastError());
|
|
78d4c0b0 | Hamish Coleman | }
|
|
} else {
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: Error %d reading from COM port\n",GetLastError());
|
|
78d4c0b0 | Hamish Coleman | SetEvent(connectionCloseEvent);
|
|
return 0;
|
|||
}
|
|||
}
|
|||
if (size>0) {
|
|||
c0d1e8fe | Hamish Coleman | send(cs,(void*)&buf,size,0);
|
|
78d4c0b0 | Hamish Coleman | }
|
|
}
|
|||
return 0;
|
|||
62045e98 | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | void send_help(SOCKET cs) {
|
|
fdprintf(cs,"available commands:\r\n\n"
|
|||
" port, speed, data, parity, stop\r\n"
|
|||
" help, status, copyright\r\n"
|
|||
" open, close, autoclose\r\n"
|
|||
" quit\r\n");
|
|||
53110989 | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | void show_status(struct connection* conn) {
|
|
/* print the status to the net connection */
|
|||
fdprintf(conn->net, "status:\r\n\n"
|
|||
" port=%d speed=%d data=%d parity=%d stop=%d\r\n\n",
|
|||
com_port, com_speed, com_data, com_parity, com_stop);
|
|||
if(com_state) {
|
|||
fdprintf(conn->net, " state=open autoclose=%d\r\n\n", com_autoclose);
|
|||
} else {
|
|||
fdprintf(conn->net, " state=closed autoclose=%d\r\n\n", com_autoclose);
|
|||
}
|
|||
}
|
|||
/*
|
|||
* I thought that I would need this a lot, but it turns out that there
|
|||
* was a lot of duplicated code
|
|||
*/
|
|||
int check_atoi(char *p,int old_value,SOCKET fd,char *error) {
|
|||
if (!p) {
|
|||
fdprintf(fd,error);
|
|||
return old_value;
|
|||
}
|
|||
return atoi(p);
|
|||
53110989 | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | int process_menu_line(struct connection*conn, char *line) {
|
|
53110989 | Hamish Coleman | DWORD errcode;
|
|
d51f658a | Hamish Coleman | char *command;
|
|
char *parameter1;
|
|||
c7c4a6f7 | Hamish Coleman | /*
|
|
* FIXME - non re-entrant code
|
|||
*
|
|||
* based on winelib, I am unsure if windows has strtok_r
|
|||
* and thus I am running these two strtok as close as possible.
|
|||
* I am definitely not going to re-invent the wheel with my own
|
|||
* code.
|
|||
*/
|
|||
d51f658a | Hamish Coleman | command = strtok(line," ");
|
|
parameter1 = strtok(NULL," ");
|
|||
if (!strcmp(command, "help") || !strcmp(command, "?")) {
|
|||
// help
|
|||
send_help(cs);
|
|||
} else if (!strcmp(command, "status")) {
|
|||
// status
|
|||
c7c4a6f7 | Hamish Coleman | show_status(conn);
|
|
d51f658a | Hamish Coleman | } else if (!strcmp(command, "copyright")) { // copyright
|
|
c7c4a6f7 | Hamish Coleman | fdprintf(conn->net,
|
|
" Copyright (c) 2008 by Hamish Coleman <hamish@zot.org>\r\n"
|
|||
" 2003 by Benjamin Schweizer <gopher at h07 dot org>\r\n"
|
|||
" 1998 by Stephen Early <Stephen.Early@cl.cam.ac.uk>\r\n"
|
|||
"\r\n"
|
|||
"\r\n"
|
|||
" This program is free software; you can redistribute it and/or modify\r\n"
|
|||
" it under the terms of the GNU General Public License as published by\r\n"
|
|||
" the Free Software Foundation; either version 2 of the License, or\r\n"
|
|||
" (at your option) any later version.\r\n"
|
|||
"\r\n"
|
|||
" This program is distributed in the hope that it will be useful,\r\n"
|
|||
" but WITHOUT ANY WARRANTY; without even the implied warranty of\r\n"
|
|||
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r\n"
|
|||
" GNU General Public License for more details.\r\n"
|
|||
"\r\n"
|
|||
" You should have received a copy of the GNU General Public License\r\n"
|
|||
" along with this program; if not, write to the Free Software\r\n"
|
|||
" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\r\n"
|
|||
"\n");
|
|||
d51f658a | Hamish Coleman | } else if (!strcmp(command, "port")) { // port
|
|
c7c4a6f7 | Hamish Coleman | int new = check_atoi(parameter1,com_port,conn->net,"must specify a port\r\n");
|
|
if (new >= 1 && new <= 16) {
|
|||
com_port=new;
|
|||
d51f658a | Hamish Coleman | }
|
|
} else if (!strcmp(command, "speed")) { // speed
|
|||
c7c4a6f7 | Hamish Coleman | com_speed = check_atoi(parameter1,com_port,conn->net,"must specify a speed\r\n");
|
|
d51f658a | Hamish Coleman | } else if (!strcmp(command, "data")) { // data
|
|
if (!strcmp(parameter1, "5")) {
|
|||
com_data=5;
|
|||
} else if (!strcmp(parameter1, "6")) {
|
|||
com_data=6;
|
|||
} else if (!strcmp(parameter1, "7")) {
|
|||
com_data=7;
|
|||
} else if (!strcmp(parameter1, "8")) {
|
|||
com_data=8;
|
|||
}
|
|||
c7c4a6f7 | Hamish Coleman | show_status(conn);
|
|
d51f658a | Hamish Coleman | } else if (!strcmp(command, "parity")) { // parity
|
|
if (!strcmp(parameter1, "no") || !strcmp(parameter1, "0")) {
|
|||
com_parity=NOPARITY;
|
|||
} else if (!strcmp(parameter1, "even") || !strcmp(parameter1, "2")) {
|
|||
com_parity=EVENPARITY;
|
|||
} else if (!strcmp(parameter1, "odd") || !strcmp(parameter1, "1")) {
|
|||
com_parity=ODDPARITY;
|
|||
} else if (!strcmp(parameter1, "mark")) {
|
|||
com_parity=MARKPARITY;
|
|||
} else if (!strcmp(parameter1, "space")) {
|
|||
com_parity=SPACEPARITY;
|
|||
}
|
|||
c7c4a6f7 | Hamish Coleman | show_status(conn);
|
|
d51f658a | Hamish Coleman | } else if (!strcmp(command, "stop")) {
|
|
if (!strcmp(parameter1, "one") || !strcmp(parameter1, "1")) {
|
|||
com_stop=ONESTOPBIT;
|
|||
} else if (!strcmp(parameter1, "one5") || !strcmp(parameter1, "1.5")) {
|
|||
com_stop=ONE5STOPBITS;
|
|||
} else if (!strcmp(parameter1, "two") || !strcmp(parameter1, "2")) {
|
|||
com_stop=TWOSTOPBITS;
|
|||
}
|
|||
c7c4a6f7 | Hamish Coleman | show_status(conn);
|
|
d51f658a | Hamish Coleman | } else if (!strcmp(command, "open")) { // open
|
|
c7c4a6f7 | Hamish Coleman | int new = check_atoi(parameter1,com_port,conn->net,"must specify a port\r\n");
|
|
if (new >= 1 && new <= 16) {
|
|||
com_port=new;
|
|||
d51f658a | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | if (com_state) {
|
|
/* port ist still open */
|
|||
send(cs,"\r\n\n",3,0);
|
|||
/* signal to quit the menu */
|
|||
conn->menuactive=0;
|
|||
/* and return still running */
|
|||
return 1;
|
|||
}
|
|||
if (!open_com_port(&errcode)) {
|
|||
send(cs,"\r\n\n",3,0);
|
|||
b0f1897b | Hamish Coleman | // signal to quit the menu
|
|
c7c4a6f7 | Hamish Coleman | conn->menuactive=0;
|
|
/* and return still running */
|
|||
return 1;
|
|||
} else {
|
|||
fdprintf(conn->net,"error: cannot open port\r\n\n");
|
|||
d51f658a | Hamish Coleman | }
|
|
} else if (!strcmp(command, "close")) { // close
|
|||
close_com_port();
|
|||
c7c4a6f7 | Hamish Coleman | fdprintf(conn->net,"info: actual com port closed\r\n\n");
|
|
d51f658a | Hamish Coleman | } else if (!strcmp(command, "autoclose")) { // autoclose
|
|
if (!strcmp(parameter1, "true") || !strcmp(parameter1, "1") || !strcmp(parameter1, "yes")) {
|
|||
com_autoclose=TRUE;
|
|||
} else if (!strcmp(parameter1, "false") || !strcmp(parameter1, "0") || !strcmp(parameter1, "no")) {
|
|||
com_autoclose=FALSE;
|
|||
}
|
|||
c7c4a6f7 | Hamish Coleman | show_status(conn);
|
|
b0f1897b | Hamish Coleman | } else if (!strcmp(command, "quit")) {
|
|
// quit the connection
|
|||
SetEvent(connectionCloseEvent);
|
|||
c7c4a6f7 | Hamish Coleman | conn->menuactive=0;
|
|
/* and return not running */
|
|||
return 0;
|
|||
} else {
|
|||
/* other, unknown commands */
|
|||
fdprintf(conn->net,"debug: line='%s', command='%s'\r\n\n",line,command);
|
|||
d51f658a | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | /* return still running */
|
|
return 1;
|
|||
53110989 | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | int run_menu(struct connection * conn) {
|
|
c0d1e8fe | Hamish Coleman | unsigned char buf[BUFSIZE], line[MAXLEN];
|
|
62045e98 | Hamish Coleman | DWORD size, linelen=0;
|
|
WORD i;
|
|||
0bcbd4d3 | Hamish Coleman | int skip_lf=0;
|
|
62045e98 | Hamish Coleman | unsigned long zero=0;
|
|
53110989 | Hamish Coleman | ||
c7c4a6f7 | Hamish Coleman | fdprintf(conn->net,"wconsd " VERSION " a serial port server\r\n\r\n");
|
|
send_help(conn->net);
|
|||
send(conn->net,"> ",2,0);
|
|||
while (conn->menuactive) {
|
|||
size=recv(conn->net,(void*)&buf,BUFSIZE,0);
|
|||
62045e98 | Hamish Coleman | if (size==0) {
|
|
c7c4a6f7 | Hamish Coleman | closesocket(conn->net);
|
|
conn->net=INVALID_SOCKET;
|
|||
62045e98 | Hamish Coleman | SetEvent(connectionCloseEvent);
|
|
c7c4a6f7 | Hamish Coleman | return 0; /* signal running=0 */
|
|
62045e98 | Hamish Coleman | }
|
|
if (size==SOCKET_ERROR) {
|
|||
/* General paranoia about blocking sockets */
|
|||
c7c4a6f7 | Hamish Coleman | ioctlsocket(conn->net,FIONBIO,&zero);
|
|
53110989 | Hamish Coleman | continue;
|
|
62045e98 | Hamish Coleman | }
|
|
53110989 | Hamish Coleman | for (i = 0; i < size; i++) {
|
|
b9a4e090 | Hamish Coleman | if (buf[i]==0) {
|
|
// strange, I'm not expecting nul bytes !
|
|||
continue;
|
|||
} else if (buf[i] == 127 || buf[i]==8) {
|
|||
// backspace
|
|||
53110989 | Hamish Coleman | if (linelen > 0) {
|
|
c7c4a6f7 | Hamish Coleman | send(conn->net,"\x08",1,0);
|
|
53110989 | Hamish Coleman | linelen--;
|
|
} else {
|
|||
c7c4a6f7 | Hamish Coleman | /* if the linebuf is empty, ring the bell */
|
|
send(conn->net,"\x07",1,0);
|
|||
53110989 | Hamish Coleman | }
|
|
continue;
|
|||
b9a4e090 | Hamish Coleman | } else if (buf[i] == 0x0d || buf[i]==0x0a) {
|
|
// detected cr or lf
|
|||
0bcbd4d3 | Hamish Coleman | if (buf[i]==0x0a && skip_lf==1) {
|
|
skip_lf=0;
|
|||
b9a4e090 | Hamish Coleman | continue;
|
|
}
|
|||
0bcbd4d3 | Hamish Coleman | if (buf[i]==0x0d) {
|
|
skip_lf=1;
|
|||
}
|
|||
53110989 | Hamish Coleman | ||
0bcbd4d3 | Hamish Coleman | // echo the endofline
|
|
// FIXME - dont do this if linemode is on
|
|||
c7c4a6f7 | Hamish Coleman | send(conn->net,"\r\n",2,0);
|
|
53110989 | Hamish Coleman | ||
0bcbd4d3 | Hamish Coleman | if (linelen!=0) {
|
|
c7c4a6f7 | Hamish Coleman | int running;
|
|
0bcbd4d3 | Hamish Coleman | line[linelen]=0; // ensure string is terminated
|
|
c7c4a6f7 | Hamish Coleman | ||
running = process_menu_line(conn,(char*)line);
|
|||
if (!conn->menuactive) {
|
|||
/* exiting the menu.. */
|
|||
return running;
|
|||
0bcbd4d3 | Hamish Coleman | }
|
|
53110989 | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | send(conn->net,"> ",2,0);
|
|
53110989 | Hamish Coleman | linelen=0;
|
|
b9a4e090 | Hamish Coleman | continue;
|
|
} else if (buf[i]==0xff) {
|
|||
// telnet option packet
|
|||
// skip next two bytes as well
|
|||
i+=2;
|
|||
continue;
|
|||
53110989 | Hamish Coleman | } else {
|
|
// other chars
|
|||
0bcbd4d3 | Hamish Coleman | skip_lf=0;
|
|
53110989 | Hamish Coleman | if (linelen < MAXLEN - 1) {
|
|
line[linelen] = buf[i];
|
|||
linelen++;
|
|||
// FIXME - dont echo if the other end is in
|
|||
// linemode
|
|||
c7c4a6f7 | Hamish Coleman | send(conn->net,(void*)&buf[i],1,0); /* echo */
|
|
53110989 | Hamish Coleman | } else {
|
|
c7c4a6f7 | Hamish Coleman | send(conn->net,"\x07",1,0); /* linebuf full bell */
|
|
62045e98 | Hamish Coleman | }
|
|
b9a4e090 | Hamish Coleman | continue;
|
|
62045e98 | Hamish Coleman | }
|
|
}
|
|||
}
|
|||
c7c4a6f7 | Hamish Coleman | ||
/* not reached */
|
|||
62045e98 | Hamish Coleman | return 0;
|
|
78d4c0b0 | Hamish Coleman | }
|
|
ef78cec5 | Hamish Coleman | DWORD WINAPI thread_new_connection(LPVOID lpParam) {
|
|
c7c4a6f7 | Hamish Coleman | struct connection * conn = (struct connection*)lpParam;
|
|
ef78cec5 | Hamish Coleman | HANDLE netThread=NULL, comThread=NULL;
|
|
c7c4a6f7 | Hamish Coleman | int running=1;
|
|
ef78cec5 | Hamish Coleman | ||
c7c4a6f7 | Hamish Coleman | while(running) {
|
|
if (conn->menuactive) {
|
|||
/* run the menu to ask the user questions */
|
|||
running = run_menu(conn);
|
|||
} else {
|
|||
/* they must have opened the com port, so start the threads */
|
|||
PurgeComm(hCom,PURGE_RXCLEAR|PURGE_RXABORT);
|
|||
netThread=CreateThread(NULL,0,wconsd_net_to_com,NULL,0,NULL);
|
|||
comThread=CreateThread(NULL,0,wconsd_com_to_net,NULL,0,NULL);
|
|||
/* since I dont wait for the threads, we finish here */
|
|||
running = 0;
|
|||
}
|
|||
}
|
|||
ef78cec5 | Hamish Coleman | ||
c7c4a6f7 | Hamish Coleman | /* cleanup */
|
|
dprintf(1,"wconsd: connection closed\n");
|
|||
/* TODO print bytecounts */
|
|||
/* maybe close file descriptors? */
|
|||
ef78cec5 | Hamish Coleman | ||
return 0;
|
|||
}
|
|||
78d4c0b0 | Hamish Coleman | static void wconsd_main(void)
|
|
{
|
|||
HANDLE wait_array[3];
|
|||
BOOL run=TRUE;
|
|||
DWORD o;
|
|||
SOCKET as;
|
|||
HANDLE netThread=NULL, comThread=NULL;
|
|||
c0d1e8fe | Hamish Coleman | unsigned long zero=0;
|
|
78d4c0b0 | Hamish Coleman | ||
ef78cec5 | Hamish Coleman | struct sockaddr_in sa;
|
|
int salen;
|
|||
int i;
|
|||
/* clear out any bogus data in the connections table */
|
|||
for (i=0;i<MAXCONNECTIONS;i++) {
|
|||
connection[i].connected = 0;
|
|||
}
|
|||
78d4c0b0 | Hamish Coleman | /* Main loop: wait for a connection, service it, repeat
|
|
* until signalled that the service is terminating */
|
|||
wait_array[0]=stopEvent;
|
|||
wait_array[1]=listenSocketEvent;
|
|||
wait_array[2]=connectionCloseEvent;
|
|||
while (run) {
|
|||
o=WaitForMultipleObjects(3,wait_array,FALSE,INFINITE);
|
|||
switch (o-WAIT_OBJECT_0) {
|
|||
ef78cec5 | Hamish Coleman | case 0: /* stopEvent */
|
|
78d4c0b0 | Hamish Coleman | run=FALSE;
|
|
ResetEvent(stopEvent);
|
|||
break;
|
|||
ef78cec5 | Hamish Coleman | case 1: /* listenSocketEvent */
|
|
/* There is an incoming connection */
|
|||
78d4c0b0 | Hamish Coleman | WSAResetEvent(listenSocketEvent);
|
|
ef78cec5 | Hamish Coleman | salen = sizeof(sa);
|
|
as=accept(ls,(struct sockaddr*)&sa,&salen);
|
|||
if (as==INVALID_SOCKET) {
|
|||
break;
|
|||
}
|
|||
53110989 | Hamish Coleman | ||
ef78cec5 | Hamish Coleman | dprintf(1,"wconsd: new connection from %08x\n",
|
|
sa.sin_addr.s_addr);
|
|||
53110989 | Hamish Coleman | ||
ef78cec5 | Hamish Coleman | /* search for an empty connection slot */
|
|
i=0;
|
|||
while(connection[i].connected && i<MAXCONNECTIONS) {
|
|||
i++;
|
|||
}
|
|||
if (i==MAXCONNECTIONS) {
|
|||
dprintf(1,"wconsd: connection table overflow\n");
|
|||
/* FIXME - properly reject the incoming connection */
|
|||
/* for now, just close the socket */
|
|||
closesocket(as);
|
|||
break;
|
|||
78d4c0b0 | Hamish Coleman | }
|
|
c7c4a6f7 | Hamish Coleman | connection[i].connected=1; /* mark this entry busy */
|
|
connection[i].menuactive=1; /* start in the menu */
|
|||
ef78cec5 | Hamish Coleman | connection[i].menuThread=NULL;
|
|
connection[i].net=as;
|
|||
connection[i].netThread=NULL;
|
|||
connection[i].serial=INVALID_HANDLE_VALUE;
|
|||
connection[i].serialThread=NULL;
|
|||
connection[i].net_bytes_rx=0;
|
|||
connection[i].net_bytes_tx=0;
|
|||
dprintf(1,"wconsd: accepted connection id %i\n",i);
|
|||
/* we successfully accepted the connection */
|
|||
if (cs!=INVALID_SOCKET) {
|
|||
/* There is an existing connected socket */
|
|||
/* Close down the existing connection and let the new one through */
|
|||
SetEvent(threadTermEvent);
|
|||
shutdown(cs,SD_BOTH);
|
|||
WaitForSingleObject(netThread,INFINITE);
|
|||
WaitForSingleObject(comThread,INFINITE);
|
|||
CloseHandle(netThread);
|
|||
CloseHandle(comThread);
|
|||
closesocket(cs);
|
|||
ResetEvent(connectionCloseEvent);
|
|||
ResetEvent(threadTermEvent);
|
|||
}
|
|||
cs=as;
|
|||
ioctlsocket(cs,FIONBIO,&zero);
|
|||
connection[i].menuThread = CreateThread(NULL,0,thread_new_connection,&connection[i],0,NULL);
|
|||
78d4c0b0 | Hamish Coleman | break;
|
|
ef78cec5 | Hamish Coleman | case 2: /* connectionCloseEvent*/
|
|
/* The data connection has been broken */
|
|||
b0f1897b | Hamish Coleman | dprintf(1,"wconsd: connection closed\n");
|
|
78d4c0b0 | Hamish Coleman | SetEvent(threadTermEvent);
|
|
WaitForSingleObject(netThread,INFINITE);
|
|||
WaitForSingleObject(comThread,INFINITE);
|
|||
CloseHandle(netThread);
|
|||
CloseHandle(comThread);
|
|||
netThread=NULL;
|
|||
comThread=NULL;
|
|||
if (cs!=INVALID_SOCKET) {
|
|||
shutdown(cs,SD_BOTH);
|
|||
closesocket(cs);
|
|||
cs=INVALID_SOCKET;
|
|||
}
|
|||
ResetEvent(connectionCloseEvent);
|
|||
62045e98 | Hamish Coleman | ResetEvent(threadTermEvent);
|
|
if (com_autoclose) {
|
|||
close_com_port();
|
|||
ff03d660 | Hamish Coleman | }
|
|
78d4c0b0 | Hamish Coleman | break;
|
|
default:
|
|||
run=FALSE; // Stop the service - I want to get off!
|
|||
break;
|
|||
}
|
|||
}
|
|||
if (cs!=INVALID_SOCKET) {
|
|||
shutdown(cs,SD_BOTH);
|
|||
closesocket(cs);
|
|||
}
|
|||
if (netThread || comThread) {
|
|||
SetEvent(threadTermEvent);
|
|||
WaitForSingleObject(netThread,INFINITE);
|
|||
WaitForSingleObject(comThread,INFINITE);
|
|||
CloseHandle(netThread);
|
|||
CloseHandle(comThread);
|
|||
}
|
|||
CloseHandle(hCom);
|
|||
closesocket(ls);
|
|||
WSACleanup();
|
|||
}
|
|||
VOID WINAPI MyServiceCtrlHandler(DWORD opcode)
|
|||
{
|
|||
DWORD status;
|
|||
switch(opcode) {
|
|||
case SERVICE_CONTROL_STOP:
|
|||
wconsd_status.dwWin32ExitCode = 0;
|
|||
wconsd_status.dwCurrentState = SERVICE_STOP_PENDING;
|
|||
wconsd_status.dwCheckPoint = 0;
|
|||
wconsd_status.dwWaitHint = 0;
|
|||
if (!SetServiceStatus(wconsd_statusHandle, &wconsd_status)) {
|
|||
status = GetLastError();
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: SetServiceStatus error %ld\n",status);
|
|
78d4c0b0 | Hamish Coleman | }
|
|
SetEvent(stopEvent);
|
|||
break;
|
|||
case SERVICE_CONTROL_INTERROGATE:
|
|||
// fall through to send current status
|
|||
break;
|
|||
default:
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: unrecognised opcode %ld\n",opcode);
|
|
78d4c0b0 | Hamish Coleman | break;
|
|
}
|
|||
// Send current status
|
|||
if (!SetServiceStatus(wconsd_statusHandle, &wconsd_status)) {
|
|||
status = GetLastError();
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: SetServiceStatus error %ld\n",status);
|
|
78d4c0b0 | Hamish Coleman | }
|
|
return;
|
|||
}
|
|||
VOID WINAPI ServiceStart(DWORD argc, LPSTR *argv)
|
|||
{
|
|||
DWORD status;
|
|||
DWORD specificError;
|
|||
wconsd_status.dwServiceType = SERVICE_WIN32;
|
|||
wconsd_status.dwCurrentState = SERVICE_START_PENDING;
|
|||
wconsd_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
|||
wconsd_status.dwWin32ExitCode = 0;
|
|||
wconsd_status.dwServiceSpecificExitCode = 0;
|
|||
wconsd_status.dwCheckPoint = 0;
|
|||
wconsd_status.dwWaitHint = 0;
|
|||
25fa1d20 | Hamish Coleman | wconsd_statusHandle = RegisterServiceCtrlHandler(TEXT("wconsd"),MyServiceCtrlHandler);
|
|
78d4c0b0 | Hamish Coleman | ||
if (wconsd_statusHandle == (SERVICE_STATUS_HANDLE)0) {
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: RegisterServiceCtrlHandler failed %d\n", GetLastError());
|
|
78d4c0b0 | Hamish Coleman | return;
|
|
}
|
|||
status = wconsd_init(argc, argv, &specificError);
|
|||
if (status != NO_ERROR) {
|
|||
wconsd_status.dwCurrentState = SERVICE_STOPPED;
|
|||
wconsd_status.dwCheckPoint = 0;
|
|||
wconsd_status.dwWaitHint = 0;
|
|||
wconsd_status.dwWin32ExitCode = status;
|
|||
wconsd_status.dwServiceSpecificExitCode = specificError;
|
|||
SetServiceStatus(wconsd_statusHandle, &wconsd_status);
|
|||
return;
|
|||
}
|
|||
/* Initialisation complete - report running status */
|
|||
wconsd_status.dwCurrentState = SERVICE_RUNNING;
|
|||
wconsd_status.dwCheckPoint = 0;
|
|||
wconsd_status.dwWaitHint = 0;
|
|||
if (!SetServiceStatus(wconsd_statusHandle, &wconsd_status)) {
|
|||
status = GetLastError();
|
|||
9b2f3609 | Hamish Coleman | dprintf(1,"wconsd: SetServiceStatus error %ld\n",status);
|
|
78d4c0b0 | Hamish Coleman | }
|
|
wconsd_main();
|
|||
wconsd_status.dwCurrentState = SERVICE_STOPPED;
|
|||
wconsd_status.dwCheckPoint = 0;
|
|||
wconsd_status.dwWaitHint = 0;
|
|||
wconsd_status.dwWin32ExitCode = 0;
|
|||
wconsd_status.dwServiceSpecificExitCode = 0;
|
|||
SetServiceStatus(wconsd_statusHandle, &wconsd_status);
|
|||
return;
|
|||
}
|
|||
static void RegisterService(LPSTR path)
|
|||
{
|
|||
SC_HANDLE schSCManager, schService;
|
|||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
|||
schService = CreateService(
|
|||
schSCManager,
|
|||
TEXT("wconsd"),
|
|||
ff03d660 | Hamish Coleman | TEXT("wconsd - a serial port server"),
|
|
78d4c0b0 | Hamish Coleman | SERVICE_ALL_ACCESS,
|
|
SERVICE_WIN32_OWN_PROCESS,
|
|||
SERVICE_AUTO_START,
|
|||
SERVICE_ERROR_NORMAL,
|
|||
path,
|
|||
NULL, NULL, NULL, NULL, NULL);
|
|||
if (schService == NULL) {
|
|||
printf("CreateService failed\n");
|
|||
} else {
|
|||
printf("Created service 'wconsd', binary path %s\n",path);
|
|||
printf("You should now start the service using the service manager.\n");
|
|||
}
|
|||
CloseServiceHandle(schService);
|
|||
}
|
|||
static void RemoveService(void)
|
|||
{
|
|||
SC_HANDLE schSCManager, schService;
|
|||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
|||
if (schSCManager == NULL) {
|
|||
printf("Couldn't open service manager\n");
|
|||
return;
|
|||
}
|
|||
schService = OpenService(schSCManager, TEXT("wconsd"), DELETE);
|
|||
if (schService == NULL) {
|
|||
printf("Couldn't open wconsd service\n");
|
|||
return;
|
|||
}
|
|||
if (!DeleteService(schService)) {
|
|||
printf("Couldn't delete wconsd service\n");
|
|||
return;
|
|||
}
|
|||
printf("Deleted service 'wconsd'\n");
|
|||
CloseServiceHandle(schService);
|
|||
}
|
|||
static void usage(void)
|
|||
{
|
|||
printf("Usage: wconsd [-i pathname | -r | -d]\n");
|
|||
printf(" -i pathname install service 'wconsd'; pathname\n");
|
|||
printf(" must be the full path to the binary\n");
|
|||
printf(" -r remove service 'wconsd'\n");
|
|||
printf(" -d run wconsd in debug mode (in the foreground)\n");
|
|||
}
|
|||
int main(int argc, char **argv)
|
|||
{
|
|||
DWORD err;
|
|||
62727af2 | Hamish Coleman | int console_application=0;
|
|
78d4c0b0 | Hamish Coleman | SERVICE_TABLE_ENTRY DispatchTable[] =
|
|
{
|
|||
{ "wconsd", ServiceStart },
|
|||
{ NULL, NULL }
|
|||
62045e98 | Hamish Coleman | };
|
|
78d4c0b0 | Hamish Coleman | ||
62727af2 | Hamish Coleman | // debug info for when I test this as a service
|
|
dprintf(1,"wconsd: started with argc==%i\n",argc);
|
|||
62045e98 | Hamish Coleman | if (argc==1 || argc==0) {
|
|
62727af2 | Hamish Coleman | ||
// assume that our messages are going to the debug log
|
|||
53110989 | Hamish Coleman | debug_mode=0;
|
|
62727af2 | Hamish Coleman | ||
// start by trying to run as a service
|
|||
if (StartServiceCtrlDispatcher(DispatchTable)==0) {
|
|||
err = GetLastError();
|
|||
dprintf(1,"wconsd: StartServiceCtrlDispatcher error = %d\n", err);
|
|||
if (err != ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
|
|||
// any other error, assume fatal
|
|||
return 1;
|
|||
}
|
|||
78d4c0b0 | Hamish Coleman | }
|
|
62727af2 | Hamish Coleman | ||
// fall through and try running as a command-line application
|
|||
console_application=1;
|
|||
78d4c0b0 | Hamish Coleman | }
|
|
53110989 | Hamish Coleman | // We are running in debug mode (or any other command-line mode)
|
|
debug_mode=1;
|
|||
62727af2 | Hamish Coleman | dprintf(1,"wconsd: Serial Console server\n");
|
|
c2484981 | Hamish Coleman | if (argc>1) {
|
|
62727af2 | Hamish Coleman | if (strcmp(argv[1],"-i")==0) {
|
|
// request service installation
|
|||
if (argc!=3) {
|
|||
usage();
|
|||
return 1;
|
|||
}
|
|||
RegisterService(argv[2]);
|
|||
return 0;
|
|||
} else if (strcmp(argv[1],"-r")==0) {
|
|||
// request service removal
|
|||
RemoveService();
|
|||
return 0;
|
|||
780d7607 | Hamish Coleman | } else if (strcmp(argv[1],"-p")==0) {
|
|
console_application=1;
|
|||
default_tcpport = atoi(argv[2]);
|
|||
62727af2 | Hamish Coleman | } else if (strcmp(argv[1],"-d")==0) {
|
|
console_application=1;
|
|||
} else {
|
|||
78d4c0b0 | Hamish Coleman | usage();
|
|
return 1;
|
|||
}
|
|||
62727af2 | Hamish Coleman | }
|
|
c2484981 | Hamish Coleman | printf("wconsd: listen on port %i\n",default_tcpport);
|
|
62727af2 | Hamish Coleman | // if we have decided to run as a console app..
|
|
if (console_application) {
|
|||
78d4c0b0 | Hamish Coleman | int r;
|
|
b9a4e090 | Hamish Coleman | printf("wconsd: Console Application Mode (version %s)\n",VERSION);
|
|
78d4c0b0 | Hamish Coleman | r=wconsd_init(argc,argv,&err);
|
|
if (r!=0) {
|
|||
ef78cec5 | Hamish Coleman | printf("wconsd: wconsd_init failed, return code %d [%d]\n",r, err);
|
|
78d4c0b0 | Hamish Coleman | return 1;
|
|
}
|
|||
wconsd_main();
|
|||
62727af2 | Hamish Coleman | }
|
|
return 0;
|
|||
78d4c0b0 | Hamish Coleman | }
|