1 /**
2 * Copyright (C) ARM Limited 2010-2012. 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 <stdint.h>
10 #include <stdlib.h>
11 #include <signal.h>
12 #include <ctype.h>
13 #include <sys/wait.h>
14 #include <unistd.h>
15 #include <sys/syscall.h>
16 #include <sys/prctl.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <sys/mount.h>
20 #include "Child.h"
21 #include "SessionData.h"
22 #include "OlySocket.h"
23 #include "Logging.h"
24 #include "OlyUtility.h"
26 #define DEBUG false
28 extern Child* child;
29 extern void handleException();
30 int shutdownFilesystem();
31 static pthread_mutex_t numSessions_mutex;
32 static int numSessions = 0;
33 static OlySocket* socket = NULL;
34 static bool driverRunningAtStart = false;
35 static bool driverMountedAtStart = false;
37 struct cmdline_t {
38 int port;
39 char* module;
40 };
42 void cleanUp() {
43 if (shutdownFilesystem() == -1) {
44 logg->logMessage("Error shutting down gator filesystem");
45 }
46 delete socket;
47 delete util;
48 delete logg;
49 }
51 // CTRL C Signal Handler
52 void handler(int signum) {
53 logg->logMessage("Received signal %d, gator daemon exiting", signum);
55 // Case 1: both child and parent receive the signal
56 if (numSessions > 0) {
57 // Arbitrary sleep of 1 second to give time for the child to exit;
58 // if something bad happens, continue the shutdown process regardless
59 sleep(1);
60 }
62 // Case 2: only the parent received the signal
63 if (numSessions > 0) {
64 // Kill child threads - the first signal exits gracefully
65 logg->logMessage("Killing process group as %d child was running when signal was received", numSessions);
66 kill(0, SIGINT);
68 // Give time for the child to exit
69 sleep(1);
71 if (numSessions > 0) {
72 // The second signal force kills the child
73 logg->logMessage("Force kill the child");
74 kill(0, SIGINT);
75 // Again, sleep for 1 second
76 sleep(1);
78 if (numSessions > 0) {
79 // Something bad has really happened; the child is not exiting and therefore may hold the /dev/gator resource open
80 printf("Unable to kill the gatord child process, thus gator.ko may still be loaded.\n");
81 }
82 }
83 }
85 cleanUp();
86 exit(0);
87 }
89 // Child exit Signal Handler
90 void child_exit(int signum) {
91 int status;
92 int pid = wait(&status);
93 if (pid != -1) {
94 pthread_mutex_lock(&numSessions_mutex);
95 numSessions--;
96 pthread_mutex_unlock(&numSessions_mutex);
97 logg->logMessage("Child process %d exited with status %d", pid, status);
98 }
99 }
101 // retval: -1 = failure; 0 = was already mounted; 1 = successfully mounted
102 int mountGatorFS() {
103 // If already mounted,
104 if (access("/dev/gator/buffer", F_OK) == 0) {
105 return 0;
106 }
108 // else, mount the filesystem
109 mkdir("/dev/gator", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
110 if (mount("nodev", "/dev/gator", "gatorfs", 0, NULL) != 0) {
111 return -1;
112 } else {
113 return 1;
114 }
115 }
117 int setupFilesystem(char* module) {
118 int retval;
120 // Verify root permissions
121 uid_t euid = geteuid();
122 if (euid) {
123 logg->logError(__FILE__, __LINE__, "gatord must be launched with root privileges");
124 handleException();
125 }
127 if (module) {
128 // unmount and rmmod if the module was specified on the commandline, i.e. ensure that the specified module is indeed running
129 shutdownFilesystem();
131 // if still mounted
132 if (access("/dev/gator/buffer", F_OK) == 0) {
133 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");
134 handleException();
135 }
136 }
138 retval = mountGatorFS();
139 if (retval == 1) {
140 logg->logMessage("Driver already running at startup");
141 driverRunningAtStart = true;
142 } else if (retval == 0) {
143 logg->logMessage("Driver already mounted at startup");
144 driverRunningAtStart = driverMountedAtStart = true;
145 } else {
146 char command[256]; // arbitrarily large amount
147 char location[256]; // arbitrarily large amount
149 if (module) {
150 strncpy(location, module, sizeof(location));
151 } else {
152 // Is the driver co-located in the same directory?
153 if (util->getApplicationFullPath(location, sizeof(location)) != 0) { // allow some buffer space
154 logg->logMessage("Unable to determine the full path of gatord, the cwd will be used");
155 }
156 strncat(location, "gator.ko", sizeof(location) - strlen(location) - 1);
157 }
159 if (access(location, F_OK) == -1) {
160 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");
161 handleException();
162 }
164 // Load driver
165 snprintf(command, sizeof(command), "insmod %s >/dev/null 2>&1", location);
166 if (system(command) != 0) {
167 logg->logMessage("Unable to load gator.ko driver with command: %s", command);
168 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");
169 handleException();
170 }
172 if (mountGatorFS() == -1) {
173 logg->logError(__FILE__, __LINE__, "Unable to mount the gator filesystem needed for profiling.");
174 handleException();
175 }
176 }
178 return 0;
179 }
181 int shutdownFilesystem() {
182 if (driverMountedAtStart == false) {
183 umount("/dev/gator");
184 }
185 if (driverRunningAtStart == false) {
186 if (system("rmmod gator >/dev/null 2>&1") != 0) {
187 return -1;
188 }
189 }
191 return 0; // success
192 }
194 struct cmdline_t parseCommandLine(int argc, char** argv) {
195 struct cmdline_t cmdline;
196 cmdline.port = 8080;
197 cmdline.module = NULL;
198 int c;
200 while ((c = getopt(argc, argv, "hvp:s:c:e:m:")) != -1) {
201 switch(c) {
202 case 'c':
203 gSessionData->mConfigurationXMLPath = optarg;
204 break;
205 case 'e':
206 gSessionData->mEventsXMLPath = optarg;
207 break;
208 case 'm':
209 cmdline.module = optarg;
210 break;
211 case 'p':
212 cmdline.port = strtol(optarg, NULL, 10);
213 break;
214 case 's':
215 gSessionData->mSessionXMLPath = optarg;
216 break;
217 case 'h':
218 case '?':
219 logg->logError(__FILE__, __LINE__,
220 "Streamline gatord version %d. All parameters are optional:\n"
221 "-c config_xml\tpath and filename of the configuration.xml to use\n"
222 "-e events_xml\tpath and filename of the events.xml to use\n"
223 "-h\t\tthis help page\n"
224 "-m module\tpath and filename of gator.ko\n"
225 "-p port_number\tport upon which the server listens; default is 8080\n"
226 "-s session_xml\tpath and filename of a session xml used for local capture\n"
227 "-v\t\tversion information\n"
228 , PROTOCOL_VERSION);
229 handleException();
230 break;
231 case 'v':
232 logg->logError(__FILE__, __LINE__, "Streamline gatord version %d", PROTOCOL_VERSION);
233 handleException();
234 break;
235 }
236 }
238 // Error checking
239 if (cmdline.port != 8080 && gSessionData->mSessionXMLPath != NULL) {
240 logg->logError(__FILE__, __LINE__, "Only a port or a session xml can be specified, not both");
241 handleException();
242 }
244 if (optind < argc) {
245 logg->logError(__FILE__, __LINE__, "Unknown argument: %s. Use '-h' for help.", argv[optind]);
246 handleException();
247 }
249 return cmdline;
250 }
252 // Gator data flow: collector -> collector fifo -> sender
253 int main(int argc, char** argv, char* envp[]) {
254 gSessionData = new SessionData(); // Global data class
255 logg = new Logging(DEBUG); // Set up global thread-safe logging
256 util = new OlyUtility(); // Set up global utility class
258 prctl(PR_SET_NAME, (unsigned int)&"gatord-main", 0, 0, 0);
259 pthread_mutex_init(&numSessions_mutex, NULL);
261 signal(SIGINT, handler);
262 signal(SIGTERM, handler);
263 signal(SIGABRT, handler);
265 // Set to high priority
266 if (setpriority(PRIO_PROCESS, syscall(__NR_gettid), -19) == -1) {
267 logg->logMessage("setpriority() failed");
268 }
270 // Initialize session data
271 gSessionData->initialize();
273 // Parse the command line parameters
274 struct cmdline_t cmdline = parseCommandLine(argc, argv);
276 // Call before setting up the SIGCHLD handler, as system() spawns child processes
277 setupFilesystem(cmdline.module);
279 // Handle child exit codes
280 signal(SIGCHLD, child_exit);
282 // Ignore the SIGPIPE signal so that any send to a broken socket will return an error code instead of asserting a signal
283 // Handling the error at the send function call is much easier than trying to do anything intelligent in the sig handler
284 signal(SIGPIPE, SIG_IGN);
286 // If the command line argument is a session xml file, no need to open a socket
287 if (gSessionData->mSessionXMLPath) {
288 child = new Child();
289 child->run();
290 delete child;
291 } else {
292 socket = new OlySocket(cmdline.port, true);
293 // Forever loop, can be exited via a signal or exception
294 while (1) {
295 logg->logMessage("Waiting on connection...");
296 socket->acceptConnection();
298 int pid = fork();
299 if (pid < 0) {
300 // Error
301 logg->logError(__FILE__, __LINE__, "Fork process failed. Please power cycle the target device if this error persists.");
302 } else if (pid == 0) {
303 // Child
304 socket->closeServerSocket();
305 child = new Child(socket, numSessions + 1);
306 child->run();
307 delete child;
308 exit(0);
309 } else {
310 // Parent
311 socket->closeSocket();
313 pthread_mutex_lock(&numSessions_mutex);
314 numSessions++;
315 pthread_mutex_unlock(&numSessions_mutex);
317 // Maximum number of connections is 2
318 int wait = 0;
319 while (numSessions > 1) {
320 // Throttle until one of the children exits before continuing to accept another socket connection
321 logg->logMessage("%d sessions active!", numSessions);
322 if (wait++ >= 10) { // Wait no more than 10 seconds
323 // Kill last created child
324 kill(pid, SIGALRM);
325 break;
326 }
327 sleep(1);
328 }
329 }
330 }
331 }
333 cleanUp();
334 return 0;
335 }