1 #ifdef __WIN32__
2
3 #ifndef _WIN32_WINNT
4 #define _WIN32_WINNT 0x0500
5 #endif //_WIN32_WINNT
6
7 #include <windows.h>
8 #include <winsock.h>
9 #include <wininet.h>
10 #define SHUT_RDWR SD_BOTH
11
12 #ifndef JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
13 #define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE (0x2000)
14 #endif
15 HANDLE FcpJobObject;
16
17 #else
18
19 #include <sys/socket.h>
20 #include <sys/wait.h>
21 #include <fcntl.h>
22 #include <arpa/inet.h>
23 #include <grp.h>
24 #include <pwd.h>
25 #include <unistd.h>
26 #define closesocket close
27
28 #endif //__WIN32__
29
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <getopt.h>
34 #include <string.h>
35 #include <pthread.h>
36 #include <errno.h>
37
38 #define MAX_PROCESSES 1024
39 static const char version[] = "$Revision: 0.01 $";
40 static char* prog_name;
41 int number = 1;
42 int port = 8000;
43 char *ip = "127.0.0.1";
44 char *user = "";
45 char *root = "";
46 char *path = "";
47 char *group = "";
48 int listen_fd;
49 struct sockaddr_in listen_addr;
50 int process_fp[MAX_PROCESSES];
51 int process_idx = 0;
52 pthread_t threads[MAX_PROCESSES];
53
54 static struct option longopts[] =
55 {
56 {"help", no_argument, NULL, 'h'},
57 {"version", no_argument, NULL, 'v'},
58 {"number", required_argument, NULL, 'n'},
59 {"ip", required_argument, NULL, 'i'},
60 {"port", required_argument, NULL, 'p'},
61 {"user", required_argument, NULL, 'u'},
62 {"group", required_argument, NULL, 'g'},
63 {"root", required_argument, NULL, 'r'},
64 {NULL, 0, NULL, 0}
65 };
66
67 static char opts[] = "hvnipugr";
68
69 static void usage(FILE* where)
70 {
71 fprintf(where, ""
72 "Usage: %s path [-n number] [-i ip] [-p port]\n"
73 "Manage FastCGI processes.\n"
74 "\n"
75 " -n, --number number of processes to keep\n"
76 " -i, --ip ip address to bind\n"
77 " -p, --port port to bind, default is 8000\n"
78 " -u, --user start processes using specified linux user\n"
79 " -g, --group start processes using specified linux group\n"
80 " -r, --root change root direcotry for the processes\n"
81 " -h, --help output usage information and exit\n"
82 " -v, --version output version information and exit\n"
83 "", prog_name);
84 exit(where == stderr ? 1:0);
85 }
86
87 static void print_version()
88 {
89 printf("%s %s\n\
90 FastCGI Process Manager\n\
91 Copyright 2010 Xiaoxia.org\n\
92 Compiled on %s\n\
93 ", prog_name, version, __DATE__);
94 exit(0);
95 }
96
97 static int try_to_bind()
98 {
99 listen_addr.sin_family = PF_INET;
100 listen_addr.sin_addr.s_addr = inet_addr( ip );
101 listen_addr.sin_port = htons( port );
102 listen_fd = socket(AF_INET, SOCK_STREAM, 0);
103
104 if (-1 == bind(listen_fd, (struct sockaddr*)&listen_addr, sizeof(struct sockaddr_in)) ) {
105 fprintf(stderr, "failed to bind %s:%d\n", ip, port );
106 return -1;
107 }
108
109 listen(listen_fd, MAX_PROCESSES);
110 return 0;
111 }
112
113 static void* spawn_process(void* arg)
114 {
115 int idx = process_idx ++, ret;
116 while(1){
117 #ifdef __WIN32__
118 STARTUPINFO si={0};
119 PROCESS_INFORMATION pi={0};
120 ZeroMemory(&si,sizeof(STARTUPINFO));
121 si.cb = sizeof(STARTUPINFO);
122 si.dwFlags = STARTF_USESTDHANDLES;
123 si.hStdInput = (HANDLE)listen_fd;
124 si.hStdOutput = INVALID_HANDLE_VALUE;
125 si.hStdError = INVALID_HANDLE_VALUE;
126 if(0 == (ret=CreateProcess(NULL, path,
127 NULL,NULL,
128 TRUE, CREATE_NO_WINDOW | CREATE_SUSPENDED | CREATE_BREAKAWAY_FROM_JOB,
129 NULL,NULL,
130 &si,&pi)) ){
131 fprintf(stderr, "failed to create process %s, ret=%d\n", path, ret);
132 return NULL;
133 }
134
135 /* Use Job Control System */
136 if(!AssignProcessToJobObject(FcpJobObject, pi.hProcess)){
137 TerminateProcess(pi.hProcess, 1);
138 CloseHandle(pi.hProcess);
139 CloseHandle(pi.hThread);
140 return NULL;
141 }
142
143 if(!ResumeThread(pi.hThread)){
144 TerminateProcess(pi.hProcess, 1);
145 CloseHandle(pi.hProcess);
146 CloseHandle(pi.hThread);
147 return NULL;
148 }
149
150 process_fp[idx] = (int)pi.hProcess;
151 WaitForSingleObject(pi.hProcess, INFINITE);
152 process_fp[idx] = 0;
153 CloseHandle(pi.hThread);
154 #else
155 ret = fork();
156 switch(ret){
157 case 0:{ //child
158 /* change uid from root to other user */
159 if(getuid()==0){
160 struct group *grp = NULL;
161 struct passwd *pwd = NULL;
162 if (*user) {
163 if (NULL == (pwd = getpwnam(user))) {
164 fprintf(stderr, "[fcgi] %s %s\n", "can't find username", user);
165 exit(-1);
166 }
167
168 if (pwd->pw_uid == 0) {
169 fprintf(stderr, "[fcgi] %s\n", "what? dest uid == 0?" );
170 exit(-1);
171 }
172 }
173
174 if (*group) {
175 if (NULL == (grp = getgrnam(group))) {
176 fprintf(stderr, "[fcgi] %s %s\n", "can't find groupname", group);
177 exit(1);
178 }
179
180 if (grp->gr_gid == 0) {
181 fprintf(stderr, "[fcgi] %s\n", "what? dest gid == 0?" );
182 exit(1);
183 }
184 /* do the change before we do the chroot() */
185 setgid(grp->gr_gid);
186 setgroups(0, NULL);
187
188 if (user) {
189 initgroups(user, grp->gr_gid);
190 }
191 }
192 if (*root) {
193 if (-1 == chroot(root)) {
194 fprintf(stderr, "[fcgi] %s %s\n", "can't change root", root);
195 exit(1);
196 }
197 if (-1 == chdir("/")) {
198 fprintf(stderr, "[fcgi] %s %s\n", "can't change dir to", root);
199 exit(1);
200 }
201 }
202
203 /* drop root privs */
204 if (*user) {
205 setuid(pwd->pw_uid);
206 }
207 }
208
209 int max_fd = 0, i=0;
210 // Set stdin to listen_fd
211 close(STDIN_FILENO);
212 dup2(listen_fd, STDIN_FILENO);
213 close(listen_fd);
214 // Set stdout and stderr to dummy fd
215 max_fd = open("/dev/null", O_RDWR);
216 close(STDERR_FILENO);
217 dup2(max_fd, STDERR_FILENO);
218 close(max_fd);
219 max_fd = open("/dev/null", O_RDWR);
220 close(STDOUT_FILENO);
221 dup2(max_fd, STDOUT_FILENO);
222 close(max_fd);
223 // close other handles
224 for(i=3; i<max_fd; i++)
225 close(i);
226 char *b = malloc(strlen("exec ") + strlen(path) + 1);
227 strcpy(b, "exec ");
228 strcat(b, path);
229
230 /* exec the cgi */
231 execl("/bin/sh", "sh", "-c", b, (char *)NULL);
232 exit(errno);
233 break;
234 }
235 case -1:
236 fprintf(stderr, "[fcgi] fork failed\n");
237 return NULL;
238 default:{
239 struct timeval tv = { 0, 100 * 1000 };
240 int status;
241 select(0, NULL, NULL, NULL, &tv);
242 switch(waitpid(ret, &status, WNOHANG)){
243 case 0:
244 printf("[fcg] spawned process %s: %d\n", path, ret);
245 break;
246 case -1:
247 fprintf(stderr, "[fcgi] waitpid failed\n");
248 return NULL;
249 default:
250 if (WIFEXITED(status)) {
251 fprintf(stderr, "[fcgi] child exited with: %d\n", WEXITSTATUS(status));
252 } else if (WIFSIGNALED(status)) {
253 fprintf(stderr, "[fcgi] child signaled: %d\n", WTERMSIG(status));
254 } else {
255 fprintf(stderr, "[fcgi] child died somehow: %d\n", status);
256 }
257 return NULL;
258 }
259 //wait for child process to exit
260 process_fp[idx] = ret;
261 waitpid(ret, &status, 0);
262 process_fp[idx] = 0;
263 }
264 }
265 #endif
266 }
267 }
268
269 static int start_processes()
270 {
271 int i;
272 pthread_attr_t attr;
273 pthread_attr_init(&attr);
274 pthread_attr_setstacksize(&attr, 64*1024); //64KB
275 for(i=0; i<number; i++){
276 if( pthread_create( &threads, &attr, spawn_process, NULL ) == -1 ){
277 fprintf(stderr, "failed to create thread %d\n", i);
278 }
279 }
280
281 for(i=0; i<number; i++){
282 pthread_join(threads, NULL);
283 }
284 return 0;
285 }
286
287 #ifdef __WIN32__
288 void init_win32()
289 {
290 /* init win32 socket */
291 static WSADATA wsa_data;
292 if(WSAStartup((WORD)(1<<8|1), &wsa_data) != 0)
293 exit(1);
294 JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit;
295 FcpJobObject = (HANDLE)CreateJobObject(NULL, NULL);
296 if(FcpJobObject == NULL)
297 exit(1);
298
299 /* let all processes assigned to this job object
300 * being killed when the job object closed */
301 if (!QueryInformationJobObject(FcpJobObject, JobObjectExtendedLimitInformation, &limit, sizeof(limit), NULL)) {
302 CloseHandle(FcpJobObject);
303 exit(1);
304 }
305
306 limit.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
307
308 if (!SetInformationJobObject(FcpJobObject, JobObjectExtendedLimitInformation, &limit, sizeof(limit))) {
309 CloseHandle(FcpJobObject);
310 exit(1);
311 }
312 }
313 #endif //__WIN32__
314
315 #ifndef __WIN32__
316 void before_exit(int sig)
317 {
318 signal(SIGTERM, SIG_DFL);
319 /* call child processes to exit */
320 kill(0, SIGTERM);
321 }
322 #endif
323
324 int main(int argc, char **argv)
325 {
326 prog_name = strrchr(argv[0], '/');
327 if(prog_name == NULL)
328 prog_name = strrchr(argv[0], '\\');
329 if(prog_name == NULL)
330 prog_name = argv[0];
331 else
332 prog_name++;
333
334 if(argc == 1)
335 usage(stderr);
336
337 path = argv[1];
338
339 opterr = 0;
340
341 char* p;
342
343 for(;;){
344 int ch;
345 if((ch = getopt_long(argc, argv, opts, longopts, NULL)) == EOF)
346 break;
347 char *av = argv[optind];
348 switch(ch){
349 case 'h':
350 usage(stdout);
351 break;
352 case 'v':
353 print_version();
354 break;
355 case 'n':
356 number = atoi(av);
357 if(number > MAX_PROCESSES){
358 fprintf(stderr, "exceeds MAX_PROCESSES!\n");
359 number = MAX_PROCESSES;
360 }
361 break;
362 case 'u':
363 user = av;
364 break;
365 case 'r':
366 root = av;
367 break;
368 case 'g':
369 group = av;
370 break;
371 case 'i':
372 ip = av;
373 break;
374 case 'p':
375 port = atoi(av);
376 break;
377 default:
378 usage(stderr);
379 break;
380 }
381 }
382
383 #ifdef __WIN32__
384 init_win32();
385 #else
386 /* call child processes to exit */
387 signal(SIGTERM, before_exit);
388 signal(SIGINT, before_exit);
389 signal(SIGABRT, before_exit);
390 #endif
391
392 int ret;
393 ret = try_to_bind();
394 if(ret != 0)
395 return ret;
396 ret = start_processes();
397 if(ret !=0)
398 return ret;
399
400
401 #ifdef __WIN32__
402 CloseHandle(FcpJobObject);
403 WSACleanup();
404 #endif
405 return 0;
406 }