|
1
|
/* |
|
2
|
** Copyright (c) 2008 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
|
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file implements a very simple (and low-performance) HTTP server |
|
19
|
** for windows. It also implements a Windows Service which allows the HTTP |
|
20
|
** server to be run without any user logged on. |
|
21
|
*/ |
|
22
|
#include "config.h" |
|
23
|
#ifdef _WIN32 |
|
24
|
/* This code is for win32 only */ |
|
25
|
# if !defined(_WIN32_WINNT) |
|
26
|
# define _WIN32_WINNT 0x0501 |
|
27
|
# endif |
|
28
|
#include <winsock2.h> |
|
29
|
#include <ws2tcpip.h> |
|
30
|
#include <windows.h> |
|
31
|
#include <process.h> |
|
32
|
#include "winhttp.h" |
|
33
|
|
|
34
|
#ifndef IPV6_V6ONLY |
|
35
|
# define IPV6_V6ONLY 27 /* Because this definition is missing in MinGW */ |
|
36
|
#endif |
|
37
|
|
|
38
|
/* |
|
39
|
** The SocketAddr structure holds a SOCKADDR_STORAGE and its content size. |
|
40
|
*/ |
|
41
|
typedef struct SocketAddr SocketAddr; |
|
42
|
struct SocketAddr { |
|
43
|
SOCKADDR_STORAGE addr; |
|
44
|
int len; |
|
45
|
}; |
|
46
|
|
|
47
|
static char* SocketAddr_toString(const SocketAddr* pAddr){ |
|
48
|
SocketAddr addr; |
|
49
|
char* zIp; |
|
50
|
DWORD nIp = 50; |
|
51
|
assert( pAddr!=NULL ); |
|
52
|
memcpy(&addr, pAddr, sizeof(SocketAddr)); |
|
53
|
if( addr.len==sizeof(SOCKADDR_IN6) ){ |
|
54
|
((SOCKADDR_IN6*)&addr)->sin6_port = 0; |
|
55
|
}else{ |
|
56
|
((SOCKADDR_IN*)&addr)->sin_port = 0; |
|
57
|
} |
|
58
|
zIp = fossil_malloc(nIp); |
|
59
|
if( WSAAddressToStringA((SOCKADDR*)&addr, addr.len, NULL, zIp, &nIp)!=0 ){ |
|
60
|
zIp[0] = 0; |
|
61
|
} |
|
62
|
return zIp; |
|
63
|
} |
|
64
|
|
|
65
|
/* |
|
66
|
** The DualAddr structure holds two SocketAddr (one IPv4 and on IPv6). |
|
67
|
*/ |
|
68
|
typedef struct DualAddr DualAddr; |
|
69
|
struct DualAddr { |
|
70
|
SocketAddr a4; /* IPv4 SOCKADDR_IN */ |
|
71
|
SocketAddr a6; /* IPv6 SOCKADDR_IN6 */ |
|
72
|
}; |
|
73
|
|
|
74
|
static void DualAddr_init(DualAddr* pDA){ |
|
75
|
assert( pDA!=NULL ); |
|
76
|
memset(pDA, 0, sizeof(DualAddr)); |
|
77
|
pDA->a4.len = sizeof(SOCKADDR_IN); |
|
78
|
pDA->a6.len = sizeof(SOCKADDR_IN6); |
|
79
|
} |
|
80
|
|
|
81
|
/* |
|
82
|
** The DualSocket structure holds two SOCKETs. One or both could be |
|
83
|
** used or INVALID_SOCKET. One is dedicated to IPv4, the other to IPv6. |
|
84
|
*/ |
|
85
|
typedef struct DualSocket DualSocket; |
|
86
|
struct DualSocket { |
|
87
|
SOCKET s4; /* IPv4 socket or INVALID_SOCKET */ |
|
88
|
SOCKET s6; /* IPv6 socket or INVALID_SOCKET */ |
|
89
|
}; |
|
90
|
|
|
91
|
/* |
|
92
|
** Initializes a DualSocket. |
|
93
|
*/ |
|
94
|
static void DualSocket_init(DualSocket* ds){ |
|
95
|
assert( ds!=NULL ); |
|
96
|
ds->s4 = INVALID_SOCKET; |
|
97
|
ds->s6 = INVALID_SOCKET; |
|
98
|
}; |
|
99
|
|
|
100
|
/* |
|
101
|
** Close and reset a DualSocket. |
|
102
|
*/ |
|
103
|
static void DualSocket_close(DualSocket* ds){ |
|
104
|
assert( ds!=NULL ); |
|
105
|
if( ds->s4!=INVALID_SOCKET ){ |
|
106
|
closesocket(ds->s4); |
|
107
|
ds->s4 = INVALID_SOCKET; |
|
108
|
} |
|
109
|
if( ds->s6!=INVALID_SOCKET ){ |
|
110
|
closesocket(ds->s6); |
|
111
|
ds->s6 = INVALID_SOCKET; |
|
112
|
} |
|
113
|
}; |
|
114
|
|
|
115
|
/* |
|
116
|
** When ip is "W", listen to wildcard address (IPv4/IPv6 as available). |
|
117
|
** When ip is "L", listen to loopback address (IPv4/IPv6 as available). |
|
118
|
** Else listen only the specified ip, which is either IPv4 or IPv6 or invalid. |
|
119
|
** Returns 1 on success, 0 on failure. |
|
120
|
*/ |
|
121
|
static int DualSocket_listen(DualSocket* ds, const char* zIp, int iPort){ |
|
122
|
SOCKADDR_IN addr4; |
|
123
|
SOCKADDR_IN6 addr6; |
|
124
|
assert( ds!=NULL && zIp!=NULL && iPort!=0 ); |
|
125
|
DualSocket_close(ds); |
|
126
|
memset(&addr4, 0, sizeof(addr4)); |
|
127
|
memset(&addr6, 0, sizeof(addr6)); |
|
128
|
if (strcmp(zIp, "W")==0 || strcmp(zIp, "L")==0 ){ |
|
129
|
ds->s4 = socket(AF_INET, SOCK_STREAM, 0); |
|
130
|
ds->s6 = socket(AF_INET6, SOCK_STREAM, 0); |
|
131
|
if( ds->s4==INVALID_SOCKET && ds->s6==INVALID_SOCKET ){ |
|
132
|
return 0; |
|
133
|
} |
|
134
|
if (ds->s4!=INVALID_SOCKET ) { |
|
135
|
addr4.sin_family = AF_INET; |
|
136
|
addr4.sin_port = htons(iPort); |
|
137
|
if( strcmp(zIp, "L")==0 ){ |
|
138
|
addr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
|
139
|
}else{ |
|
140
|
addr4.sin_addr.s_addr = INADDR_ANY; |
|
141
|
} |
|
142
|
} |
|
143
|
if( ds->s6!=INVALID_SOCKET ) { |
|
144
|
DWORD ipv6only = 1; /* don't want a dual-stack socket */ |
|
145
|
setsockopt(ds->s6, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, |
|
146
|
sizeof(ipv6only)); |
|
147
|
addr6.sin6_family = AF_INET6; |
|
148
|
addr6.sin6_port = htons(iPort); |
|
149
|
if( strcmp(zIp, "L")==0 ){ |
|
150
|
memcpy(&addr6.sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback)); |
|
151
|
}else{ |
|
152
|
memcpy(&addr6.sin6_addr, &in6addr_any, sizeof(in6addr_any)); |
|
153
|
} |
|
154
|
} |
|
155
|
}else{ |
|
156
|
if( strstr(zIp, ".") ){ |
|
157
|
int addrlen = sizeof(addr4); |
|
158
|
ds->s4 = socket(AF_INET, SOCK_STREAM, 0); |
|
159
|
if( ds->s4==INVALID_SOCKET ){ |
|
160
|
return 0; |
|
161
|
} |
|
162
|
addr4.sin_family = AF_INET; |
|
163
|
if (WSAStringToAddress((char*)zIp, AF_INET, NULL, |
|
164
|
(struct sockaddr *)&addr4, &addrlen) != 0){ |
|
165
|
return 0; |
|
166
|
} |
|
167
|
addr4.sin_port = htons(iPort); |
|
168
|
}else{ |
|
169
|
DWORD ipv6only = 1; /* don't want a dual-stack socket */ |
|
170
|
int addrlen = sizeof(addr6); |
|
171
|
ds->s6 = socket(AF_INET6, SOCK_STREAM, 0); |
|
172
|
if( ds->s6==INVALID_SOCKET ){ |
|
173
|
return 0; |
|
174
|
} |
|
175
|
setsockopt(ds->s6, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, |
|
176
|
sizeof(ipv6only)); |
|
177
|
addr6.sin6_family = AF_INET6; |
|
178
|
if (WSAStringToAddress((char*)zIp, AF_INET6, NULL, |
|
179
|
(struct sockaddr *)&addr6, &addrlen) != 0){ |
|
180
|
return 0; |
|
181
|
} |
|
182
|
addr6.sin6_port = htons(iPort); |
|
183
|
} |
|
184
|
} |
|
185
|
assert( ds->s4!=INVALID_SOCKET || ds->s6!=INVALID_SOCKET ); |
|
186
|
if( ds->s4!=INVALID_SOCKET && bind(ds->s4, (struct sockaddr*)&addr4, |
|
187
|
sizeof(addr4))==SOCKET_ERROR ){ |
|
188
|
return 0; |
|
189
|
} |
|
190
|
if( ds->s6!=INVALID_SOCKET && bind(ds->s6, (struct sockaddr*)&addr6, |
|
191
|
sizeof(addr6))==SOCKET_ERROR ){ |
|
192
|
return 0; |
|
193
|
} |
|
194
|
if( ds->s4!=INVALID_SOCKET && listen(ds->s4, SOMAXCONN)==SOCKET_ERROR ){ |
|
195
|
return 0; |
|
196
|
} |
|
197
|
if( ds->s6!=INVALID_SOCKET && listen(ds->s6, SOMAXCONN)==SOCKET_ERROR ){ |
|
198
|
return 0; |
|
199
|
} |
|
200
|
return 1; |
|
201
|
}; |
|
202
|
|
|
203
|
/* |
|
204
|
** Accepts connections on DualSocket. |
|
205
|
*/ |
|
206
|
static void DualSocket_accept(DualSocket* pListen, DualSocket* pClient, |
|
207
|
DualAddr* pClientAddr){ |
|
208
|
fd_set rs; |
|
209
|
int rs_count = 0; |
|
210
|
assert( pListen!=NULL && pClient!=NULL && pClientAddr!= NULL ); |
|
211
|
DualSocket_init(pClient); |
|
212
|
DualAddr_init(pClientAddr); |
|
213
|
FD_ZERO(&rs); |
|
214
|
if( pListen->s4!=INVALID_SOCKET ){ |
|
215
|
FD_SET(pListen->s4, &rs); |
|
216
|
++rs_count; |
|
217
|
} |
|
218
|
if( pListen->s6!=INVALID_SOCKET ){ |
|
219
|
FD_SET(pListen->s6, &rs); |
|
220
|
++rs_count; |
|
221
|
} |
|
222
|
if( select(rs_count, &rs, 0, 0, 0 /*blocking*/)==SOCKET_ERROR ){ |
|
223
|
return; |
|
224
|
} |
|
225
|
if( FD_ISSET(pListen->s4, &rs) ){ |
|
226
|
pClient->s4 = accept(pListen->s4, (struct sockaddr*)&pClientAddr->a4.addr, |
|
227
|
&pClientAddr->a4.len); |
|
228
|
} |
|
229
|
if( FD_ISSET(pListen->s6, &rs) ){ |
|
230
|
pClient->s6 = accept(pListen->s6, (struct sockaddr*)&pClientAddr->a6.addr, |
|
231
|
&pClientAddr->a6.len); |
|
232
|
} |
|
233
|
} |
|
234
|
|
|
235
|
/* |
|
236
|
** The HttpServer structure holds information about an instance of |
|
237
|
** the HTTP server itself. |
|
238
|
*/ |
|
239
|
typedef struct HttpServer HttpServer; |
|
240
|
struct HttpServer { |
|
241
|
HANDLE hStoppedEvent; /* Event to signal when server is stopped, |
|
242
|
** must be closed by callee. */ |
|
243
|
char *zStopper; /* The stopper file name, must be freed by |
|
244
|
** callee. */ |
|
245
|
DualSocket listener; /* Sockets on which the server is listening, |
|
246
|
** may be closed by callee. */ |
|
247
|
}; |
|
248
|
|
|
249
|
/* |
|
250
|
** The HttpRequest structure holds information about each incoming |
|
251
|
** HTTP request. |
|
252
|
*/ |
|
253
|
typedef struct HttpRequest HttpRequest; |
|
254
|
struct HttpRequest { |
|
255
|
int id; /* ID counter */ |
|
256
|
SOCKET s; /* Socket on which to receive data */ |
|
257
|
SocketAddr addr; /* Address from which data is coming */ |
|
258
|
int flags; /* Flags passed to win32_http_server() */ |
|
259
|
const char *zOptions; /* --baseurl, --notfound, --localauth, --th-trace */ |
|
260
|
}; |
|
261
|
|
|
262
|
/* |
|
263
|
** Prefix for a temporary file. |
|
264
|
*/ |
|
265
|
static char *zTempPrefix; |
|
266
|
|
|
267
|
/* |
|
268
|
** Look at the HTTP header contained in zHdr. Find the content |
|
269
|
** length and return it. Return 0 if there is no Content-Length: |
|
270
|
** header line. |
|
271
|
*/ |
|
272
|
static int find_content_length(const char *zHdr){ |
|
273
|
while( *zHdr ){ |
|
274
|
if( zHdr[0]=='\n' ){ |
|
275
|
if( zHdr[1]=='\r' ) return 0; |
|
276
|
if( fossil_strnicmp(&zHdr[1], "content-length:", 15)==0 ){ |
|
277
|
return atoi(&zHdr[17]); |
|
278
|
} |
|
279
|
} |
|
280
|
zHdr++; |
|
281
|
} |
|
282
|
return 0; |
|
283
|
} |
|
284
|
|
|
285
|
/* |
|
286
|
** Issue a fatal error. |
|
287
|
*/ |
|
288
|
static NORETURN void winhttp_fatal( |
|
289
|
const char *zOp, |
|
290
|
const char *zService, |
|
291
|
const char *zErr |
|
292
|
){ |
|
293
|
fossil_fatal("unable to %s service '%s': %s", zOp, zService, zErr); |
|
294
|
} |
|
295
|
|
|
296
|
/* |
|
297
|
** Make sure the server stops as soon as possible after the stopper file |
|
298
|
** is found. If there is no stopper file name, do nothing. |
|
299
|
*/ |
|
300
|
static void win32_server_stopper(void *pAppData){ |
|
301
|
HttpServer *p = (HttpServer*)pAppData; |
|
302
|
if( p!=0 ){ |
|
303
|
HANDLE hStoppedEvent = p->hStoppedEvent; |
|
304
|
const char *zStopper = p->zStopper; |
|
305
|
if( hStoppedEvent!=NULL && zStopper!=0 ){ |
|
306
|
while( 1 ){ |
|
307
|
DWORD dwResult = WaitForMultipleObjectsEx(1, &hStoppedEvent, FALSE, |
|
308
|
1000, TRUE); |
|
309
|
if( dwResult!=WAIT_IO_COMPLETION && dwResult!=WAIT_TIMEOUT ){ |
|
310
|
/* The event is either invalid, signaled, or abandoned. Bail |
|
311
|
** out now because those conditions should indicate the parent |
|
312
|
** thread is dead or dying. */ |
|
313
|
break; |
|
314
|
} |
|
315
|
if( file_size(zStopper, ExtFILE)>=0 ){ |
|
316
|
/* The stopper file has been found. Attempt to close the server |
|
317
|
** listener socket now and then exit. */ |
|
318
|
DualSocket_close(&p->listener); |
|
319
|
break; |
|
320
|
} |
|
321
|
} |
|
322
|
} |
|
323
|
if( hStoppedEvent!=NULL ){ |
|
324
|
CloseHandle(hStoppedEvent); |
|
325
|
p->hStoppedEvent = NULL; |
|
326
|
} |
|
327
|
if( zStopper!=0 ){ |
|
328
|
fossil_free(p->zStopper); |
|
329
|
p->zStopper = 0; |
|
330
|
} |
|
331
|
fossil_free(p); |
|
332
|
} |
|
333
|
} |
|
334
|
|
|
335
|
/* |
|
336
|
** Process a single incoming HTTP request. |
|
337
|
*/ |
|
338
|
static void win32_http_request(void *pAppData){ |
|
339
|
HttpRequest *p = (HttpRequest*)pAppData; |
|
340
|
FILE *in = 0, *out = 0, *aux = 0; |
|
341
|
int amt, got, i; |
|
342
|
int wanted = 0; |
|
343
|
char *z; |
|
344
|
char *zIp; |
|
345
|
void *sslConn = 0; |
|
346
|
char zCmdFName[MAX_PATH]; |
|
347
|
char zRequestFName[MAX_PATH]; |
|
348
|
char zReplyFName[MAX_PATH]; |
|
349
|
char zCmd[2000]; /* Command-line to process the request */ |
|
350
|
char zBuf[65536]; /* The HTTP request header */ |
|
351
|
const int szHdr = 4000; /* Reduced header size */ |
|
352
|
|
|
353
|
sqlite3_snprintf(MAX_PATH, zCmdFName, |
|
354
|
"%s_%06d_cmd.txt", zTempPrefix, p->id); |
|
355
|
sqlite3_snprintf(MAX_PATH, zRequestFName, |
|
356
|
"%s_%06d_in.txt", zTempPrefix, p->id); |
|
357
|
sqlite3_snprintf(MAX_PATH, zReplyFName, |
|
358
|
"%s_%06d_out.txt", zTempPrefix, p->id); |
|
359
|
amt = 0; |
|
360
|
if( g.httpUseSSL ){ |
|
361
|
#ifdef FOSSIL_ENABLE_SSL |
|
362
|
sslConn = ssl_new_server(p->s); |
|
363
|
#endif |
|
364
|
} |
|
365
|
while( amt<szHdr ){ |
|
366
|
if( sslConn ){ |
|
367
|
#ifdef FOSSIL_ENABLE_SSL |
|
368
|
got = ssl_read_server(sslConn, &zBuf[amt], szHdr-1-amt, 0); |
|
369
|
#endif |
|
370
|
}else{ |
|
371
|
got = recv(p->s, &zBuf[amt], szHdr-1-amt, 0); |
|
372
|
if( got==SOCKET_ERROR ) goto end_request; |
|
373
|
} |
|
374
|
if( got==0 ){ |
|
375
|
wanted = 0; |
|
376
|
break; |
|
377
|
} |
|
378
|
amt += got; |
|
379
|
zBuf[amt] = 0; |
|
380
|
z = strstr(zBuf, "\r\n\r\n"); |
|
381
|
if( z ){ |
|
382
|
wanted = find_content_length(zBuf) + (&z[4]-zBuf) - amt; |
|
383
|
break; |
|
384
|
}else{ |
|
385
|
z = strstr(zBuf, "\n\n"); |
|
386
|
if( z ){ |
|
387
|
wanted = find_content_length(zBuf) + (&z[2]-zBuf) - amt; |
|
388
|
break; |
|
389
|
} |
|
390
|
} |
|
391
|
} |
|
392
|
if( amt>=szHdr ) goto end_request; |
|
393
|
out = fossil_fopen(zRequestFName, "wb"); |
|
394
|
if( out==0 ) goto end_request; |
|
395
|
fwrite(zBuf, 1, amt, out); |
|
396
|
while( wanted>0 ){ |
|
397
|
if( sslConn ){ |
|
398
|
#ifdef FOSSIL_ENABLE_SSL |
|
399
|
got = ssl_read_server(sslConn, zBuf, min(wanted, sizeof(zBuf)), 1); |
|
400
|
#endif |
|
401
|
}else{ |
|
402
|
got = recv(p->s, zBuf, sizeof(zBuf), 0); |
|
403
|
if( got==SOCKET_ERROR ) goto end_request; |
|
404
|
} |
|
405
|
if( got>0 ){ |
|
406
|
fwrite(zBuf, 1, got, out); |
|
407
|
}else{ |
|
408
|
break; |
|
409
|
} |
|
410
|
wanted -= got; |
|
411
|
} |
|
412
|
|
|
413
|
/* |
|
414
|
** The repository name is only needed if there was no open check-out. This |
|
415
|
** is designed to allow the open check-out for the interactive user to work |
|
416
|
** with the local Fossil server started via the "ui" command. |
|
417
|
*/ |
|
418
|
aux = fossil_fopen(zCmdFName, "wb"); |
|
419
|
if( aux==0 ) goto end_request; |
|
420
|
fprintf(aux, "%s--in %s\n", get_utf8_bom(0), zRequestFName); |
|
421
|
zIp = SocketAddr_toString(&p->addr); |
|
422
|
fprintf(aux, "--out %s\n--ipaddr %s\n", zReplyFName, zIp); |
|
423
|
fossil_free(zIp); |
|
424
|
fprintf(aux, "--as %s\n", g.zCmdName); |
|
425
|
if( g.zErrlog && g.zErrlog[0] ){ |
|
426
|
fprintf(aux,"--errorlog %s\n", g.zErrlog); |
|
427
|
} |
|
428
|
if( (p->flags & HTTP_SERVER_HAD_CHECKOUT)==0 ){ |
|
429
|
fprintf(aux,"%s",g.zRepositoryName); |
|
430
|
} |
|
431
|
|
|
432
|
sqlite3_snprintf(sizeof(zCmd), zCmd, |
|
433
|
"\"%s\" http -args \"%s\"%s%s", |
|
434
|
g.nameOfExe, zCmdFName, |
|
435
|
g.httpUseSSL ? "" : " --nossl", p->zOptions |
|
436
|
); |
|
437
|
in = fossil_fopen(zReplyFName, "w+b"); |
|
438
|
fflush(out); |
|
439
|
fflush(aux); |
|
440
|
if( g.fHttpTrace ){ |
|
441
|
fossil_print("%s\n", zCmd); |
|
442
|
} |
|
443
|
fossil_system(zCmd); |
|
444
|
if( in ){ |
|
445
|
while( (got = fread(zBuf, 1, sizeof(zBuf), in))>0 ){ |
|
446
|
if( sslConn ){ |
|
447
|
#ifdef FOSSIL_ENABLE_SSL |
|
448
|
ssl_write_server(sslConn, zBuf, got); |
|
449
|
#endif |
|
450
|
}else{ |
|
451
|
send(p->s, zBuf, got, 0); |
|
452
|
} |
|
453
|
} |
|
454
|
} |
|
455
|
|
|
456
|
end_request: |
|
457
|
if( out ) fclose(out); |
|
458
|
if( aux ) fclose(aux); |
|
459
|
if( in ) fclose(in); |
|
460
|
/* Initiate shutdown prior to closing the socket */ |
|
461
|
if( sslConn!=0 ){ |
|
462
|
#ifdef FOSSIL_ENABLE_SSL |
|
463
|
ssl_close_server(sslConn); |
|
464
|
#endif |
|
465
|
} |
|
466
|
if( shutdown(p->s,1)==0 ) shutdown(p->s,0); |
|
467
|
closesocket(p->s); |
|
468
|
/* Make multiple attempts to delete the temporary files. Sometimes AV |
|
469
|
** software keeps the files open for a few seconds, preventing the file |
|
470
|
** from being deleted on the first try. */ |
|
471
|
if( !g.fHttpTrace ){ |
|
472
|
for(i=1; i<=10 && file_delete(zRequestFName); i++){ Sleep(1000*i); } |
|
473
|
for(i=1; i<=10 && file_delete(zCmdFName); i++){ Sleep(1000*i); } |
|
474
|
for(i=1; i<=10 && file_delete(zReplyFName); i++){ Sleep(1000*i); } |
|
475
|
} |
|
476
|
fossil_free(p); |
|
477
|
} |
|
478
|
|
|
479
|
/* |
|
480
|
** Process a single incoming SCGI request. |
|
481
|
*/ |
|
482
|
static void win32_scgi_request(void *pAppData){ |
|
483
|
HttpRequest *p = (HttpRequest*)pAppData; |
|
484
|
FILE *in = 0, *out = 0; |
|
485
|
int amt, got, nHdr, i; |
|
486
|
int wanted = 0; |
|
487
|
char *zIp; |
|
488
|
char zRequestFName[MAX_PATH]; |
|
489
|
char zReplyFName[MAX_PATH]; |
|
490
|
char zCmd[2000]; /* Command-line to process the request */ |
|
491
|
char zHdr[4000]; /* The SCGI request header */ |
|
492
|
|
|
493
|
sqlite3_snprintf(MAX_PATH, zRequestFName, |
|
494
|
"%s_%06d_in.txt", zTempPrefix, p->id); |
|
495
|
sqlite3_snprintf(MAX_PATH, zReplyFName, |
|
496
|
"%s_%06d_out.txt", zTempPrefix, p->id); |
|
497
|
out = fossil_fopen(zRequestFName, "wb"); |
|
498
|
if( out==0 ) goto end_request; |
|
499
|
amt = 0; |
|
500
|
got = recv(p->s, zHdr, sizeof(zHdr), 0); |
|
501
|
if( got==SOCKET_ERROR ) goto end_request; |
|
502
|
amt = fwrite(zHdr, 1, got, out); |
|
503
|
nHdr = 0; |
|
504
|
for(i=0; zHdr[i]>='0' && zHdr[i]<='9'; i++){ |
|
505
|
nHdr = 10*nHdr + zHdr[i] - '0'; |
|
506
|
} |
|
507
|
wanted = nHdr + i + 1; |
|
508
|
if( strcmp(zHdr+i+1, "CONTENT_LENGTH")==0 ){ |
|
509
|
wanted += atoi(zHdr+i+15); |
|
510
|
} |
|
511
|
while( wanted>amt ){ |
|
512
|
got = recv(p->s, zHdr, wanted<sizeof(zHdr) ? wanted : sizeof(zHdr), 0); |
|
513
|
if( got<=0 ) break; |
|
514
|
fwrite(zHdr, 1, got, out); |
|
515
|
wanted += got; |
|
516
|
} |
|
517
|
assert( g.zRepositoryName && g.zRepositoryName[0] ); |
|
518
|
zIp = SocketAddr_toString(&p->addr); |
|
519
|
sqlite3_snprintf(sizeof(zCmd), zCmd, |
|
520
|
"\"%s\" http --in \"%s\" --out \"%s\" --ipaddr %s \"%s\"" |
|
521
|
" --scgi --nossl%s", |
|
522
|
g.nameOfExe, zRequestFName, zReplyFName, zIp, |
|
523
|
g.zRepositoryName, p->zOptions |
|
524
|
); |
|
525
|
fossil_free(zIp); |
|
526
|
in = fossil_fopen(zReplyFName, "w+b"); |
|
527
|
fflush(out); |
|
528
|
fossil_system(zCmd); |
|
529
|
if( in ){ |
|
530
|
while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){ |
|
531
|
send(p->s, zHdr, got, 0); |
|
532
|
} |
|
533
|
} |
|
534
|
|
|
535
|
end_request: |
|
536
|
if( out ) fclose(out); |
|
537
|
if( in ) fclose(in); |
|
538
|
/* Initiate shutdown prior to closing the socket */ |
|
539
|
if( shutdown(p->s,1)==0 ) shutdown(p->s,0); |
|
540
|
closesocket(p->s); |
|
541
|
/* Make multiple attempts to delete the temporary files. Sometimes AV |
|
542
|
** software keeps the files open for a few seconds, preventing the file |
|
543
|
** from being deleted on the first try. */ |
|
544
|
for(i=1; i<=10 && file_delete(zRequestFName); i++){ Sleep(1000*i); } |
|
545
|
for(i=1; i<=10 && file_delete(zReplyFName); i++){ Sleep(1000*i); } |
|
546
|
fossil_free(p); |
|
547
|
} |
|
548
|
|
|
549
|
/* forward reference */ |
|
550
|
static void win32_http_service_running(DualSocket* pS); |
|
551
|
|
|
552
|
/* |
|
553
|
** Start a listening socket and process incoming HTTP requests on |
|
554
|
** that socket. |
|
555
|
*/ |
|
556
|
void win32_http_server( |
|
557
|
int mnPort, int mxPort, /* Range of allowed TCP port numbers */ |
|
558
|
const char *zBrowser, /* Command to launch browser. (Or NULL) */ |
|
559
|
const char *zStopper, /* Stop server when this file is exists (Or NULL) */ |
|
560
|
const char *zBaseUrl, /* The --baseurl option, or NULL */ |
|
561
|
const char *zNotFound, /* The --notfound option, or NULL */ |
|
562
|
const char *zFileGlob, /* The --fileglob option, or NULL */ |
|
563
|
const char *zIpAddr, /* Bind to this IP address, if not NULL */ |
|
564
|
int flags /* One or more HTTP_SERVER_ flags */ |
|
565
|
){ |
|
566
|
HANDLE hStoppedEvent; |
|
567
|
WSADATA wd; |
|
568
|
DualSocket ds; |
|
569
|
int idCnt = 0; |
|
570
|
int iPort = mnPort; |
|
571
|
Blob options; |
|
572
|
wchar_t zTmpPath[MAX_PATH]; |
|
573
|
char *zTempSubDirPath; |
|
574
|
const char *zTempSubDir = "fossil"; |
|
575
|
const char *zSkin; |
|
576
|
#if USE_SEE |
|
577
|
const char *zSavedKey = 0; |
|
578
|
size_t savedKeySize = 0; |
|
579
|
#endif |
|
580
|
|
|
581
|
blob_zero(&options); |
|
582
|
if( PB("HTTPS") ){ |
|
583
|
blob_appendf(&options, " --https"); |
|
584
|
} |
|
585
|
if( zBaseUrl ){ |
|
586
|
blob_appendf(&options, " --baseurl "); |
|
587
|
blob_append_escaped_arg(&options, zBaseUrl, 0); |
|
588
|
} |
|
589
|
if( zNotFound ){ |
|
590
|
blob_appendf(&options, " --notfound "); |
|
591
|
blob_append_escaped_arg(&options, zNotFound, 1); |
|
592
|
} |
|
593
|
if( g.zCkoutAlias ){ |
|
594
|
blob_appendf(&options, " --ckout-alias "); |
|
595
|
blob_append_escaped_arg(&options, g.zCkoutAlias, 0); |
|
596
|
} |
|
597
|
if( zFileGlob ){ |
|
598
|
blob_appendf(&options, " --files-urlenc %T", zFileGlob); |
|
599
|
} |
|
600
|
if( g.useLocalauth ){ |
|
601
|
blob_appendf(&options, " --localauth"); |
|
602
|
} |
|
603
|
if( g.thTrace ){ |
|
604
|
blob_appendf(&options, " --th-trace"); |
|
605
|
} |
|
606
|
if( flags & HTTP_SERVER_REPOLIST ){ |
|
607
|
blob_appendf(&options, " --repolist"); |
|
608
|
} |
|
609
|
if( g.zExtRoot && g.zExtRoot[0] ){ |
|
610
|
blob_appendf(&options, " --extroot"); |
|
611
|
blob_append_escaped_arg(&options, g.zExtRoot, 1); |
|
612
|
} |
|
613
|
zSkin = skin_in_use(); |
|
614
|
if( zSkin ){ |
|
615
|
blob_appendf(&options, " --skin %s", zSkin); |
|
616
|
} |
|
617
|
if( g.zMainMenuFile ){ |
|
618
|
blob_appendf(&options, " --mainmenu "); |
|
619
|
blob_append_escaped_arg(&options, g.zMainMenuFile, 1); |
|
620
|
} |
|
621
|
if( builtin_get_js_delivery_mode()!=0 /* JS_INLINE==0 may change? */ ){ |
|
622
|
blob_appendf(&options, " --jsmode "); |
|
623
|
blob_append_escaped_arg(&options, builtin_get_js_delivery_mode_name(), 0); |
|
624
|
} |
|
625
|
#if USE_SEE |
|
626
|
zSavedKey = db_get_saved_encryption_key(); |
|
627
|
savedKeySize = db_get_saved_encryption_key_size(); |
|
628
|
if( db_is_valid_saved_encryption_key(zSavedKey, savedKeySize) ){ |
|
629
|
blob_appendf(&options, " --usepidkey %lu:%p:%u", GetCurrentProcessId(), |
|
630
|
zSavedKey, savedKeySize); |
|
631
|
} |
|
632
|
#endif |
|
633
|
if( WSAStartup(MAKEWORD(2,0), &wd) ){ |
|
634
|
fossil_panic("unable to initialize winsock"); |
|
635
|
} |
|
636
|
DualSocket_init(&ds); |
|
637
|
while( iPort<=mxPort ){ |
|
638
|
if( zIpAddr ){ |
|
639
|
if( DualSocket_listen(&ds, zIpAddr, iPort)==0 ){ |
|
640
|
iPort++; |
|
641
|
continue; |
|
642
|
} |
|
643
|
}else{ |
|
644
|
if( DualSocket_listen(&ds, |
|
645
|
(flags & HTTP_SERVER_LOCALHOST) ? "L" : "W", |
|
646
|
iPort |
|
647
|
)==0 ){ |
|
648
|
iPort++; |
|
649
|
continue; |
|
650
|
} |
|
651
|
} |
|
652
|
break; |
|
653
|
} |
|
654
|
if( iPort>mxPort ){ |
|
655
|
/* These exits are merely fatal because firewall settings can cause them. */ |
|
656
|
if( mnPort==mxPort ){ |
|
657
|
fossil_fatal("unable to open listening socket on port %d", mnPort); |
|
658
|
}else{ |
|
659
|
fossil_fatal("unable to open listening socket on any" |
|
660
|
" port in the range %d..%d", mnPort, mxPort); |
|
661
|
} |
|
662
|
} |
|
663
|
if( !GetTempPathW(MAX_PATH, zTmpPath) ){ |
|
664
|
fossil_panic("unable to get path to the temporary directory."); |
|
665
|
} |
|
666
|
/* Use a subdirectory for temp files (can then be excluded from virus scan) */ |
|
667
|
zTempSubDirPath = mprintf("%s%s\\",fossil_path_to_utf8(zTmpPath),zTempSubDir); |
|
668
|
if ( !file_mkdir(zTempSubDirPath, ExtFILE, 0) || |
|
669
|
file_isdir(zTempSubDirPath, ExtFILE)==1 ){ |
|
670
|
wcscpy(zTmpPath, fossil_utf8_to_path(zTempSubDirPath, 1)); |
|
671
|
} |
|
672
|
if( g.fHttpTrace ){ |
|
673
|
zTempPrefix = mprintf("httptrace"); |
|
674
|
}else{ |
|
675
|
zTempPrefix = mprintf("%sfossil_server_P%d", |
|
676
|
fossil_unicode_to_utf8(zTmpPath), iPort); |
|
677
|
} |
|
678
|
fossil_print("Temporary files: %s*\n", zTempPrefix); |
|
679
|
fossil_print("Listening for %s requests on TCP port %d\n", |
|
680
|
(flags&HTTP_SERVER_SCGI)!=0 ? "SCGI" : |
|
681
|
g.httpUseSSL ? "TLS-encrypted HTTPS" : "HTTP", iPort); |
|
682
|
if( zBrowser ){ |
|
683
|
zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); |
|
684
|
fossil_print("Launch webbrowser: %s\n", zBrowser); |
|
685
|
fossil_system(zBrowser); |
|
686
|
} |
|
687
|
fossil_print("Type Ctrl-C to stop the HTTP server\n"); |
|
688
|
/* Create an event used to signal when this server is exiting. */ |
|
689
|
hStoppedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
|
690
|
assert( hStoppedEvent!=NULL ); |
|
691
|
/* If there is a stopper file name, start the dedicated thread now. |
|
692
|
** It will attempt to close the listener socket within 1 second of |
|
693
|
** the stopper file being created. */ |
|
694
|
if( zStopper ){ |
|
695
|
HttpServer *pServer = fossil_malloc(sizeof(HttpServer)); |
|
696
|
memset(pServer, 0, sizeof(HttpServer)); |
|
697
|
DuplicateHandle(GetCurrentProcess(), hStoppedEvent, |
|
698
|
GetCurrentProcess(), &pServer->hStoppedEvent, |
|
699
|
0, FALSE, DUPLICATE_SAME_ACCESS); |
|
700
|
assert( pServer->hStoppedEvent!=NULL ); |
|
701
|
pServer->zStopper = fossil_strdup(zStopper); |
|
702
|
pServer->listener = ds; |
|
703
|
file_delete(zStopper); |
|
704
|
_beginthread(win32_server_stopper, 0, (void*)pServer); |
|
705
|
} |
|
706
|
/* Set the service status to running and pass the listener socket to the |
|
707
|
** service handling procedures. */ |
|
708
|
win32_http_service_running(&ds); |
|
709
|
for(;;){ |
|
710
|
DualSocket client; |
|
711
|
DualAddr client_addr; |
|
712
|
HttpRequest *pRequest; |
|
713
|
int wsaError; |
|
714
|
|
|
715
|
DualSocket_accept(&ds, &client, &client_addr); |
|
716
|
if( client.s4==INVALID_SOCKET && client.s6==INVALID_SOCKET ){ |
|
717
|
/* If the service control handler has closed the listener socket, |
|
718
|
** cleanup and return, otherwise report a fatal error. */ |
|
719
|
wsaError = WSAGetLastError(); |
|
720
|
DualSocket_close(&ds); |
|
721
|
if( (wsaError==WSAEINTR) || (wsaError==WSAENOTSOCK) ){ |
|
722
|
WSACleanup(); |
|
723
|
return; |
|
724
|
}else{ |
|
725
|
WSACleanup(); |
|
726
|
fossil_panic("error from accept()"); |
|
727
|
} |
|
728
|
} |
|
729
|
if( client.s4!=INVALID_SOCKET ){ |
|
730
|
pRequest = fossil_malloc(sizeof(HttpRequest)); |
|
731
|
pRequest->id = ++idCnt; |
|
732
|
pRequest->s = client.s4; |
|
733
|
memcpy(&pRequest->addr, &client_addr.a4, sizeof(client_addr.a4)); |
|
734
|
pRequest->flags = flags; |
|
735
|
pRequest->zOptions = blob_str(&options); |
|
736
|
if( flags & HTTP_SERVER_SCGI ){ |
|
737
|
_beginthread(win32_scgi_request, 0, (void*)pRequest); |
|
738
|
}else{ |
|
739
|
_beginthread(win32_http_request, 0, (void*)pRequest); |
|
740
|
} |
|
741
|
} |
|
742
|
if( client.s6!=INVALID_SOCKET ){ |
|
743
|
pRequest = fossil_malloc(sizeof(HttpRequest)); |
|
744
|
pRequest->id = ++idCnt; |
|
745
|
pRequest->s = client.s6; |
|
746
|
memcpy(&pRequest->addr, &client_addr.a6, sizeof(client_addr.a6)); |
|
747
|
pRequest->flags = flags; |
|
748
|
pRequest->zOptions = blob_str(&options); |
|
749
|
if( flags & HTTP_SERVER_SCGI ){ |
|
750
|
_beginthread(win32_scgi_request, 0, (void*)pRequest); |
|
751
|
}else{ |
|
752
|
_beginthread(win32_http_request, 0, (void*)pRequest); |
|
753
|
} |
|
754
|
} |
|
755
|
} |
|
756
|
DualSocket_close(&ds); |
|
757
|
WSACleanup(); |
|
758
|
SetEvent(hStoppedEvent); |
|
759
|
CloseHandle(hStoppedEvent); |
|
760
|
} |
|
761
|
|
|
762
|
/* |
|
763
|
** The HttpService structure is used to pass information to the service main |
|
764
|
** function and to the service control handler function. |
|
765
|
*/ |
|
766
|
typedef struct HttpService HttpService; |
|
767
|
struct HttpService { |
|
768
|
int port; /* Port on which the http server should run */ |
|
769
|
const char *zBaseUrl; /* The --baseurl option, or NULL */ |
|
770
|
const char *zNotFound; /* The --notfound option, or NULL */ |
|
771
|
const char *zFileGlob; /* The --files option, or NULL */ |
|
772
|
int flags; /* One or more HTTP_SERVER_ flags */ |
|
773
|
int isRunningAsService; /* Are we running as a service ? */ |
|
774
|
const wchar_t *zServiceName;/* Name of the service */ |
|
775
|
DualSocket s; /* Sockets on which the http server listens */ |
|
776
|
}; |
|
777
|
|
|
778
|
/* |
|
779
|
** Variables used for running as windows service. |
|
780
|
*/ |
|
781
|
static HttpService hsData = {8080, NULL, NULL, NULL, 0, 0, NULL, |
|
782
|
{INVALID_SOCKET, INVALID_SOCKET}}; |
|
783
|
static SERVICE_STATUS ssStatus; |
|
784
|
static SERVICE_STATUS_HANDLE sshStatusHandle; |
|
785
|
|
|
786
|
/* |
|
787
|
** Get message string of the last system error. Return a pointer to the |
|
788
|
** message string. Call fossil_unicode_free() to deallocate any memory used |
|
789
|
** to store the message string when done. |
|
790
|
*/ |
|
791
|
static char *win32_get_last_errmsg(void){ |
|
792
|
DWORD nMsg; |
|
793
|
DWORD nErr = GetLastError(); |
|
794
|
LPWSTR tmp = NULL; |
|
795
|
char *zMsg = NULL; |
|
796
|
|
|
797
|
/* Try first to get the error text in English. */ |
|
798
|
nMsg = FormatMessageW( |
|
799
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | |
|
800
|
FORMAT_MESSAGE_FROM_SYSTEM | |
|
801
|
FORMAT_MESSAGE_IGNORE_INSERTS, |
|
802
|
NULL, |
|
803
|
nErr, |
|
804
|
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), |
|
805
|
(LPWSTR) &tmp, |
|
806
|
0, |
|
807
|
NULL |
|
808
|
); |
|
809
|
if( !nMsg ){ |
|
810
|
/* No English, get what the system has available. */ |
|
811
|
nMsg = FormatMessageW( |
|
812
|
FORMAT_MESSAGE_ALLOCATE_BUFFER | |
|
813
|
FORMAT_MESSAGE_FROM_SYSTEM | |
|
814
|
FORMAT_MESSAGE_IGNORE_INSERTS, |
|
815
|
NULL, |
|
816
|
nErr, |
|
817
|
0, |
|
818
|
(LPWSTR) &tmp, |
|
819
|
0, |
|
820
|
NULL |
|
821
|
); |
|
822
|
} |
|
823
|
if( nMsg ){ |
|
824
|
zMsg = fossil_unicode_to_utf8(tmp); |
|
825
|
}else{ |
|
826
|
fossil_panic("unable to get system error message."); |
|
827
|
} |
|
828
|
if( tmp ){ |
|
829
|
LocalFree((HLOCAL) tmp); |
|
830
|
} |
|
831
|
return zMsg; |
|
832
|
} |
|
833
|
|
|
834
|
/* |
|
835
|
** Report the current status of the service to the service control manager. |
|
836
|
** Make sure that during service startup no control codes are accepted. |
|
837
|
*/ |
|
838
|
static void win32_report_service_status( |
|
839
|
DWORD dwCurrentState, /* The current state of the service */ |
|
840
|
DWORD dwWin32ExitCode, /* The error code to report */ |
|
841
|
DWORD dwWaitHint /* The estimated time for a pending operation */ |
|
842
|
){ |
|
843
|
if( dwCurrentState==SERVICE_START_PENDING ){ |
|
844
|
ssStatus.dwControlsAccepted = 0; |
|
845
|
}else{ |
|
846
|
ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; |
|
847
|
} |
|
848
|
ssStatus.dwCurrentState = dwCurrentState; |
|
849
|
ssStatus.dwWin32ExitCode = dwWin32ExitCode; |
|
850
|
ssStatus.dwWaitHint = dwWaitHint; |
|
851
|
|
|
852
|
if( (dwCurrentState==SERVICE_RUNNING) || |
|
853
|
(dwCurrentState==SERVICE_STOPPED) ){ |
|
854
|
ssStatus.dwCheckPoint = 0; |
|
855
|
}else{ |
|
856
|
ssStatus.dwCheckPoint++; |
|
857
|
} |
|
858
|
SetServiceStatus(sshStatusHandle, &ssStatus); |
|
859
|
return ; |
|
860
|
} |
|
861
|
|
|
862
|
/* |
|
863
|
** Handle control codes sent from the service control manager. |
|
864
|
** The control dispatcher in the main thread of the service process invokes |
|
865
|
** this function whenever it receives a control request from the service |
|
866
|
** control manager. |
|
867
|
*/ |
|
868
|
static void WINAPI win32_http_service_ctrl( |
|
869
|
DWORD dwCtrlCode |
|
870
|
){ |
|
871
|
switch( dwCtrlCode ){ |
|
872
|
case SERVICE_CONTROL_STOP: { |
|
873
|
DualSocket_close(&hsData.s); |
|
874
|
win32_report_service_status(SERVICE_STOP_PENDING, NO_ERROR, 0); |
|
875
|
break; |
|
876
|
} |
|
877
|
default: { |
|
878
|
break; |
|
879
|
} |
|
880
|
} |
|
881
|
return; |
|
882
|
} |
|
883
|
|
|
884
|
/* |
|
885
|
** This is the main entry point for the service. |
|
886
|
** When the service control manager receives a request to start the service, |
|
887
|
** it starts the service process (if it is not already running). The main |
|
888
|
** thread of the service process calls the StartServiceCtrlDispatcher |
|
889
|
** function with a pointer to an array of SERVICE_TABLE_ENTRY structures. |
|
890
|
** Then the service control manager sends a start request to the service |
|
891
|
** control dispatcher for this service process. The service control dispatcher |
|
892
|
** creates a new thread to execute the ServiceMain function (this function) |
|
893
|
** of the service being started. |
|
894
|
*/ |
|
895
|
static void WINAPI win32_http_service_main( |
|
896
|
DWORD argc, /* Number of arguments in argv */ |
|
897
|
LPWSTR *argv /* Arguments passed */ |
|
898
|
){ |
|
899
|
|
|
900
|
/* Update the service information. */ |
|
901
|
hsData.isRunningAsService = 1; |
|
902
|
if( argc>0 ){ |
|
903
|
hsData.zServiceName = argv[0]; |
|
904
|
} |
|
905
|
|
|
906
|
/* Register the service control handler function */ |
|
907
|
sshStatusHandle = RegisterServiceCtrlHandlerW(L"", win32_http_service_ctrl); |
|
908
|
if( !sshStatusHandle ){ |
|
909
|
win32_report_service_status(SERVICE_STOPPED, NO_ERROR, 0); |
|
910
|
return; |
|
911
|
} |
|
912
|
|
|
913
|
/* Set service specific data and report that the service is starting. */ |
|
914
|
ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; |
|
915
|
ssStatus.dwServiceSpecificExitCode = 0; |
|
916
|
win32_report_service_status(SERVICE_START_PENDING, NO_ERROR, 3000); |
|
917
|
|
|
918
|
/* Execute the http server */ |
|
919
|
win32_http_server(hsData.port, hsData.port, |
|
920
|
NULL, NULL, hsData.zBaseUrl, hsData.zNotFound, |
|
921
|
hsData.zFileGlob, 0, hsData.flags); |
|
922
|
|
|
923
|
/* Service has stopped now. */ |
|
924
|
win32_report_service_status(SERVICE_STOPPED, NO_ERROR, 0); |
|
925
|
return; |
|
926
|
} |
|
927
|
|
|
928
|
/* |
|
929
|
** When running as service, update the HttpService structure with the |
|
930
|
** listener socket and update the service status. This procedure must be |
|
931
|
** called from the http server when he is ready to accept connections. |
|
932
|
*/ |
|
933
|
static void win32_http_service_running(DualSocket *pS){ |
|
934
|
if( hsData.isRunningAsService ){ |
|
935
|
hsData.s = *pS; |
|
936
|
win32_report_service_status(SERVICE_RUNNING, NO_ERROR, 0); |
|
937
|
} |
|
938
|
} |
|
939
|
|
|
940
|
/* |
|
941
|
** Try to start the http server as a windows service. If we are running in |
|
942
|
** an interactive console session, this routine fails and returns a non zero |
|
943
|
** integer value. When running as service, this routine does not return until |
|
944
|
** the service is stopped. In this case, the return value is zero. |
|
945
|
*/ |
|
946
|
int win32_http_service( |
|
947
|
int nPort, /* TCP port number */ |
|
948
|
const char *zBaseUrl, /* The --baseurl option, or NULL */ |
|
949
|
const char *zNotFound, /* The --notfound option, or NULL */ |
|
950
|
const char *zFileGlob, /* The --files option, or NULL */ |
|
951
|
int flags /* One or more HTTP_SERVER_ flags */ |
|
952
|
){ |
|
953
|
/* Define the service table. */ |
|
954
|
SERVICE_TABLE_ENTRYW ServiceTable[] = |
|
955
|
{{L"", (LPSERVICE_MAIN_FUNCTIONW)win32_http_service_main}, {NULL, NULL}}; |
|
956
|
|
|
957
|
/* Initialize the HttpService structure. */ |
|
958
|
hsData.port = nPort; |
|
959
|
hsData.zBaseUrl = zBaseUrl; |
|
960
|
hsData.zNotFound = zNotFound; |
|
961
|
hsData.zFileGlob = zFileGlob; |
|
962
|
hsData.flags = flags; |
|
963
|
|
|
964
|
if( GetStdHandle(STD_INPUT_HANDLE)!=NULL ){ return 1; } |
|
965
|
|
|
966
|
/* Try to start the control dispatcher thread for the service. */ |
|
967
|
if( !StartServiceCtrlDispatcherW(ServiceTable) ){ |
|
968
|
if( GetLastError()==ERROR_FAILED_SERVICE_CONTROLLER_CONNECT ){ |
|
969
|
return 1; |
|
970
|
}else{ |
|
971
|
fossil_fatal("error from StartServiceCtrlDispatcher()"); |
|
972
|
} |
|
973
|
} |
|
974
|
return 0; |
|
975
|
} |
|
976
|
|
|
977
|
/* Duplicate #ifdef needed for mkindex */ |
|
978
|
#ifdef _WIN32 |
|
979
|
/* |
|
980
|
** COMMAND: winsrv* |
|
981
|
** |
|
982
|
** Usage: %fossil winsrv METHOD ?SERVICE-NAME? ?OPTIONS? |
|
983
|
** |
|
984
|
** Where METHOD is one of: create delete show start stop. |
|
985
|
** |
|
986
|
** The winsrv command manages Fossil as a Windows service. This allows |
|
987
|
** (for example) Fossil to be running in the background when no user |
|
988
|
** is logged in. |
|
989
|
** |
|
990
|
** In the following description of the methods, "Fossil-DSCM" will be |
|
991
|
** used as the default SERVICE-NAME: |
|
992
|
** |
|
993
|
** %fossil winsrv create ?SERVICE-NAME? ?OPTIONS? |
|
994
|
** |
|
995
|
** Creates a service. Available options include: |
|
996
|
** |
|
997
|
** -D|--display DISPLAY-NAME |
|
998
|
** |
|
999
|
** Sets the display name of the service. This name is shown |
|
1000
|
** by graphical interface programs. By default, the display name |
|
1001
|
** is equal to the service name. |
|
1002
|
** |
|
1003
|
** -S|--start TYPE |
|
1004
|
** |
|
1005
|
** Sets the start type of the service. TYPE can be "manual", |
|
1006
|
** which means you need to start the service yourself with the |
|
1007
|
** 'fossil winsrv start' command or with the "net start" command |
|
1008
|
** from the operating system. If TYPE is set to "auto", the service |
|
1009
|
** will be started automatically by the system during startup. |
|
1010
|
** |
|
1011
|
** --username USERNAME |
|
1012
|
** |
|
1013
|
** Specifies the user account which will be used to run the |
|
1014
|
** service. The account needs the "Logon as a service" right |
|
1015
|
** enabled in its profile. Specify local accounts as follows: |
|
1016
|
** ".\\USERNAME". By default, the "LocalSystem" account will be |
|
1017
|
** used. |
|
1018
|
** |
|
1019
|
** -W|--password PASSWORD |
|
1020
|
** |
|
1021
|
** Password for the user account. |
|
1022
|
** |
|
1023
|
** The following options are more or less the same as for the "server" |
|
1024
|
** command and influence the behavior of the http server: |
|
1025
|
** |
|
1026
|
** --baseurl URL |
|
1027
|
** |
|
1028
|
** Use URL as the base (useful for reverse proxies) |
|
1029
|
** |
|
1030
|
** -P|--port TCPPORT |
|
1031
|
** |
|
1032
|
** Specifies the TCP port (default port is 8080) on which the |
|
1033
|
** server should listen. |
|
1034
|
** |
|
1035
|
** -R|--repository REPO |
|
1036
|
** |
|
1037
|
** Specifies the name of the repository to be served. |
|
1038
|
** The repository option may be omitted if the working directory |
|
1039
|
** is within an open check-out. |
|
1040
|
** The REPOSITORY can be a directory (aka folder) that contains |
|
1041
|
** one or more repositories with names ending in ".fossil". |
|
1042
|
** In that case, the first element of the URL is used to select |
|
1043
|
** among the various repositories. |
|
1044
|
** |
|
1045
|
** --notfound URL |
|
1046
|
** |
|
1047
|
** If REPOSITORY is a directory that contains one or more |
|
1048
|
** repositories with names of the form "*.fossil" then the |
|
1049
|
** first element of the URL pathname selects among the various |
|
1050
|
** repositories. If the pathname does not select a valid |
|
1051
|
** repository and the --notfound option is available, |
|
1052
|
** then the server redirects (HTTP code 302) to the URL of |
|
1053
|
** --notfound. |
|
1054
|
** |
|
1055
|
** --localauth |
|
1056
|
** |
|
1057
|
** Enables automatic login if the --localauth option is present |
|
1058
|
** and the "localauth" setting is off and the connection is from |
|
1059
|
** localhost. |
|
1060
|
** |
|
1061
|
** --repolist |
|
1062
|
** |
|
1063
|
** If REPOSITORY is directory, URL "/" lists all repositories. |
|
1064
|
** |
|
1065
|
** --scgi |
|
1066
|
** |
|
1067
|
** Create an SCGI server instead of an HTTP server |
|
1068
|
** |
|
1069
|
** |
|
1070
|
** %fossil winsrv delete ?SERVICE-NAME? |
|
1071
|
** |
|
1072
|
** Deletes a service. If the service is currently running, it will be |
|
1073
|
** stopped first and then deleted. |
|
1074
|
** |
|
1075
|
** |
|
1076
|
** %fossil winsrv show ?SERVICE-NAME? |
|
1077
|
** |
|
1078
|
** Shows how the service is configured and its current state. |
|
1079
|
** |
|
1080
|
** |
|
1081
|
** %fossil winsrv start ?SERVICE-NAME? |
|
1082
|
** |
|
1083
|
** Start the service. |
|
1084
|
** |
|
1085
|
** |
|
1086
|
** %fossil winsrv stop ?SERVICE-NAME? |
|
1087
|
** |
|
1088
|
** Stop the service. |
|
1089
|
** |
|
1090
|
** |
|
1091
|
** NOTE: This command is available on Windows operating systems only and |
|
1092
|
** requires administrative rights on the machine executed. |
|
1093
|
** |
|
1094
|
*/ |
|
1095
|
void cmd_win32_service(void){ |
|
1096
|
int n; |
|
1097
|
const char *zMethod; |
|
1098
|
const char *zSvcName = "Fossil-DSCM"; /* Default service name */ |
|
1099
|
|
|
1100
|
if( g.argc<3 ){ |
|
1101
|
usage("create|delete|show|start|stop ..."); |
|
1102
|
} |
|
1103
|
zMethod = g.argv[2]; |
|
1104
|
n = strlen(zMethod); |
|
1105
|
|
|
1106
|
if( strncmp(zMethod, "create", n)==0 ){ |
|
1107
|
SC_HANDLE hScm; |
|
1108
|
SC_HANDLE hSvc; |
|
1109
|
SERVICE_DESCRIPTIONW |
|
1110
|
svcDescr = {L"Fossil - Distributed Software Configuration Management"}; |
|
1111
|
DWORD dwStartType = SERVICE_DEMAND_START; |
|
1112
|
const char *zAltBase = find_option("baseurl", 0, 1); |
|
1113
|
const char *zDisplay = find_option("display", "D", 1); |
|
1114
|
const char *zStart = find_option("start", "S", 1); |
|
1115
|
const char *zUsername = find_option("username", 0, 1); |
|
1116
|
const char *zPassword = find_option("password", "W", 1); |
|
1117
|
const char *zPort = find_option("port", "P", 1); |
|
1118
|
const char *zNotFound = find_option("notfound", 0, 1); |
|
1119
|
const char *zFileGlob = find_option("files", 0, 1); |
|
1120
|
const char *zLocalAuth = find_option("localauth", 0, 0); |
|
1121
|
const char *zRepository = find_repository_option(); |
|
1122
|
int useSCGI = find_option("scgi", 0, 0)!=0; |
|
1123
|
int allowRepoList = find_option("repolist",0,0)!=0; |
|
1124
|
Blob binPath; |
|
1125
|
|
|
1126
|
verify_all_options(); |
|
1127
|
if( g.argc==4 ){ |
|
1128
|
zSvcName = g.argv[3]; |
|
1129
|
}else if( g.argc>4 ){ |
|
1130
|
fossil_fatal("too many arguments for create method."); |
|
1131
|
} |
|
1132
|
/* Process service creation specific options. */ |
|
1133
|
if( !zDisplay ){ |
|
1134
|
zDisplay = zSvcName; |
|
1135
|
} |
|
1136
|
/* Per MSDN, the password parameter cannot be NULL. Must use empty |
|
1137
|
** string instead (i.e. in the call to CreateServiceW). */ |
|
1138
|
if( !zPassword ){ |
|
1139
|
zPassword = ""; |
|
1140
|
} |
|
1141
|
if( zStart ){ |
|
1142
|
if( strncmp(zStart, "auto", strlen(zStart))==0 ){ |
|
1143
|
dwStartType = SERVICE_AUTO_START; |
|
1144
|
}else if( strncmp(zStart, "manual", strlen(zStart))==0 ){ |
|
1145
|
dwStartType = SERVICE_DEMAND_START; |
|
1146
|
}else{ |
|
1147
|
winhttp_fatal("create", zSvcName, |
|
1148
|
"specify 'auto' or 'manual' for the '-S|--start' option"); |
|
1149
|
} |
|
1150
|
} |
|
1151
|
/* Process options for Fossil running as server. */ |
|
1152
|
if( zPort && (atoi(zPort)<=0) ){ |
|
1153
|
winhttp_fatal("create", zSvcName, |
|
1154
|
"port number must be in the range 1 - 65535."); |
|
1155
|
} |
|
1156
|
if( !zRepository ){ |
|
1157
|
db_must_be_within_tree(); |
|
1158
|
}else if( file_isdir(zRepository, ExtFILE)==1 ){ |
|
1159
|
g.zRepositoryName = fossil_strdup(zRepository); |
|
1160
|
file_simplify_name(g.zRepositoryName, -1, 0); |
|
1161
|
}else{ |
|
1162
|
db_open_repository(zRepository); |
|
1163
|
} |
|
1164
|
db_close(0); |
|
1165
|
/* Build the fully-qualified path to the service binary file. */ |
|
1166
|
blob_zero(&binPath); |
|
1167
|
blob_appendf(&binPath, "\"%s\" server", g.nameOfExe); |
|
1168
|
if( zAltBase ) blob_appendf(&binPath, " --baseurl %s", zAltBase); |
|
1169
|
if( zPort ) blob_appendf(&binPath, " --port %s", zPort); |
|
1170
|
if( useSCGI ) blob_appendf(&binPath, " --scgi"); |
|
1171
|
if( allowRepoList ) blob_appendf(&binPath, " --repolist"); |
|
1172
|
if( zNotFound ) blob_appendf(&binPath, " --notfound \"%s\"", zNotFound); |
|
1173
|
if( zFileGlob ) blob_appendf(&binPath, " --files-urlenc %T", zFileGlob); |
|
1174
|
if( zLocalAuth ) blob_append(&binPath, " --localauth", -1); |
|
1175
|
blob_appendf(&binPath, " \"%s\"", g.zRepositoryName); |
|
1176
|
/* Create the service. */ |
|
1177
|
hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); |
|
1178
|
if( !hScm ) winhttp_fatal("create", zSvcName, win32_get_last_errmsg()); |
|
1179
|
hSvc = CreateServiceW( |
|
1180
|
hScm, /* Handle to the SCM */ |
|
1181
|
fossil_utf8_to_unicode(zSvcName), /* Name of the service */ |
|
1182
|
fossil_utf8_to_unicode(zDisplay), /* Display name */ |
|
1183
|
SERVICE_ALL_ACCESS, /* Desired access */ |
|
1184
|
SERVICE_WIN32_OWN_PROCESS, /* Service type */ |
|
1185
|
dwStartType, /* Start type */ |
|
1186
|
SERVICE_ERROR_NORMAL, /* Error control */ |
|
1187
|
fossil_utf8_to_unicode(blob_str(&binPath)), /* Binary path */ |
|
1188
|
NULL, /* Load ordering group */ |
|
1189
|
NULL, /* Tag value */ |
|
1190
|
NULL, /* Service dependencies */ |
|
1191
|
zUsername ? fossil_utf8_to_unicode(zUsername) : 0, /* Account */ |
|
1192
|
fossil_utf8_to_unicode(zPassword) /* Account password */ |
|
1193
|
); |
|
1194
|
if( !hSvc ) winhttp_fatal("create", zSvcName, win32_get_last_errmsg()); |
|
1195
|
/* Set the service description. */ |
|
1196
|
ChangeServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, &svcDescr); |
|
1197
|
fossil_print("Service '%s' successfully created.\n", zSvcName); |
|
1198
|
CloseServiceHandle(hSvc); |
|
1199
|
CloseServiceHandle(hScm); |
|
1200
|
}else |
|
1201
|
if( strncmp(zMethod, "delete", n)==0 ){ |
|
1202
|
SC_HANDLE hScm; |
|
1203
|
SC_HANDLE hSvc; |
|
1204
|
SERVICE_STATUS sstat; |
|
1205
|
|
|
1206
|
verify_all_options(); |
|
1207
|
if( g.argc==4 ){ |
|
1208
|
zSvcName = g.argv[3]; |
|
1209
|
}else if( g.argc>4 ){ |
|
1210
|
fossil_fatal("too many arguments for delete method."); |
|
1211
|
} |
|
1212
|
hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); |
|
1213
|
if( !hScm ) winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); |
|
1214
|
hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), |
|
1215
|
SERVICE_ALL_ACCESS); |
|
1216
|
if( !hSvc ) winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); |
|
1217
|
QueryServiceStatus(hSvc, &sstat); |
|
1218
|
if( sstat.dwCurrentState!=SERVICE_STOPPED ){ |
|
1219
|
fossil_print("Stopping service '%s'", zSvcName); |
|
1220
|
if( sstat.dwCurrentState!=SERVICE_STOP_PENDING ){ |
|
1221
|
if( !ControlService(hSvc, SERVICE_CONTROL_STOP, &sstat) ){ |
|
1222
|
winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); |
|
1223
|
} |
|
1224
|
QueryServiceStatus(hSvc, &sstat); |
|
1225
|
} |
|
1226
|
while( sstat.dwCurrentState==SERVICE_STOP_PENDING || |
|
1227
|
sstat.dwCurrentState==SERVICE_RUNNING ){ |
|
1228
|
Sleep(100); |
|
1229
|
fossil_print("."); |
|
1230
|
QueryServiceStatus(hSvc, &sstat); |
|
1231
|
} |
|
1232
|
if( sstat.dwCurrentState==SERVICE_STOPPED ){ |
|
1233
|
fossil_print("\nService '%s' stopped.\n", zSvcName); |
|
1234
|
}else{ |
|
1235
|
winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); |
|
1236
|
} |
|
1237
|
} |
|
1238
|
if( !DeleteService(hSvc) ){ |
|
1239
|
if( GetLastError()==ERROR_SERVICE_MARKED_FOR_DELETE ){ |
|
1240
|
fossil_warning("Service '%s' already marked for delete.\n", zSvcName); |
|
1241
|
}else{ |
|
1242
|
winhttp_fatal("delete", zSvcName, win32_get_last_errmsg()); |
|
1243
|
} |
|
1244
|
}else{ |
|
1245
|
fossil_print("Service '%s' successfully deleted.\n", zSvcName); |
|
1246
|
} |
|
1247
|
CloseServiceHandle(hSvc); |
|
1248
|
CloseServiceHandle(hScm); |
|
1249
|
}else |
|
1250
|
if( strncmp(zMethod, "show", n)==0 ){ |
|
1251
|
SC_HANDLE hScm; |
|
1252
|
SC_HANDLE hSvc; |
|
1253
|
SERVICE_STATUS sstat; |
|
1254
|
LPQUERY_SERVICE_CONFIGW pSvcConfig; |
|
1255
|
LPSERVICE_DESCRIPTIONW pSvcDescr; |
|
1256
|
BOOL bStatus; |
|
1257
|
DWORD nRequired; |
|
1258
|
static const char *const zSvcTypes[] = { |
|
1259
|
"Driver service", |
|
1260
|
"File system driver service", |
|
1261
|
"Service runs in its own process", |
|
1262
|
"Service shares a process with other services", |
|
1263
|
"Service can interact with the desktop" |
|
1264
|
}; |
|
1265
|
const char *zSvcType = ""; |
|
1266
|
static const char *const zSvcStartTypes[] = { |
|
1267
|
"Started by the system loader", |
|
1268
|
"Started by the IoInitSystem function", |
|
1269
|
"Started automatically by the service control manager", |
|
1270
|
"Started manually", |
|
1271
|
"Service cannot be started" |
|
1272
|
}; |
|
1273
|
const char *zSvcStartType = ""; |
|
1274
|
static const char *const zSvcStates[] = { |
|
1275
|
"Stopped", "Starting", "Stopping", "Running", |
|
1276
|
"Continue pending", "Pause pending", "Paused" |
|
1277
|
}; |
|
1278
|
const char *zSvcState = ""; |
|
1279
|
|
|
1280
|
verify_all_options(); |
|
1281
|
if( g.argc==4 ){ |
|
1282
|
zSvcName = g.argv[3]; |
|
1283
|
}else if( g.argc>4 ){ |
|
1284
|
fossil_fatal("too many arguments for show method."); |
|
1285
|
} |
|
1286
|
hScm = OpenSCManagerW(NULL, NULL, GENERIC_READ); |
|
1287
|
if( !hScm ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); |
|
1288
|
hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), GENERIC_READ); |
|
1289
|
if( !hSvc ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); |
|
1290
|
/* Get the service configuration */ |
|
1291
|
bStatus = QueryServiceConfigW(hSvc, NULL, 0, &nRequired); |
|
1292
|
if( !bStatus && GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ |
|
1293
|
winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); |
|
1294
|
} |
|
1295
|
pSvcConfig = fossil_malloc(nRequired); |
|
1296
|
bStatus = QueryServiceConfigW(hSvc, pSvcConfig, nRequired, &nRequired); |
|
1297
|
if( !bStatus ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); |
|
1298
|
/* Translate the service type */ |
|
1299
|
switch( pSvcConfig->dwServiceType ){ |
|
1300
|
case SERVICE_KERNEL_DRIVER: zSvcType = zSvcTypes[0]; break; |
|
1301
|
case SERVICE_FILE_SYSTEM_DRIVER: zSvcType = zSvcTypes[1]; break; |
|
1302
|
case SERVICE_WIN32_OWN_PROCESS: zSvcType = zSvcTypes[2]; break; |
|
1303
|
case SERVICE_WIN32_SHARE_PROCESS: zSvcType = zSvcTypes[3]; break; |
|
1304
|
case SERVICE_INTERACTIVE_PROCESS: zSvcType = zSvcTypes[4]; break; |
|
1305
|
} |
|
1306
|
/* Translate the service start type */ |
|
1307
|
switch( pSvcConfig->dwStartType ){ |
|
1308
|
case SERVICE_BOOT_START: zSvcStartType = zSvcStartTypes[0]; break; |
|
1309
|
case SERVICE_SYSTEM_START: zSvcStartType = zSvcStartTypes[1]; break; |
|
1310
|
case SERVICE_AUTO_START: zSvcStartType = zSvcStartTypes[2]; break; |
|
1311
|
case SERVICE_DEMAND_START: zSvcStartType = zSvcStartTypes[3]; break; |
|
1312
|
case SERVICE_DISABLED: zSvcStartType = zSvcStartTypes[4]; break; |
|
1313
|
} |
|
1314
|
/* Get the service description. */ |
|
1315
|
bStatus = QueryServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, |
|
1316
|
NULL, 0, &nRequired); |
|
1317
|
if( !bStatus && GetLastError()!=ERROR_INSUFFICIENT_BUFFER ){ |
|
1318
|
winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); |
|
1319
|
} |
|
1320
|
pSvcDescr = fossil_malloc(nRequired); |
|
1321
|
bStatus = QueryServiceConfig2W(hSvc, SERVICE_CONFIG_DESCRIPTION, |
|
1322
|
(LPBYTE)pSvcDescr, nRequired, &nRequired); |
|
1323
|
if( !bStatus ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); |
|
1324
|
/* Retrieves the current status of the specified service. */ |
|
1325
|
bStatus = QueryServiceStatus(hSvc, &sstat); |
|
1326
|
if( !bStatus ) winhttp_fatal("show", zSvcName, win32_get_last_errmsg()); |
|
1327
|
/* Translate the current state. */ |
|
1328
|
switch( sstat.dwCurrentState ){ |
|
1329
|
case SERVICE_STOPPED: zSvcState = zSvcStates[0]; break; |
|
1330
|
case SERVICE_START_PENDING: zSvcState = zSvcStates[1]; break; |
|
1331
|
case SERVICE_STOP_PENDING: zSvcState = zSvcStates[2]; break; |
|
1332
|
case SERVICE_RUNNING: zSvcState = zSvcStates[3]; break; |
|
1333
|
case SERVICE_CONTINUE_PENDING: zSvcState = zSvcStates[4]; break; |
|
1334
|
case SERVICE_PAUSE_PENDING: zSvcState = zSvcStates[5]; break; |
|
1335
|
case SERVICE_PAUSED: zSvcState = zSvcStates[6]; break; |
|
1336
|
} |
|
1337
|
/* Print service information to terminal */ |
|
1338
|
fossil_print("Service name .......: %s\n", zSvcName); |
|
1339
|
fossil_print("Display name .......: %s\n", |
|
1340
|
fossil_unicode_to_utf8(pSvcConfig->lpDisplayName)); |
|
1341
|
fossil_print("Service description : %s\n", |
|
1342
|
fossil_unicode_to_utf8(pSvcDescr->lpDescription)); |
|
1343
|
fossil_print("Service type .......: %s.\n", zSvcType); |
|
1344
|
fossil_print("Service start type .: %s.\n", zSvcStartType); |
|
1345
|
fossil_print("Binary path name ...: %s\n", |
|
1346
|
fossil_unicode_to_utf8(pSvcConfig->lpBinaryPathName)); |
|
1347
|
fossil_print("Service username ...: %s\n", |
|
1348
|
fossil_unicode_to_utf8(pSvcConfig->lpServiceStartName)); |
|
1349
|
fossil_print("Current state ......: %s.\n", zSvcState); |
|
1350
|
/* Cleanup */ |
|
1351
|
fossil_free(pSvcConfig); |
|
1352
|
fossil_free(pSvcDescr); |
|
1353
|
CloseServiceHandle(hSvc); |
|
1354
|
CloseServiceHandle(hScm); |
|
1355
|
}else |
|
1356
|
if( strncmp(zMethod, "start", n)==0 ){ |
|
1357
|
SC_HANDLE hScm; |
|
1358
|
SC_HANDLE hSvc; |
|
1359
|
SERVICE_STATUS sstat; |
|
1360
|
|
|
1361
|
verify_all_options(); |
|
1362
|
if( g.argc==4 ){ |
|
1363
|
zSvcName = g.argv[3]; |
|
1364
|
}else if( g.argc>4 ){ |
|
1365
|
fossil_fatal("too many arguments for start method."); |
|
1366
|
} |
|
1367
|
hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); |
|
1368
|
if( !hScm ) winhttp_fatal("start", zSvcName, win32_get_last_errmsg()); |
|
1369
|
hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), |
|
1370
|
SERVICE_ALL_ACCESS); |
|
1371
|
if( !hSvc ) winhttp_fatal("start", zSvcName, win32_get_last_errmsg()); |
|
1372
|
QueryServiceStatus(hSvc, &sstat); |
|
1373
|
if( sstat.dwCurrentState!=SERVICE_RUNNING ){ |
|
1374
|
fossil_print("Starting service '%s'", zSvcName); |
|
1375
|
if( sstat.dwCurrentState!=SERVICE_START_PENDING ){ |
|
1376
|
if( !StartServiceW(hSvc, 0, NULL) ){ |
|
1377
|
winhttp_fatal("start", zSvcName, win32_get_last_errmsg()); |
|
1378
|
} |
|
1379
|
QueryServiceStatus(hSvc, &sstat); |
|
1380
|
} |
|
1381
|
while( sstat.dwCurrentState==SERVICE_START_PENDING || |
|
1382
|
sstat.dwCurrentState==SERVICE_STOPPED ){ |
|
1383
|
Sleep(100); |
|
1384
|
fossil_print("."); |
|
1385
|
QueryServiceStatus(hSvc, &sstat); |
|
1386
|
} |
|
1387
|
if( sstat.dwCurrentState==SERVICE_RUNNING ){ |
|
1388
|
fossil_print("\nService '%s' started.\n", zSvcName); |
|
1389
|
}else{ |
|
1390
|
winhttp_fatal("start", zSvcName, win32_get_last_errmsg()); |
|
1391
|
} |
|
1392
|
}else{ |
|
1393
|
fossil_print("Service '%s' is already started.\n", zSvcName); |
|
1394
|
} |
|
1395
|
CloseServiceHandle(hSvc); |
|
1396
|
CloseServiceHandle(hScm); |
|
1397
|
}else |
|
1398
|
if( strncmp(zMethod, "stop", n)==0 ){ |
|
1399
|
SC_HANDLE hScm; |
|
1400
|
SC_HANDLE hSvc; |
|
1401
|
SERVICE_STATUS sstat; |
|
1402
|
|
|
1403
|
verify_all_options(); |
|
1404
|
if( g.argc==4 ){ |
|
1405
|
zSvcName = g.argv[3]; |
|
1406
|
}else if( g.argc>4 ){ |
|
1407
|
fossil_fatal("too many arguments for stop method."); |
|
1408
|
} |
|
1409
|
hScm = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS); |
|
1410
|
if( !hScm ) winhttp_fatal("stop", zSvcName, win32_get_last_errmsg()); |
|
1411
|
hSvc = OpenServiceW(hScm, fossil_utf8_to_unicode(zSvcName), |
|
1412
|
SERVICE_ALL_ACCESS); |
|
1413
|
if( !hSvc ) winhttp_fatal("stop", zSvcName, win32_get_last_errmsg()); |
|
1414
|
QueryServiceStatus(hSvc, &sstat); |
|
1415
|
if( sstat.dwCurrentState!=SERVICE_STOPPED ){ |
|
1416
|
fossil_print("Stopping service '%s'", zSvcName); |
|
1417
|
if( sstat.dwCurrentState!=SERVICE_STOP_PENDING ){ |
|
1418
|
if( !ControlService(hSvc, SERVICE_CONTROL_STOP, &sstat) ){ |
|
1419
|
winhttp_fatal("stop", zSvcName, win32_get_last_errmsg()); |
|
1420
|
} |
|
1421
|
QueryServiceStatus(hSvc, &sstat); |
|
1422
|
} |
|
1423
|
while( sstat.dwCurrentState==SERVICE_STOP_PENDING || |
|
1424
|
sstat.dwCurrentState==SERVICE_RUNNING ){ |
|
1425
|
Sleep(100); |
|
1426
|
fossil_print("."); |
|
1427
|
QueryServiceStatus(hSvc, &sstat); |
|
1428
|
} |
|
1429
|
if( sstat.dwCurrentState==SERVICE_STOPPED ){ |
|
1430
|
fossil_print("\nService '%s' stopped.\n", zSvcName); |
|
1431
|
}else{ |
|
1432
|
winhttp_fatal("stop", zSvcName, win32_get_last_errmsg()); |
|
1433
|
} |
|
1434
|
}else{ |
|
1435
|
fossil_print("Service '%s' is already stopped.\n", zSvcName); |
|
1436
|
} |
|
1437
|
CloseServiceHandle(hSvc); |
|
1438
|
CloseServiceHandle(hScm); |
|
1439
|
}else |
|
1440
|
{ |
|
1441
|
fossil_fatal("METHOD should be one of:" |
|
1442
|
" create delete show start stop"); |
|
1443
|
} |
|
1444
|
return; |
|
1445
|
} |
|
1446
|
#endif /* _WIN32 -- dupe needed for mkindex */ |
|
1447
|
#endif /* _WIN32 -- This code is for win32 only */ |
|
1448
|
|