Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/teraterm/teraterm/commlib.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 6738 - (show annotations) (download) (as text)
Sat May 20 06:38:01 2017 UTC (6 years, 9 months ago) by maya
File MIME type: text/x-csrc
File size: 32379 byte(s)
NO_INET6 マクロを削除
1 /* Tera Term
2 Copyright(C) 1994-1998 T. Teranishi
3 All rights reserved. */
4 /* IPv6 modification is Copyright (C) 2000, 2001 Jun-ya KATO <kato@win6.jp> */
5
6 /* TERATERM.EXE, Communication routines */
7 #include "teraterm.h"
8 #include "tttypes.h"
9 #include "tt_res.h"
10 #include <process.h>
11
12 #include "ttcommon.h"
13 #include "ttwsk.h"
14 #include "ttlib.h"
15 #include "ttfileio.h"
16 #include "ttplug.h" /* TTPLUG */
17
18 #include "commlib.h"
19 #include <winsock2.h>
20 #include <ws2tcpip.h>
21 #include <stdio.h> /* for _snprintf() */
22 #include <time.h>
23 #include <locale.h>
24
25 static SOCKET OpenSocket(PComVar);
26 static void AsyncConnect(PComVar);
27 static int CloseSocket(SOCKET);
28
29 /* create socket */
30 static SOCKET OpenSocket(PComVar cv)
31 {
32 cv->s = cv->res->ai_family;
33 cv->s = Psocket(cv->res->ai_family, cv->res->ai_socktype, cv->res->ai_protocol);
34 return cv->s;
35 }
36
37 /* connect with asynchronous mode */
38 static void AsyncConnect(PComVar cv)
39 {
40 int Err;
41 BOOL BBuf;
42 BBuf = TRUE;
43 /* set synchronous mode */
44 PWSAAsyncSelect(cv->s,cv->HWin,0,0);
45 Psetsockopt(cv->s,(int)SOL_SOCKET,SO_OOBINLINE,(char FAR *)&BBuf,sizeof(BBuf));
46 /* set asynchronous mode */
47 PWSAAsyncSelect(cv->s,cv->HWin,WM_USER_COMMOPEN, FD_CONNECT);
48
49 // �z�X�g���������������������������A�����I���\�P�b�g���N���[�Y�����A
50 // �����������L�����Z���������B�l��0�������������������B
51 // (2007.1.11 yutaka)
52 if (*cv->ConnetingTimeout > 0) {
53 SetTimer(cv->HWin, IdCancelConnectTimer, *cv->ConnetingTimeout * 1000, NULL);
54 }
55
56 /* WM_USER_COMMOPEN occurs, CommOpen is called, then CommStart is called */
57 Err = Pconnect(cv->s, cv->res->ai_addr, cv->res->ai_addrlen);
58 if (Err != 0) {
59 Err = PWSAGetLastError();
60 if (Err == WSAEWOULDBLOCK) {
61 /* Do nothing */
62 } else if (Err!=0 ) {
63 PostMessage(cv->HWin, WM_USER_COMMOPEN,0,
64 MAKELONG(FD_CONNECT,Err));
65 }
66 }
67 }
68
69 /* close socket */
70 static int CloseSocket(SOCKET s)
71 {
72 return Pclosesocket(s);
73 }
74
75 #define CommInQueSize 8192
76 #define CommOutQueSize 2048
77 #define CommXonLim 2048
78 #define CommXoffLim 2048
79
80 #define READENDNAME "ReadEnd"
81 #define WRITENAME "Write"
82 #define READNAME "Read"
83 #define PRNWRITENAME "PrnWrite"
84
85 static HANDLE ReadEnd;
86 static OVERLAPPED wol, rol;
87
88 // Winsock async operation handle
89 static HANDLE HAsync=0;
90
91 BOOL TCPIPClosed = TRUE;
92
93 /* Printer port handle for
94 direct pass-thru printing */
95 static HANDLE PrnID = INVALID_HANDLE_VALUE;
96 static BOOL LPTFlag;
97
98 // Initialize ComVar.
99 // This routine is called only once
100 // by the initialization procedure of Tera Term.
101 void CommInit(PComVar cv)
102 {
103 cv->Open = FALSE;
104 cv->Ready = FALSE;
105
106 // log-buffer variables
107 cv->HLogBuf = 0;
108 cv->HBinBuf = 0;
109 cv->LogBuf = NULL;
110 cv->BinBuf = NULL;
111 cv->LogPtr = 0;
112 cv->LStart = 0;
113 cv->LCount = 0;
114 cv->BinPtr = 0;
115 cv->BStart = 0;
116 cv->BCount = 0;
117 cv->DStart = 0;
118 cv->DCount = 0;
119 cv->BinSkip = 0;
120 cv->FilePause = 0;
121 cv->ProtoFlag = FALSE;
122 /* message flag */
123 cv->NoMsg = 0;
124
125 cv->NotifyIcon = NULL;
126 }
127
128 /* reset a serial port which is already open */
129 void CommResetSerial(PTTSet ts, PComVar cv, BOOL ClearBuff)
130 {
131 DCB dcb;
132 DWORD DErr;
133 COMMTIMEOUTS ctmo;
134
135 if (! cv->Open ||
136 (cv->PortType != IdSerial)) {
137 return;
138 }
139
140 ClearCommError(cv->ComID,&DErr,NULL);
141 SetupComm(cv->ComID,CommInQueSize,CommOutQueSize);
142 /* flush input and output buffers */
143 if (ClearBuff) {
144 PurgeComm(cv->ComID, PURGE_TXABORT | PURGE_RXABORT |
145 PURGE_TXCLEAR | PURGE_RXCLEAR);
146 }
147
148 memset(&ctmo,0,sizeof(ctmo));
149 ctmo.ReadIntervalTimeout = MAXDWORD;
150 ctmo.WriteTotalTimeoutConstant = 500;
151 SetCommTimeouts(cv->ComID,&ctmo);
152 cv->InBuffCount = 0;
153 cv->InPtr = 0;
154 cv->OutBuffCount = 0;
155 cv->OutPtr = 0;
156
157 cv->DelayPerChar = ts->DelayPerChar;
158 cv->DelayPerLine = ts->DelayPerLine;
159
160 memset(&dcb,0,sizeof(DCB));
161 dcb.DCBlength = sizeof(DCB);
162 dcb.BaudRate = ts->Baud;
163 dcb.fBinary = TRUE;
164 switch (ts->Parity) {
165 case IdParityNone:
166 dcb.Parity = NOPARITY;
167 break;
168 case IdParityOdd:
169 dcb.fParity = TRUE;
170 dcb.Parity = ODDPARITY;
171 break;
172 case IdParityEven:
173 dcb.fParity = TRUE;
174 dcb.Parity = EVENPARITY;
175 break;
176 case IdParityMark:
177 dcb.fParity = TRUE;
178 dcb.Parity = MARKPARITY;
179 break;
180 case IdParitySpace:
181 dcb.fParity = TRUE;
182 dcb.Parity = SPACEPARITY;
183 break;
184 }
185
186 dcb.fDtrControl = DTR_CONTROL_ENABLE;
187 dcb.fRtsControl = RTS_CONTROL_ENABLE;
188 switch (ts->Flow) {
189 case IdFlowX:
190 dcb.fOutX = TRUE;
191 dcb.fInX = TRUE;
192 dcb.XonLim = CommXonLim;
193 dcb.XoffLim = CommXoffLim;
194 dcb.XonChar = XON;
195 dcb.XoffChar = XOFF;
196 break;
197 case IdFlowHard:
198 dcb.fOutxCtsFlow = TRUE;
199 dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
200 break;
201 }
202
203 switch (ts->DataBit) {
204 case IdDataBit7:
205 dcb.ByteSize = 7;
206 break;
207 case IdDataBit8:
208 dcb.ByteSize = 8;
209 break;
210 }
211 switch (ts->StopBit) {
212 case IdStopBit1:
213 dcb.StopBits = ONESTOPBIT;
214 break;
215 case IdStopBit15:
216 dcb.StopBits = ONE5STOPBITS;
217 break;
218 case IdStopBit2:
219 dcb.StopBits = TWOSTOPBITS;
220 break;
221 }
222
223 SetCommState(cv->ComID, &dcb);
224
225 /* enable receive request */
226 SetCommMask(cv->ComID,0);
227 SetCommMask(cv->ComID,EV_RXCHAR);
228 }
229
230 // ���O�t���p�C�v�����������������`�F�b�N�����B
231 // \\ServerName\pipe\PipeName
232 //
233 // return 0: ������
234 // -1: �s��
235 // (2012.3.10 yutaka)
236 int CheckNamedPipeFormat(char *p, int size)
237 {
238 int ret = -1;
239 char *s;
240
241 if (size <= 8)
242 goto error;
243
244 if (p[0] == '\\' && p[1] == '\\') {
245 s = strchr(&p[2], '\\');
246 if (s && _strnicmp(s+1, "pipe\\", 5) == 0) {
247 ret = 0;
248 }
249 }
250
251 error:
252 return (ret);
253 }
254
255 void CommOpen(HWND HW, PTTSet ts, PComVar cv)
256 {
257 char ErrMsg[21+256];
258 char P[50+256];
259
260 MSG Msg;
261 ADDRINFO hints;
262 char pname[NI_MAXSERV];
263
264 BOOL InvalidHost;
265
266 char uimsg[MAX_UIMSG];
267
268 // �z�X�g�������O�t���p�C�v�����������������B
269 if (ts->PortType == IdTCPIP) {
270 if (CheckNamedPipeFormat(ts->HostName, strlen(ts->HostName)) == 0) {
271 ts->PortType = IdNamedPipe;
272 }
273 }
274
275 /* initialize ComVar */
276 cv->InBuffCount = 0;
277 cv->InPtr = 0;
278 cv->OutBuffCount = 0;
279 cv->OutPtr = 0;
280 cv->HWin = HW;
281 cv->Ready = FALSE;
282 cv->Open = FALSE;
283 cv->PortType = ts->PortType;
284 cv->ComPort = 0;
285 cv->RetryCount = 0;
286 cv->RetryWithOtherProtocol = TRUE;
287 cv->s = INVALID_SOCKET;
288 cv->ComID = INVALID_HANDLE_VALUE;
289 cv->CanSend = TRUE;
290 cv->RRQ = FALSE;
291 cv->SendKanjiFlag = FALSE;
292 cv->SendCode = IdASCII;
293 cv->EchoKanjiFlag = FALSE;
294 cv->EchoCode = IdASCII;
295 cv->Language = ts->Language;
296 cv->CRSend = ts->CRSend;
297 cv->KanjiCodeEcho = ts->KanjiCode;
298 cv->JIS7KatakanaEcho = ts->JIS7Katakana;
299 cv->KanjiCodeSend = ts->KanjiCodeSend;
300 cv->JIS7KatakanaSend = ts->JIS7KatakanaSend;
301 cv->KanjiIn = ts->KanjiIn;
302 cv->KanjiOut = ts->KanjiOut;
303 cv->RussHost = ts->RussHost;
304 cv->RussClient = ts->RussClient;
305 cv->DelayFlag = TRUE;
306 cv->DelayPerChar = ts->DelayPerChar;
307 cv->DelayPerLine = ts->DelayPerLine;
308 cv->TelBinRecv = FALSE;
309 cv->TelBinSend = FALSE;
310 cv->TelFlag = FALSE;
311 cv->TelMode = FALSE;
312 cv->IACFlag = FALSE;
313 cv->TelCRFlag = FALSE;
314 cv->TelCRSend = FALSE;
315 cv->TelCRSendEcho = FALSE;
316 cv->TelAutoDetect = ts->TelAutoDetect; /* TTPLUG */
317 cv->Locale = ts->Locale;
318 cv->locale = _create_locale(LC_ALL, cv->Locale);
319 cv->CodePage = &ts->CodePage;
320 cv->ConnetingTimeout = &ts->ConnectingTimeout;
321 cv->LastSendTime = time(NULL);
322 cv->LineModeBuffCount = 0;
323 cv->Flush = FALSE;
324 cv->FlushLen = 0;
325 cv->TelLineMode = FALSE;
326
327 if ((ts->PortType!=IdSerial) && (strlen(ts->HostName)==0))
328 {
329 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
330 return;
331 }
332
333 switch (ts->PortType) {
334 case IdTCPIP:
335 cv->TelFlag = (ts->Telnet > 0);
336 if (ts->EnableLineMode) {
337 cv->TelLineMode = TRUE;
338 }
339 if (! LoadWinsock()) {
340 if (cv->NoMsg==0) {
341 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
342 get_lang_msg("MSG_WINSOCK_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Cannot use winsock", ts->UILanguageFile);
343 MessageBox(cv->HWin,ts->UIMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
344 }
345 InvalidHost = TRUE;
346 }
347 else {
348 TTXOpenTCP(); /* TTPLUG */
349 cv->Open = TRUE;
350 /* resolving address */
351 memset(&hints, 0, sizeof(hints));
352 hints.ai_family = ts->ProtocolFamily;
353 hints.ai_socktype = SOCK_STREAM;
354 hints.ai_protocol = IPPROTO_TCP;
355 _snprintf_s(pname, sizeof(pname), _TRUNCATE, "%d", ts->TCPPort);
356
357 HAsync = PWSAAsyncGetAddrInfo(HW, WM_USER_GETHOST,
358 ts->HostName, pname, &hints, &cv->res0);
359 if (HAsync == 0)
360 InvalidHost = TRUE;
361 else {
362 cv->ComPort = 1; // set "getting host" flag
363 // (see CVTWindow::OnSysCommand())
364 do {
365 if (GetMessage(&Msg,0,0,0)) {
366 if ((Msg.hwnd==HW) &&
367 ((Msg.message == WM_SYSCOMMAND) &&
368 ((Msg.wParam & 0xfff0) == SC_CLOSE) ||
369 (Msg.message == WM_COMMAND) &&
370 (LOWORD(Msg.wParam) == ID_FILE_EXIT) ||
371 (Msg.message == WM_CLOSE))) { /* Exit when the user closes Tera Term */
372 PWSACancelAsyncRequest(HAsync);
373 CloseHandle(HAsync);
374 HAsync = 0;
375 cv->ComPort = 0; // clear "getting host" flag
376 PostMessage(HW,Msg.message,Msg.wParam,Msg.lParam);
377 return;
378 }
379 if (Msg.message != WM_USER_GETHOST) { /* Prosess messages */
380 TranslateMessage(&Msg);
381 DispatchMessage(&Msg);
382 }
383 }
384 else {
385 return;
386 }
387 } while (Msg.message!=WM_USER_GETHOST);
388 cv->ComPort = 0; // clear "getting host" flag
389 CloseHandle(HAsync);
390 HAsync = 0;
391 InvalidHost = WSAGETASYNCERROR(Msg.lParam) != 0;
392 }
393 } /* if (!LoadWinsock()) */
394
395 if (InvalidHost) {
396 if (cv->NoMsg==0) {
397 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
398 get_lang_msg("MSG_INVALID_HOST_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Invalid host", ts->UILanguageFile);
399 MessageBox(cv->HWin,ts->UIMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
400 }
401 goto BreakSC;
402 }
403 for (cv->res = cv->res0; cv->res; cv->res = cv->res->ai_next) {
404 cv->s = OpenSocket(cv);
405 if (cv->s == INVALID_SOCKET) {
406 CloseSocket(cv->s);
407 continue;
408 }
409 /* start asynchronous connect */
410 AsyncConnect(cv);
411 break; /* break for-loop immediately */
412 }
413 break;
414
415 case IdSerial:
416 InitFileIO(IdSerial); /* TTPLUG */
417 TTXOpenFile(); /* TTPLUG */
418 _snprintf_s(P, sizeof(P), _TRUNCATE, "COM%d", ts->ComPort);
419 strncpy_s(ErrMsg, sizeof(ErrMsg),P, _TRUNCATE);
420 strncpy_s(P, sizeof(P),"\\\\.\\", _TRUNCATE);
421 strncat_s(P, sizeof(P),ErrMsg, _TRUNCATE);
422 cv->ComID =
423 PCreateFile(P,GENERIC_READ | GENERIC_WRITE,
424 0,NULL,OPEN_EXISTING,
425 FILE_FLAG_OVERLAPPED,NULL);
426 if (cv->ComID == INVALID_HANDLE_VALUE ) {
427 get_lang_msg("MSG_CANTOEPN_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Cannot open %s", ts->UILanguageFile);
428 _snprintf_s(ErrMsg, sizeof(ErrMsg), _TRUNCATE, ts->UIMsg, &P[4]);
429
430 if (cv->NoMsg==0) {
431 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
432 MessageBox(cv->HWin,ErrMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
433 }
434 InvalidHost = TRUE;
435 }
436 else {
437 cv->Open = TRUE;
438 cv->ComPort = ts->ComPort;
439 CommResetSerial(ts, cv, ts->ClearComBuffOnOpen);
440 if (!ts->ClearComBuffOnOpen) {
441 cv->RRQ = TRUE;
442 }
443
444 /* notify to VT window that Comm Port is open */
445 PostMessage(cv->HWin, WM_USER_COMMOPEN, 0, 0);
446 InvalidHost = FALSE;
447
448 SetCOMFlag(ts->ComPort);
449 }
450 break; /* end of "case IdSerial:" */
451
452 case IdFile:
453 InitFileIO(IdFile); /* TTPLUG */
454 TTXOpenFile(); /* TTPLUG */
455 cv->ComID = PCreateFile(ts->HostName,GENERIC_READ,0,NULL,
456 OPEN_EXISTING,0,NULL);
457 InvalidHost = (cv->ComID == INVALID_HANDLE_VALUE);
458 if (InvalidHost) {
459 if (cv->NoMsg==0) {
460 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
461 get_lang_msg("MSG_CANTOEPN_FILE_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Cannot open file", ts->UILanguageFile);
462 MessageBox(cv->HWin,ts->UIMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
463 }
464 }
465 else {
466 cv->Open = TRUE;
467 PostMessage(cv->HWin, WM_USER_COMMOPEN, 0, 0);
468 }
469 break;
470
471 case IdNamedPipe:
472 InitFileIO(IdNamedPipe); /* TTPLUG */
473 TTXOpenFile(); /* TTPLUG */
474
475 memset(P, 0, sizeof(P));
476 strncpy_s(P, sizeof(P), ts->HostName, _TRUNCATE);
477
478 // ���O�t���p�C�v�����������������`�F�b�N�����B
479 if (CheckNamedPipeFormat(P, strlen(P)) < 0) {
480 InvalidHost = TRUE;
481
482 _snprintf_s(ErrMsg, sizeof(ErrMsg), _TRUNCATE,
483 "Invalid pipe name (%d)\n\n"
484 "A valid pipe name has the form\n"
485 "\"\\\\<ServerName>\\pipe\\<PipeName>\"",
486 GetLastError());
487 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
488 MessageBox(cv->HWin,ErrMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
489 break;
490 }
491
492 cv->ComID =
493 PCreateFile(P,GENERIC_READ | GENERIC_WRITE,
494 0,NULL,OPEN_EXISTING,
495 0, // �u���b�L���O���[�h������(FILE_FLAG_OVERLAPPED ���w��������)
496 NULL);
497 if (cv->ComID == INVALID_HANDLE_VALUE ) {
498 get_lang_msg("MSG_CANTOEPN_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Cannot open %s", ts->UILanguageFile);
499 _snprintf_s(ErrMsg, sizeof(ErrMsg), _TRUNCATE, ts->UIMsg, &P[4]);
500
501 if (cv->NoMsg==0) {
502 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
503 MessageBox(cv->HWin,ErrMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
504 }
505 InvalidHost = TRUE;
506 }
507 else {
508 cv->Open = TRUE;
509 PostMessage(cv->HWin, WM_USER_COMMOPEN, 0, 0);
510 InvalidHost = FALSE;
511 }
512 break; /* end of "case IdNamedPipe:" */
513
514 } /* end of "switch" */
515
516 BreakSC:
517 if (InvalidHost) {
518 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
519 if ( (ts->PortType==IdTCPIP) && cv->Open ) {
520 if ( cv->s!=INVALID_SOCKET ) {
521 Pclosesocket(cv->s);
522 cv->s = INVALID_SOCKET; /* �\�P�b�g�����������t�����B(2010.8.6 yutaka) */
523 }
524 FreeWinsock();
525 }
526 return;
527 }
528 }
529
530 // ���O�t���p�C�v�p�X���b�h
531 void NamedPipeThread(void *arg)
532 {
533 PComVar cv = (PComVar)arg;
534 DWORD DErr;
535 HANDLE REnd;
536 char Temp[20];
537 char Buffer[1]; // 1byte
538 DWORD BytesRead, TotalBytesAvail, BytesLeftThisMessage;
539
540 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
541 REnd = OpenEvent(EVENT_ALL_ACCESS,FALSE, Temp);
542 while (TRUE) {
543 BytesRead = 0;
544 // ���O�t���p�C�v���C�x���g���������������������d�l�������A�L���[�����g��
545 // �`���������������AReadFile() ���������������f�����B
546 if (PeekNamedPipe(cv->ComID, Buffer, sizeof(Buffer), &BytesRead, &TotalBytesAvail, &BytesLeftThisMessage)) {
547 if (! cv->Ready) {
548 _endthread();
549 }
550 if (BytesRead == 0) { // �����������A�����������B
551 Sleep(1);
552 continue;
553 }
554 if (! cv->RRQ) {
555 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_READ);
556 }
557 // ReadFile() ���I�������������B
558 WaitForSingleObject(REnd,INFINITE);
559 }
560 else {
561 DErr = GetLastError();
562 // [VMware] this returns 109 (broken pipe) if a named pipe is removed.
563 // [Virtual Box] this returns 233 (pipe not connected) if a named pipe is removed.
564 if (! cv->Ready || ERROR_BROKEN_PIPE == DErr || ERROR_PIPE_NOT_CONNECTED == DErr) {
565 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
566 _endthread();
567 }
568 }
569 }
570 }
571
572 void CommThread(void *arg)
573 {
574 DWORD Evt;
575 PComVar cv = (PComVar)arg;
576 DWORD DErr;
577 HANDLE REnd;
578 char Temp[20];
579
580 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
581 REnd = OpenEvent(EVENT_ALL_ACCESS,FALSE, Temp);
582 while (TRUE) {
583 if (WaitCommEvent(cv->ComID,&Evt,NULL)) {
584 if (! cv->Ready) {
585 _endthread();
586 }
587 if (! cv->RRQ) {
588 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_READ);
589 }
590 WaitForSingleObject(REnd,INFINITE);
591 }
592 else {
593 DErr = GetLastError(); // this returns 995 (operation aborted) if a USB com port is removed
594 if (! cv->Ready || ERROR_OPERATION_ABORTED == DErr) {
595 _endthread();
596 }
597 ClearCommError(cv->ComID,&DErr,NULL);
598 }
599 }
600 }
601
602 void CommStart(PComVar cv, LONG lParam, PTTSet ts)
603 {
604 char ErrMsg[31];
605 char Temp[20];
606 char uimsg[MAX_UIMSG];
607
608 if (! cv->Open ) {
609 return;
610 }
611 if ( cv->Ready ) {
612 return;
613 }
614
615 // �L�����Z���^�C�}�����������������B�������A�������_�� WM_TIMER �����������������\���������B
616 if (*cv->ConnetingTimeout > 0) {
617 KillTimer(cv->HWin, IdCancelConnectTimer);
618 }
619
620 switch (cv->PortType) {
621 case IdTCPIP:
622 ErrMsg[0] = 0;
623 switch (HIWORD(lParam)) {
624 case WSAECONNREFUSED:
625 get_lang_msg("MSG_COMM_REFUSE_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Connection refused", ts->UILanguageFile);
626 _snprintf_s(ErrMsg, sizeof(ErrMsg), _TRUNCATE, "%s", ts->UIMsg);
627 break;
628 case WSAENETUNREACH:
629 get_lang_msg("MSG_COMM_REACH_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Network cannot be reached", ts->UILanguageFile);
630 _snprintf_s(ErrMsg, sizeof(ErrMsg), _TRUNCATE, "%s", ts->UIMsg);
631 break;
632 case WSAETIMEDOUT:
633 get_lang_msg("MSG_COMM_CONNECT_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Connection timed out", ts->UILanguageFile);
634 _snprintf_s(ErrMsg, sizeof(ErrMsg), _TRUNCATE, "%s", ts->UIMsg);
635 break;
636 default:
637 get_lang_msg("MSG_COMM_TIMEOUT_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Cannot connect the host", ts->UILanguageFile);
638 _snprintf_s(ErrMsg, sizeof(ErrMsg), _TRUNCATE, "%s", ts->UIMsg);
639 }
640 if (HIWORD(lParam)>0) {
641 /* connect() failed */
642 if (cv->res->ai_next != NULL) {
643 /* try to connect with other protocol */
644 CloseSocket(cv->s);
645 for (cv->res = cv->res->ai_next; cv->res; cv->res = cv->res->ai_next) {
646 cv->s = OpenSocket(cv);
647 if (cv->s == INVALID_SOCKET) {
648 CloseSocket(cv->s);
649 continue;
650 }
651 AsyncConnect(cv);
652 cv->Ready = FALSE;
653 cv->RetryWithOtherProtocol = TRUE; /* retry with other procotol */
654 return;
655 }
656 } else {
657 /* trying with all protocol family are failed */
658 if (cv->NoMsg==0)
659 {
660 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
661 MessageBox(cv->HWin,ErrMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
662 }
663 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
664 cv->RetryWithOtherProtocol = FALSE;
665 return;
666 }
667 }
668
669 /* here is connection established */
670 cv->RetryWithOtherProtocol = FALSE;
671 PWSAAsyncSelect(cv->s,cv->HWin,WM_USER_COMMNOTIFY, FD_READ | FD_OOB | FD_CLOSE);
672 TCPIPClosed = FALSE;
673 break;
674
675 case IdSerial:
676 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
677 ReadEnd = CreateEvent(NULL,FALSE,FALSE,Temp);
678 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", WRITENAME, cv->ComPort);
679 memset(&wol,0,sizeof(OVERLAPPED));
680 wol.hEvent = CreateEvent(NULL,TRUE,TRUE,Temp);
681 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READNAME, cv->ComPort);
682 memset(&rol,0,sizeof(OVERLAPPED));
683 rol.hEvent = CreateEvent(NULL,TRUE,FALSE,Temp);
684
685 /* create the receiver thread */
686 if (_beginthread(CommThread,0,cv) == -1) {
687 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
688 get_lang_msg("MSG_TT_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Can't create thread", ts->UILanguageFile);
689 MessageBox(cv->HWin,ts->UIMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
690 }
691 break;
692
693 case IdFile:
694 cv->RRQ = TRUE;
695 break;
696
697 case IdNamedPipe:
698 cv->ComPort = 0;
699 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READENDNAME, cv->ComPort);
700 ReadEnd = CreateEvent(NULL,FALSE,FALSE,Temp);
701 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", WRITENAME, cv->ComPort);
702 memset(&wol,0,sizeof(OVERLAPPED));
703 wol.hEvent = CreateEvent(NULL,TRUE,TRUE,Temp);
704 _snprintf_s(Temp, sizeof(Temp), _TRUNCATE, "%s%d", READNAME, cv->ComPort);
705 memset(&rol,0,sizeof(OVERLAPPED));
706 rol.hEvent = CreateEvent(NULL,TRUE,FALSE,Temp);
707
708 /* create the receiver thread */
709 if (_beginthread(NamedPipeThread,0,cv) == -1) {
710 get_lang_msg("MSG_TT_ERROR", uimsg, sizeof(uimsg), "Tera Term: Error", ts->UILanguageFile);
711 get_lang_msg("MSG_TT_ERROR", ts->UIMsg, sizeof(ts->UIMsg), "Can't create thread", ts->UILanguageFile);
712 MessageBox(cv->HWin,ts->UIMsg,uimsg,MB_TASKMODAL | MB_ICONEXCLAMATION);
713 }
714 break;
715 }
716 cv->Ready = TRUE;
717 }
718
719 BOOL CommCanClose(PComVar cv)
720 { // check if data remains in buffer
721 if (! cv->Open) {
722 return TRUE;
723 }
724 if (cv->InBuffCount>0) {
725 return FALSE;
726 }
727 if ((cv->HLogBuf!=NULL) &&
728 ((cv->LCount>0) ||
729 (cv->DCount>0))) {
730 return FALSE;
731 }
732 if ((cv->HBinBuf!=NULL) &&
733 (cv->BCount>0)) {
734 return FALSE;
735 }
736 return TRUE;
737 }
738
739 void CommClose(PComVar cv)
740 {
741 if ( ! cv->Open ) {
742 return;
743 }
744 cv->Open = FALSE;
745
746 /* disable event message posting & flush buffer */
747 cv->RRQ = FALSE;
748 cv->Ready = FALSE;
749 cv->InPtr = 0;
750 cv->InBuffCount = 0;
751 cv->OutPtr = 0;
752 cv->OutBuffCount = 0;
753 cv->LineModeBuffCount = 0;
754 cv->FlushLen = 0;
755 cv->Flush = FALSE;
756
757 /* close port & release resources */
758 switch (cv->PortType) {
759 case IdTCPIP:
760 if (HAsync!=0) {
761 PWSACancelAsyncRequest(HAsync);
762 }
763 HAsync = 0;
764 Pfreeaddrinfo(cv->res0);
765 if ( cv->s!=INVALID_SOCKET ) {
766 Pclosesocket(cv->s);
767 }
768 cv->s = INVALID_SOCKET;
769 TTXCloseTCP(); /* TTPLUG */
770 FreeWinsock();
771 break;
772 case IdSerial:
773 if ( cv->ComID != INVALID_HANDLE_VALUE ) {
774 CloseHandle(ReadEnd);
775 CloseHandle(wol.hEvent);
776 CloseHandle(rol.hEvent);
777 PurgeComm(cv->ComID, PURGE_TXABORT | PURGE_RXABORT |
778 PURGE_TXCLEAR | PURGE_RXCLEAR);
779 EscapeCommFunction(cv->ComID,CLRDTR);
780 SetCommMask(cv->ComID,0);
781 PCloseFile(cv->ComID);
782 ClearCOMFlag(cv->ComPort);
783 }
784 TTXCloseFile(); /* TTPLUG */
785 break;
786 case IdFile:
787 if (cv->ComID != INVALID_HANDLE_VALUE) {
788 PCloseFile(cv->ComID);
789 }
790 TTXCloseFile(); /* TTPLUG */
791 break;
792
793 case IdNamedPipe:
794 if ( cv->ComID != INVALID_HANDLE_VALUE ) {
795 CloseHandle(ReadEnd);
796 CloseHandle(wol.hEvent);
797 CloseHandle(rol.hEvent);
798 PCloseFile(cv->ComID);
799 }
800 TTXCloseFile(); /* TTPLUG */
801 break;
802 }
803 cv->ComID = INVALID_HANDLE_VALUE;
804 cv->PortType = 0;
805
806 _free_locale(cv->locale);
807 }
808
809 void CommProcRRQ(PComVar cv)
810 {
811 if ( ! cv->Ready ) {
812 return;
813 }
814 /* disable receive request */
815 switch (cv->PortType) {
816 case IdTCPIP:
817 if (! TCPIPClosed) {
818 PWSAAsyncSelect(cv->s,cv->HWin,WM_USER_COMMNOTIFY, FD_OOB | FD_CLOSE);
819 }
820 break;
821 case IdSerial:
822 break;
823 }
824 cv->RRQ = TRUE;
825 CommReceive(cv);
826 }
827
828 void CommReceive(PComVar cv)
829 {
830 DWORD C;
831 DWORD DErr;
832
833 if (! cv->Ready || ! cv->RRQ ||
834 (cv->InBuffCount>=InBuffSize)) {
835 return;
836 }
837
838 /* Compact buffer */
839 if ((cv->InBuffCount>0) && (cv->InPtr>0)) {
840 memmove(cv->InBuff,&(cv->InBuff[cv->InPtr]),cv->InBuffCount);
841 cv->InPtr = 0;
842 }
843
844 if (cv->InBuffCount<InBuffSize) {
845 switch (cv->PortType) {
846 case IdTCPIP:
847 C = Precv(cv->s, &(cv->InBuff[cv->InBuffCount]),
848 InBuffSize-cv->InBuffCount, 0);
849 if (C == SOCKET_ERROR) {
850 C = 0;
851 PWSAGetLastError();
852 }
853 cv->InBuffCount = cv->InBuffCount + C;
854 break;
855 case IdSerial:
856 do {
857 ClearCommError(cv->ComID,&DErr,NULL);
858 if (! PReadFile(cv->ComID,&(cv->InBuff[cv->InBuffCount]),
859 InBuffSize-cv->InBuffCount,&C,&rol)) {
860 if (GetLastError() == ERROR_IO_PENDING) {
861 if (WaitForSingleObject(rol.hEvent, 1000) != WAIT_OBJECT_0) {
862 C = 0;
863 }
864 else {
865 GetOverlappedResult(cv->ComID,&rol,&C,FALSE);
866 }
867 }
868 else {
869 C = 0;
870 }
871 }
872 cv->InBuffCount = cv->InBuffCount + C;
873 } while ((C!=0) && (cv->InBuffCount<InBuffSize));
874 ClearCommError(cv->ComID,&DErr,NULL);
875 break;
876 case IdFile:
877 if (PReadFile(cv->ComID,&(cv->InBuff[cv->InBuffCount]),
878 InBuffSize-cv->InBuffCount,&C,NULL)) {
879 if (C == 0) {
880 DErr = ERROR_HANDLE_EOF;
881 }
882 else {
883 cv->InBuffCount = cv->InBuffCount + C;
884 }
885 }
886 else {
887 DErr = GetLastError();
888 }
889 break;
890
891 case IdNamedPipe:
892 // �L���[����������1�o�C�g�������f�[�^�������������������m�F���������������A
893 // ReadFile() ���u���b�N�������������������A�������������B
894 if (PReadFile(cv->ComID,&(cv->InBuff[cv->InBuffCount]),
895 InBuffSize-cv->InBuffCount,&C,NULL)) {
896 if (C == 0) {
897 DErr = ERROR_HANDLE_EOF;
898 }
899 else {
900 cv->InBuffCount = cv->InBuffCount + C;
901 }
902 }
903 else {
904 DErr = GetLastError();
905 }
906
907 // 1�o�C�g�������������A�C�x���g���N�����A�X���b�h�����J�������B
908 if (cv->InBuffCount > 0) {
909 cv->RRQ = FALSE;
910 SetEvent(ReadEnd);
911 }
912 break;
913 }
914 }
915
916 if (cv->InBuffCount==0) {
917 switch (cv->PortType) {
918 case IdTCPIP:
919 if (! TCPIPClosed) {
920 PWSAAsyncSelect(cv->s,cv->HWin, WM_USER_COMMNOTIFY,
921 FD_READ | FD_OOB | FD_CLOSE);
922 }
923 break;
924 case IdSerial:
925 cv->RRQ = FALSE;
926 SetEvent(ReadEnd);
927 return;
928 case IdFile:
929 if (DErr != ERROR_IO_PENDING) {
930 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
931 cv->RRQ = FALSE;
932 }
933 else {
934 cv->RRQ = TRUE;
935 }
936 return;
937 case IdNamedPipe:
938 // TODO: �������A���������������������B
939 if (DErr != ERROR_IO_PENDING) {
940 PostMessage(cv->HWin, WM_USER_COMMNOTIFY, 0, FD_CLOSE);
941 cv->RRQ = FALSE;
942 }
943 else {
944 cv->RRQ = TRUE;
945 }
946 SetEvent(ReadEnd);
947 return;
948 }
949 cv->RRQ = FALSE;
950 }
951 }
952
953 void CommSend(PComVar cv)
954 {
955 int delay;
956 COMSTAT Stat;
957 BYTE LineEnd;
958 int C, D, Max;
959 DWORD DErr;
960
961 if ((! cv->Open) || (! cv->Ready)) {
962 cv->OutBuffCount = 0;
963 return;
964 }
965
966 if ((cv->OutBuffCount == 0) || (! cv->CanSend)) {
967 return;
968 }
969
970 /* Max num of bytes to be written */
971 switch (cv->PortType) {
972 case IdTCPIP:
973 if (TCPIPClosed) {
974 cv->OutBuffCount = 0;
975 }
976 Max = cv->OutBuffCount;
977 break;
978 case IdSerial:
979 ClearCommError(cv->ComID,&DErr,&Stat);
980 Max = OutBuffSize - Stat.cbOutQue;
981 break;
982 case IdFile:
983 Max = cv->OutBuffCount;
984 break;
985 case IdNamedPipe:
986 Max = cv->OutBuffCount;
987 break;
988 }
989
990 if ( Max<=0 ) {
991 return;
992 }
993 if ( Max > cv->OutBuffCount ) {
994 Max = cv->OutBuffCount;
995 }
996
997 if (cv->PortType == IdTCPIP && cv->TelFlag) {
998 cv->LastSendTime = time(NULL);
999 }
1000
1001 C = Max;
1002 delay = 0;
1003
1004 if ( cv->DelayFlag && (cv->PortType==IdSerial) ) {
1005 if ( cv->DelayPerLine > 0 ) {
1006 if ( cv->CRSend==IdCR ) {
1007 LineEnd = 0x0d;
1008 }
1009 else { // CRLF or LF
1010 LineEnd = 0x0a;
1011 }
1012 C = 1;
1013 if ( cv->DelayPerChar==0 ) {
1014 while ((C<Max) && (cv->OutBuff[cv->OutPtr+C-1]!=LineEnd)) {
1015 C++;
1016 }
1017 }
1018 if ( cv->OutBuff[cv->OutPtr+C-1]==LineEnd ) {
1019 delay = cv->DelayPerLine;
1020 }
1021 else {
1022 delay = cv->DelayPerChar;
1023 }
1024 }
1025 else if ( cv->DelayPerChar > 0 ) {
1026 C = 1;
1027 delay = cv->DelayPerChar;
1028 }
1029 }
1030
1031 /* Write to comm driver/Winsock */
1032 switch (cv->PortType) {
1033 case IdTCPIP:
1034 D = Psend(cv->s, &(cv->OutBuff[cv->OutPtr]), C, 0);
1035 if ( D==SOCKET_ERROR ) { /* if error occurs */
1036 PWSAGetLastError(); /* Clear error */
1037 D = 0;
1038 }
1039 break;
1040
1041 case IdSerial:
1042 if (! PWriteFile(cv->ComID,&(cv->OutBuff[cv->OutPtr]),C,(LPDWORD)&D,&wol)) {
1043 if (GetLastError() == ERROR_IO_PENDING) {
1044 if (WaitForSingleObject(wol.hEvent,1000) != WAIT_OBJECT_0) {
1045 D = C; /* Time out, ignore data */
1046 }
1047 else {
1048 GetOverlappedResult(cv->ComID,&wol,(LPDWORD)&D,FALSE);
1049 }
1050 }
1051 else { /* I/O error */
1052 D = C; /* ignore error */
1053 }
1054 }
1055 ClearCommError(cv->ComID,&DErr,&Stat);
1056 break;
1057
1058 case IdFile:
1059 if (! PWriteFile(cv->ComID, &(cv->OutBuff[cv->OutPtr]), C, (LPDWORD)&D, NULL)) {
1060 if (! GetLastError() == ERROR_IO_PENDING) {
1061 D = C; /* ignore data */
1062 }
1063 }
1064 break;
1065
1066 case IdNamedPipe:
1067 if (! PWriteFile(cv->ComID, &(cv->OutBuff[cv->OutPtr]), C, (LPDWORD)&D, NULL)) {
1068 // ERROR_IO_PENDING ���O���G���[���������A�p�C�v���N���[�Y�������������������������A
1069 // ���M�����������������B
1070 if (! (GetLastError() == ERROR_IO_PENDING)) {
1071 D = C; /* ignore data */
1072 }
1073 }
1074 break;
1075 }
1076
1077 cv->OutBuffCount = cv->OutBuffCount - D;
1078 if ( cv->OutBuffCount==0 ) {
1079 cv->OutPtr = 0;
1080 }
1081 else {
1082 cv->OutPtr = cv->OutPtr + D;
1083 }
1084
1085 if ( (C==D) && (delay>0) ) {
1086 cv->CanSend = FALSE;
1087 SetTimer(cv->HWin, IdDelayTimer, delay, NULL);
1088 }
1089 }
1090
1091 void CommSendBreak(PComVar cv, int msec)
1092 /* for only serial ports */
1093 {
1094 MSG DummyMsg;
1095
1096 if ( ! cv->Ready ) {
1097 return;
1098 }
1099
1100 switch (cv->PortType) {
1101 case IdSerial:
1102 /* Set com port into a break state */
1103 SetCommBreak(cv->ComID);
1104
1105 /* pause for 1 sec */
1106 if (SetTimer(cv->HWin, IdBreakTimer, msec, NULL) != 0) {
1107 GetMessage(&DummyMsg,cv->HWin,WM_TIMER,WM_TIMER);
1108 }
1109
1110 /* Set com port into the nonbreak state */
1111 ClearCommBreak(cv->ComID);
1112 break;
1113 }
1114 }
1115
1116 void CommLock(PTTSet ts, PComVar cv, BOOL Lock)
1117 {
1118 BYTE b;
1119 DWORD Func;
1120
1121 if (! cv->Ready) {
1122 return;
1123 }
1124 if ((cv->PortType==IdTCPIP) ||
1125 (cv->PortType==IdSerial) &&
1126 (ts->Flow!=IdFlowHard)) {
1127 if (Lock) {
1128 b = XOFF;
1129 }
1130 else {
1131 b = XON;
1132 }
1133 CommBinaryOut(cv,&b,1);
1134 }
1135 else if ((cv->PortType==IdSerial) &&
1136 (ts->Flow==IdFlowHard)) {
1137 if (Lock) {
1138 Func = CLRRTS;
1139 }
1140 else {
1141 Func = SETRTS;
1142 }
1143 EscapeCommFunction(cv->ComID,Func);
1144 }
1145 }
1146
1147 BOOL PrnOpen(PCHAR DevName)
1148 {
1149 char Temp[MAXPATHLEN], *c;
1150 DCB dcb;
1151 DWORD DErr;
1152 COMMTIMEOUTS ctmo;
1153
1154 strncpy_s(Temp, sizeof(Temp),DevName, _TRUNCATE);
1155 c = Temp;
1156 while (*c != '\0' && *c != ':') {
1157 c++;
1158 }
1159 *c = '\0';
1160 LPTFlag = (Temp[0]=='L') ||
1161 (Temp[0]=='l');
1162
1163 if (IsWindowsNTKernel()) {
1164 // �l�b�g���[�N���L���}�b�v�������f�o�C�X�������������A�������������������������� (2011.01.25 maya)
1165 // http://logmett.com/forum/viewtopic.php?f=2&t=1383
1166 // http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx#5
1167 PrnID = CreateFile(Temp,GENERIC_WRITE | FILE_READ_ATTRIBUTES,
1168 FILE_SHARE_READ,NULL,CREATE_ALWAYS,
1169 0,NULL);
1170 }
1171 else {
1172 // 9x �������L���R�[�h���������������������]������������
1173 PrnID = CreateFile(Temp,GENERIC_WRITE,
1174 0,NULL,OPEN_EXISTING,
1175 0,NULL);
1176 }
1177
1178 if (PrnID == INVALID_HANDLE_VALUE) {
1179 return FALSE;
1180 }
1181
1182 if (GetCommState(PrnID,&dcb)) {
1183 BuildCommDCB(DevName,&dcb);
1184 SetCommState(PrnID,&dcb);
1185 }
1186 ClearCommError(PrnID,&DErr,NULL);
1187 if (! LPTFlag) {
1188 SetupComm(PrnID,0,CommOutQueSize);
1189 }
1190 /* flush output buffer */
1191 PurgeComm(PrnID, PURGE_TXABORT | PURGE_TXCLEAR);
1192 memset(&ctmo,0,sizeof(ctmo));
1193 ctmo.WriteTotalTimeoutConstant = 1000;
1194 SetCommTimeouts(PrnID,&ctmo);
1195 if (! LPTFlag) {
1196 EscapeCommFunction(PrnID,SETDTR);
1197 }
1198 return TRUE;
1199 }
1200
1201 int PrnWrite(PCHAR b, int c)
1202 {
1203 int d;
1204 DWORD DErr;
1205 COMSTAT Stat;
1206
1207 if (PrnID == INVALID_HANDLE_VALUE ) {
1208 return c;
1209 }
1210
1211 ClearCommError(PrnID,&DErr,&Stat);
1212 if (! LPTFlag &&
1213 (OutBuffSize - (int)Stat.cbOutQue < c)) {
1214 c = OutBuffSize - Stat.cbOutQue;
1215 }
1216 if (c<=0) {
1217 return 0;
1218 }
1219 if (! WriteFile(PrnID,b,c,(LPDWORD)&d,NULL)) {
1220 d = 0;
1221 }
1222 ClearCommError(PrnID,&DErr,NULL);
1223 return d;
1224 }
1225
1226 void PrnCancel()
1227 {
1228 PurgeComm(PrnID, PURGE_TXABORT | PURGE_TXCLEAR);
1229 PrnClose();
1230 }
1231
1232 void PrnClose()
1233 {
1234 if (PrnID != INVALID_HANDLE_VALUE) {
1235 if (!LPTFlag) {
1236 EscapeCommFunction(PrnID,CLRDTR);
1237 }
1238 CloseHandle(PrnID);
1239 }
1240 PrnID = INVALID_HANDLE_VALUE;
1241 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26