1 /**
2 * Copyright (C) ARM Limited 2010-2013. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
9 #include <stdlib.h>
10 #include <signal.h>
11 #include <sys/wait.h>
12 #include <unistd.h>
13 #include <sys/syscall.h>
14 #include <sys/prctl.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <sys/mount.h>
18 #include <fcntl.h>
19 #include <sys/mman.h>
20 #include <sys/time.h>
21 #include <sys/resource.h>
22 #include "Child.h"
23 #include "SessionData.h"
24 #include "OlySocket.h"
25 #include "Logging.h"
26 #include "OlyUtility.h"
27 #include "KMod.h"
29 #define DEBUG false
31 extern Child* child;
32 static int shutdownFilesystem();
33 static pthread_mutex_t numSessions_mutex;
34 static int numSessions = 0;
35 static OlySocket* socket = NULL;
36 static bool driverRunningAtStart = false;
37 static bool driverMountedAtStart = false;
39 struct cmdline_t {
40 int port;
41 char* module;
42 };
44 void cleanUp() {
45 if (shutdownFilesystem() == -1) {
46 logg->logMessage("Error shutting down gator filesystem");
47 }
48 delete socket;
49 delete util;
50 delete logg;
51 }
53 // CTRL C Signal Handler
54 static void handler(int signum) {
55 logg->logMessage("Received signal %d, gator daemon exiting", signum);
57 // Case 1: both child and parent receive the signal
58 if (numSessions > 0) {
59 // Arbitrary sleep of 1 second to give time for the child to exit;
60 // if something bad happens, continue the shutdown process regardless
61 sleep(1);
62 }
64 // Case 2: only the parent received the signal
65 if (numSessions > 0) {
66 // Kill child threads - the first signal exits gracefully
67 logg->logMessage("Killing process group as %d child was running when signal was received", numSessions);
68 kill(0, SIGINT);
70 // Give time for the child to exit
71 sleep(1);
73 if (numSessions > 0) {
74 // The second signal force kills the child
75 logg->logMessage("Force kill the child");
76 kill(0, SIGINT);
77 // Again, sleep for 1 second
78 sleep(1);
80 if (numSessions > 0) {
81 // Something bad has really happened; the child is not exiting and therefore may hold the /dev/gator resource open
82 printf("Unable to kill the gatord child process, thus gator.ko may still be loaded.\n");
83 }
84 }
85 }
87 cleanUp();
88 exit(0);
89 }
91 // Child exit Signal Handler
92 static void child_exit(int signum) {
93 int status;
94 int pid = wait(&status);
95 if (pid != -1) {
96 pthread_mutex_lock(&numSessions_mutex);
97 numSessions--;
98 pthread_mutex_unlock(&numSessions_mutex);
99 logg->logMessage("Child process %d exited with status %d", pid, status);
100 }
101 }
103 // retval: -1 = failure; 0 = was already mounted; 1 = successfully mounted
104 static int mountGatorFS() {
105 // If already mounted,
106 if (access("/dev/gator/buffer", F_OK) == 0) {
107 return 0;
108 }
110 // else, mount the filesystem
111 mkdir("/dev/gator", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
112 if (mount("nodev", "/dev/gator", "gatorfs", 0, NULL) != 0) {
113 return -1;
114 } else {
115 return 1;
116 }
117 }
119 static bool init_module (const char * const location) {
120 bool ret(false);
121 const int fd = open(location, O_RDONLY);
122 if (fd >= 0) {
123 struct stat st;
124 if (fstat(fd, &st) == 0) {
125 void * const p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
126 if (p != MAP_FAILED) {
127 if (syscall(__NR_init_module, p, st.st_size, "") == 0) {
128 ret = true;
129 }
130 munmap(p, st.st_size);
131 }
132 }
133 close(fd);
134 }
136 return ret;
137 }
139 static int setupFilesystem(char* module) {
140 int retval;
142 // Verify root permissions
143 uid_t euid = geteuid();
144 if (euid) {
145 logg->logError(__FILE__, __LINE__, "gatord must be launched with root privileges");
146 handleException();
147 }
149 if (module) {
150 // unmount and rmmod if the module was specified on the commandline, i.e. ensure that the specified module is indeed running
151 shutdownFilesystem();
153 // if still mounted
154 if (access("/dev/gator/buffer", F_OK) == 0) {
155 logg->logError(__FILE__, __LINE__, "Unable to remove the running gator.ko. Manually remove the module or use the running module by not specifying one on the commandline");
156 handleException();
157 }
158 }
160 retval = mountGatorFS();
161 if (retval == 1) {
162 logg->logMessage("Driver already running at startup");
163 driverRunningAtStart = true;
164 } else if (retval == 0) {
165 logg->logMessage("Driver already mounted at startup");
166 driverRunningAtStart = driverMountedAtStart = true;
167 } else {
168 char command[256]; // arbitrarily large amount
169 char location[256]; // arbitrarily large amount
171 if (module) {
172 strncpy(location, module, sizeof(location));
173 } else {
174 // Is the driver co-located in the same directory?
175 if (util->getApplicationFullPath(location, sizeof(location)) != 0) { // allow some buffer space
176 logg->logMessage("Unable to determine the full path of gatord, the cwd will be used");
177 }
178 strncat(location, "gator.ko", sizeof(location) - strlen(location) - 1);
179 }
181 if (access(location, F_OK) == -1) {
182 logg->logError(__FILE__, __LINE__, "Unable to locate gator.ko driver:\n >>> gator.ko should be co-located with gatord in the same directory\n >>> OR insmod gator.ko prior to launching gatord\n >>> OR specify the location of gator.ko on the command line");
183 handleException();
184 }
186 // Load driver
187 bool success = init_module(location);
188 if (!success) {
189 logg->logMessage("init_module failed, trying insmod");
190 snprintf(command, sizeof(command), "insmod %s >/dev/null 2>&1", location);
191 if (system(command) != 0) {
192 logg->logMessage("Unable to load gator.ko driver with command: %s", command);
193 logg->logError(__FILE__, __LINE__, "Unable to load (insmod) gator.ko driver:\n >>> gator.ko must be built against the current kernel version & configuration\n >>> See dmesg for more details");
194 handleException();
195 }
196 }
198 if (mountGatorFS() == -1) {
199 logg->logError(__FILE__, __LINE__, "Unable to mount the gator filesystem needed for profiling.");
200 handleException();
201 }
202 }
204 return 0;
205 }
207 static int shutdownFilesystem() {
208 if (driverMountedAtStart == false) {
209 umount("/dev/gator");
210 }
211 if (driverRunningAtStart == false) {
212 if (syscall(__NR_delete_module, "gator", O_NONBLOCK) != 0) {
213 logg->logMessage("delete_module failed, trying rmmod");
214 if (system("rmmod gator >/dev/null 2>&1") != 0) {
215 return -1;
216 }
217 }
218 }
220 return 0; // success
221 }
223 static struct cmdline_t parseCommandLine(int argc, char** argv) {
224 struct cmdline_t cmdline;
225 cmdline.port = 8080;
226 cmdline.module = NULL;
227 char version_string[256]; // arbitrary length to hold the version information
228 int c;
230 // build the version string
231 if (PROTOCOL_VERSION < PROTOCOL_DEV) {
232 snprintf(version_string, sizeof(version_string), "Streamline gatord version %d (DS-5 v5.%d)", PROTOCOL_VERSION, PROTOCOL_VERSION + 1);
233 } else {
234 snprintf(version_string, sizeof(version_string), "Streamline gatord development version %d", PROTOCOL_VERSION);
235 }
237 while ((c = getopt(argc, argv, "hvp:s:c:e:m:o:")) != -1) {
238 switch(c) {
239 case 'c':
240 gSessionData->mConfigurationXMLPath = optarg;
241 break;
242 case 'e':
243 gSessionData->mEventsXMLPath = optarg;
244 break;
245 case 'm':
246 cmdline.module = optarg;
247 break;
248 case 'p':
249 cmdline.port = strtol(optarg, NULL, 10);
250 break;
251 case 's':
252 gSessionData->mSessionXMLPath = optarg;
253 break;
254 case 'o':
255 gSessionData->mTargetPath = optarg;
256 break;
257 case 'h':
258 case '?':
259 logg->logError(__FILE__, __LINE__,
260 "%s. All parameters are optional:\n"
261 "-c config_xml path and filename of the configuration.xml to use\n"
262 "-e events_xml path and filename of the events.xml to use\n"
263 "-h this help page\n"
264 "-m module path and filename of gator.ko\n"
265 "-p port_number port upon which the server listens; default is 8080\n"
266 "-s session_xml path and filename of a session xml used for local capture\n"
267 "-o apc_dir path and name of the output for a local capture\n"
268 "-v version information\n"
269 , version_string);
270 handleException();
271 break;
272 case 'v':
273 logg->logError(__FILE__, __LINE__, version_string);
274 handleException();
275 break;
276 }
277 }
279 // Error checking
280 if (cmdline.port != 8080 && gSessionData->mSessionXMLPath != NULL) {
281 logg->logError(__FILE__, __LINE__, "Only a port or a session xml can be specified, not both");
282 handleException();
283 }
285 if (gSessionData->mTargetPath != NULL && gSessionData->mSessionXMLPath == NULL) {
286 logg->logError(__FILE__, __LINE__, "Missing -s command line option required for a local capture.");
287 handleException();
288 }
290 if (optind < argc) {
291 logg->logError(__FILE__, __LINE__, "Unknown argument: %s. Use '-h' for help.", argv[optind]);
292 handleException();
293 }
295 return cmdline;
296 }
298 // Gator data flow: collector -> collector fifo -> sender
299 int main(int argc, char** argv, char* envp[]) {
300 // Ensure proper signal handling by making gatord the process group leader
301 // e.g. it may not be the group leader when launched as 'sudo gatord'
302 setsid();
304 logg = new Logging(DEBUG); // Set up global thread-safe logging
305 gSessionData = new SessionData(); // Global data class
306 util = new OlyUtility(); // Set up global utility class
308 // Initialize drivers
309 new KMod();
311 prctl(PR_SET_NAME, (unsigned long)&"gatord-main", 0, 0, 0);
312 pthread_mutex_init(&numSessions_mutex, NULL);
314 signal(SIGINT, handler);
315 signal(SIGTERM, handler);
316 signal(SIGABRT, handler);
318 // Set to high priority
319 if (setpriority(PRIO_PROCESS, syscall(__NR_gettid), -19) == -1) {
320 logg->logMessage("setpriority() failed");
321 }
323 // Parse the command line parameters
324 struct cmdline_t cmdline = parseCommandLine(argc, argv);
326 // Call before setting up the SIGCHLD handler, as system() spawns child processes
327 setupFilesystem(cmdline.module);
329 // Handle child exit codes
330 signal(SIGCHLD, child_exit);
332 // Ignore the SIGPIPE signal so that any send to a broken socket will return an error code instead of asserting a signal
333 // Handling the error at the send function call is much easier than trying to do anything intelligent in the sig handler
334 signal(SIGPIPE, SIG_IGN);
336 // If the command line argument is a session xml file, no need to open a socket
337 if (gSessionData->mSessionXMLPath) {
338 child = new Child();
339 child->run();
340 delete child;
341 } else {
342 socket = new OlySocket(cmdline.port, true);
343 // Forever loop, can be exited via a signal or exception
344 while (1) {
345 logg->logMessage("Waiting on connection...");
346 socket->acceptConnection();
348 int pid = fork();
349 if (pid < 0) {
350 // Error
351 logg->logError(__FILE__, __LINE__, "Fork process failed. Please power cycle the target device if this error persists.");
352 } else if (pid == 0) {
353 // Child
354 socket->closeServerSocket();
355 child = new Child(socket, numSessions + 1);
356 child->run();
357 delete child;
358 exit(0);
359 } else {
360 // Parent
361 socket->closeSocket();
363 pthread_mutex_lock(&numSessions_mutex);
364 numSessions++;
365 pthread_mutex_unlock(&numSessions_mutex);
367 // Maximum number of connections is 2
368 int wait = 0;
369 while (numSessions > 1) {
370 // Throttle until one of the children exits before continuing to accept another socket connection
371 logg->logMessage("%d sessions active!", numSessions);
372 if (wait++ >= 10) { // Wait no more than 10 seconds
373 // Kill last created child
374 kill(pid, SIGALRM);
375 break;
376 }
377 sleep(1);
378 }
379 }
380 }
381 }
383 cleanUp();
384 return 0;
385 }