mirror of
https://github.com/hardkernel/linux.git
synced 2026-04-11 23:48:08 +09:00
496 lines
14 KiB
C++
496 lines
14 KiB
C++
/**
|
|
* Copyright (C) ARM Limited 2010-2014. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "Child.h"
|
|
#include "KMod.h"
|
|
#include "Logging.h"
|
|
#include "OlySocket.h"
|
|
#include "OlyUtility.h"
|
|
#include "SessionData.h"
|
|
|
|
#define DEBUG false
|
|
|
|
extern Child* child;
|
|
static int shutdownFilesystem();
|
|
static pthread_mutex_t numSessions_mutex;
|
|
static int numSessions = 0;
|
|
static OlyServerSocket* sock = NULL;
|
|
static bool driverRunningAtStart = false;
|
|
static bool driverMountedAtStart = false;
|
|
|
|
struct cmdline_t {
|
|
int port;
|
|
char* module;
|
|
};
|
|
|
|
#define DEFAULT_PORT 8080
|
|
|
|
void cleanUp() {
|
|
if (shutdownFilesystem() == -1) {
|
|
logg->logMessage("Error shutting down gator filesystem");
|
|
}
|
|
delete sock;
|
|
delete util;
|
|
delete logg;
|
|
}
|
|
|
|
// CTRL C Signal Handler
|
|
static void handler(int signum) {
|
|
logg->logMessage("Received signal %d, gator daemon exiting", signum);
|
|
|
|
// Case 1: both child and parent receive the signal
|
|
if (numSessions > 0) {
|
|
// Arbitrary sleep of 1 second to give time for the child to exit;
|
|
// if something bad happens, continue the shutdown process regardless
|
|
sleep(1);
|
|
}
|
|
|
|
// Case 2: only the parent received the signal
|
|
if (numSessions > 0) {
|
|
// Kill child threads - the first signal exits gracefully
|
|
logg->logMessage("Killing process group as %d child was running when signal was received", numSessions);
|
|
kill(0, SIGINT);
|
|
|
|
// Give time for the child to exit
|
|
sleep(1);
|
|
|
|
if (numSessions > 0) {
|
|
// The second signal force kills the child
|
|
logg->logMessage("Force kill the child");
|
|
kill(0, SIGINT);
|
|
// Again, sleep for 1 second
|
|
sleep(1);
|
|
|
|
if (numSessions > 0) {
|
|
// Something bad has really happened; the child is not exiting and therefore may hold the /dev/gator resource open
|
|
printf("Unable to kill the gatord child process, thus gator.ko may still be loaded.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanUp();
|
|
exit(0);
|
|
}
|
|
|
|
// Child exit Signal Handler
|
|
static void child_exit(int) {
|
|
int status;
|
|
int pid = wait(&status);
|
|
if (pid != -1) {
|
|
pthread_mutex_lock(&numSessions_mutex);
|
|
numSessions--;
|
|
pthread_mutex_unlock(&numSessions_mutex);
|
|
logg->logMessage("Child process %d exited with status %d", pid, status);
|
|
}
|
|
}
|
|
|
|
static int udpPort(int port) {
|
|
int s;
|
|
struct sockaddr_in6 sockaddr;
|
|
int on;
|
|
int family = AF_INET6;
|
|
|
|
s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (s == -1) {
|
|
family = AF_INET;
|
|
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
if (s == -1) {
|
|
logg->logError(__FILE__, __LINE__, "socket failed");
|
|
handleException();
|
|
}
|
|
}
|
|
|
|
on = 1;
|
|
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) != 0) {
|
|
logg->logError(__FILE__, __LINE__, "setsockopt failed");
|
|
handleException();
|
|
}
|
|
|
|
memset((void*)&sockaddr, 0, sizeof(sockaddr));
|
|
sockaddr.sin6_family = family;
|
|
sockaddr.sin6_port = htons(port);
|
|
sockaddr.sin6_addr = in6addr_any;
|
|
if (bind(s, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
|
|
logg->logError(__FILE__, __LINE__, "socket failed");
|
|
handleException();
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
#define UDP_ANS_PORT 30000
|
|
#define UDP_REQ_PORT 30001
|
|
|
|
typedef struct {
|
|
char rviHeader[8];
|
|
uint32_t messageID;
|
|
uint8_t ethernetAddress[8];
|
|
uint32_t ethernetType;
|
|
uint32_t dhcp;
|
|
char dhcpName[40];
|
|
uint32_t ipAddress;
|
|
uint32_t defaultGateway;
|
|
uint32_t subnetMask;
|
|
uint32_t activeConnections;
|
|
} RVIConfigureInfo;
|
|
|
|
static const char DST_REQ[] = { 'D', 'S', 'T', '_', 'R', 'E', 'Q', ' ', 0, 0, 0, 0x64 };
|
|
|
|
static void* answerThread(void* pVoid) {
|
|
prctl(PR_SET_NAME, (unsigned long)&"gatord-discover", 0, 0, 0);
|
|
const struct cmdline_t * const cmdline = (struct cmdline_t *)pVoid;
|
|
RVIConfigureInfo dstAns;
|
|
int req = udpPort(UDP_REQ_PORT);
|
|
int ans = udpPort(UDP_ANS_PORT);
|
|
|
|
// Format the answer buffer
|
|
memset(&dstAns, 0, sizeof(dstAns));
|
|
memcpy(dstAns.rviHeader, "STR_ANS ", sizeof(dstAns.rviHeader));
|
|
if (gethostname(dstAns.dhcpName, sizeof(dstAns.dhcpName) - 1) != 0) {
|
|
logg->logError(__FILE__, __LINE__, "gethostname failed");
|
|
handleException();
|
|
}
|
|
// Subvert the defaultGateway field for the port number
|
|
if (cmdline->port != DEFAULT_PORT) {
|
|
dstAns.defaultGateway = cmdline->port;
|
|
}
|
|
// Subvert the subnetMask field for the protocol version
|
|
dstAns.subnetMask = PROTOCOL_VERSION;
|
|
|
|
for (;;) {
|
|
char buf[128];
|
|
struct sockaddr_in6 sockaddr;
|
|
socklen_t addrlen;
|
|
int read;
|
|
addrlen = sizeof(sockaddr);
|
|
read = recvfrom(req, &buf, sizeof(buf), 0, (struct sockaddr *)&sockaddr, &addrlen);
|
|
if (read < 0) {
|
|
logg->logError(__FILE__, __LINE__, "recvfrom failed");
|
|
handleException();
|
|
} else if ((read == 12) && (memcmp(buf, DST_REQ, sizeof(DST_REQ)) == 0)) {
|
|
if (sendto(ans, &dstAns, sizeof(dstAns), 0, (struct sockaddr *)&sockaddr, addrlen) != sizeof(dstAns)) {
|
|
logg->logError(__FILE__, __LINE__, "sendto failed");
|
|
handleException();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// retval: -1 = failure; 0 = was already mounted; 1 = successfully mounted
|
|
static int mountGatorFS() {
|
|
// If already mounted,
|
|
if (access("/dev/gator/buffer", F_OK) == 0) {
|
|
return 0;
|
|
}
|
|
|
|
// else, mount the filesystem
|
|
mkdir("/dev/gator", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
|
if (mount("nodev", "/dev/gator", "gatorfs", 0, NULL) != 0) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static bool init_module (const char * const location) {
|
|
bool ret(false);
|
|
const int fd = open(location, O_RDONLY);
|
|
if (fd >= 0) {
|
|
struct stat st;
|
|
if (fstat(fd, &st) == 0) {
|
|
void * const p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if (p != MAP_FAILED) {
|
|
if (syscall(__NR_init_module, p, st.st_size, "") == 0) {
|
|
ret = true;
|
|
}
|
|
munmap(p, st.st_size);
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool setupFilesystem(char* module) {
|
|
if (module) {
|
|
// unmount and rmmod if the module was specified on the commandline, i.e. ensure that the specified module is indeed running
|
|
shutdownFilesystem();
|
|
|
|
// if still mounted
|
|
if (access("/dev/gator/buffer", F_OK) == 0) {
|
|
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");
|
|
handleException();
|
|
}
|
|
}
|
|
|
|
const int retval = mountGatorFS();
|
|
if (retval == 1) {
|
|
logg->logMessage("Driver already running at startup");
|
|
driverRunningAtStart = true;
|
|
} else if (retval == 0) {
|
|
logg->logMessage("Driver already mounted at startup");
|
|
driverRunningAtStart = driverMountedAtStart = true;
|
|
} else {
|
|
char command[256]; // arbitrarily large amount
|
|
char location[256]; // arbitrarily large amount
|
|
|
|
if (module) {
|
|
strncpy(location, module, sizeof(location));
|
|
} else {
|
|
// Is the driver co-located in the same directory?
|
|
if (util->getApplicationFullPath(location, sizeof(location)) != 0) { // allow some buffer space
|
|
logg->logMessage("Unable to determine the full path of gatord, the cwd will be used");
|
|
}
|
|
strncat(location, "gator.ko", sizeof(location) - strlen(location) - 1);
|
|
}
|
|
|
|
if (access(location, F_OK) == -1) {
|
|
// The gator kernel is not already loaded and unable to locate gator.ko
|
|
return false;
|
|
}
|
|
|
|
// Load driver
|
|
bool success = init_module(location);
|
|
if (!success) {
|
|
logg->logMessage("init_module failed, trying insmod");
|
|
snprintf(command, sizeof(command), "insmod %s >/dev/null 2>&1", location);
|
|
if (system(command) != 0) {
|
|
logg->logMessage("Unable to load gator.ko driver with command: %s", command);
|
|
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");
|
|
handleException();
|
|
}
|
|
}
|
|
|
|
if (mountGatorFS() == -1) {
|
|
logg->logError(__FILE__, __LINE__, "Unable to mount the gator filesystem needed for profiling.");
|
|
handleException();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int shutdownFilesystem() {
|
|
if (driverMountedAtStart == false) {
|
|
umount("/dev/gator");
|
|
}
|
|
if (driverRunningAtStart == false) {
|
|
if (syscall(__NR_delete_module, "gator", O_NONBLOCK) != 0) {
|
|
logg->logMessage("delete_module failed, trying rmmod");
|
|
if (system("rmmod gator >/dev/null 2>&1") != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
static struct cmdline_t parseCommandLine(int argc, char** argv) {
|
|
struct cmdline_t cmdline;
|
|
cmdline.port = DEFAULT_PORT;
|
|
cmdline.module = NULL;
|
|
char version_string[256]; // arbitrary length to hold the version information
|
|
int c;
|
|
|
|
// build the version string
|
|
if (PROTOCOL_VERSION < PROTOCOL_DEV) {
|
|
snprintf(version_string, sizeof(version_string), "Streamline gatord version %d (DS-5 v5.%d)", PROTOCOL_VERSION, PROTOCOL_VERSION);
|
|
} else {
|
|
snprintf(version_string, sizeof(version_string), "Streamline gatord development version %d", PROTOCOL_VERSION);
|
|
}
|
|
|
|
while ((c = getopt(argc, argv, "hvp:s:c:e:m:o:")) != -1) {
|
|
switch(c) {
|
|
case 'c':
|
|
gSessionData->mConfigurationXMLPath = optarg;
|
|
break;
|
|
case 'e':
|
|
gSessionData->mEventsXMLPath = optarg;
|
|
break;
|
|
case 'm':
|
|
cmdline.module = optarg;
|
|
break;
|
|
case 'p':
|
|
cmdline.port = strtol(optarg, NULL, 10);
|
|
break;
|
|
case 's':
|
|
gSessionData->mSessionXMLPath = optarg;
|
|
break;
|
|
case 'o':
|
|
gSessionData->mTargetPath = optarg;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
logg->logError(__FILE__, __LINE__,
|
|
"%s. All parameters are optional:\n"
|
|
"-c config_xml path and filename of the configuration.xml to use\n"
|
|
"-e events_xml path and filename of the events.xml to use\n"
|
|
"-h this help page\n"
|
|
"-m module path and filename of gator.ko\n"
|
|
"-p port_number port upon which the server listens; default is 8080\n"
|
|
"-s session_xml path and filename of a session xml used for local capture\n"
|
|
"-o apc_dir path and name of the output for a local capture\n"
|
|
"-v version information\n"
|
|
, version_string);
|
|
handleException();
|
|
break;
|
|
case 'v':
|
|
logg->logError(__FILE__, __LINE__, version_string);
|
|
handleException();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Error checking
|
|
if (cmdline.port != DEFAULT_PORT && gSessionData->mSessionXMLPath != NULL) {
|
|
logg->logError(__FILE__, __LINE__, "Only a port or a session xml can be specified, not both");
|
|
handleException();
|
|
}
|
|
|
|
if (gSessionData->mTargetPath != NULL && gSessionData->mSessionXMLPath == NULL) {
|
|
logg->logError(__FILE__, __LINE__, "Missing -s command line option required for a local capture.");
|
|
handleException();
|
|
}
|
|
|
|
if (optind < argc) {
|
|
logg->logError(__FILE__, __LINE__, "Unknown argument: %s. Use '-h' for help.", argv[optind]);
|
|
handleException();
|
|
}
|
|
|
|
return cmdline;
|
|
}
|
|
|
|
// Gator data flow: collector -> collector fifo -> sender
|
|
int main(int argc, char** argv) {
|
|
// Ensure proper signal handling by making gatord the process group leader
|
|
// e.g. it may not be the group leader when launched as 'sudo gatord'
|
|
setsid();
|
|
|
|
logg = new Logging(DEBUG); // Set up global thread-safe logging
|
|
gSessionData = new SessionData(); // Global data class
|
|
util = new OlyUtility(); // Set up global utility class
|
|
|
|
// Initialize drivers
|
|
new KMod();
|
|
|
|
prctl(PR_SET_NAME, (unsigned long)&"gatord-main", 0, 0, 0);
|
|
pthread_mutex_init(&numSessions_mutex, NULL);
|
|
|
|
signal(SIGINT, handler);
|
|
signal(SIGTERM, handler);
|
|
signal(SIGABRT, handler);
|
|
|
|
// Set to high priority
|
|
if (setpriority(PRIO_PROCESS, syscall(__NR_gettid), -19) == -1) {
|
|
logg->logMessage("setpriority() failed");
|
|
}
|
|
|
|
// Parse the command line parameters
|
|
struct cmdline_t cmdline = parseCommandLine(argc, argv);
|
|
|
|
// Verify root permissions
|
|
uid_t euid = geteuid();
|
|
if (euid) {
|
|
logg->logError(__FILE__, __LINE__, "gatord must be launched with root privileges");
|
|
handleException();
|
|
}
|
|
|
|
// Call before setting up the SIGCHLD handler, as system() spawns child processes
|
|
if (!setupFilesystem(cmdline.module)) {
|
|
logg->logMessage("Unable to setup gatorfs, trying perf");
|
|
if (!gSessionData->perf.setup()) {
|
|
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\n"
|
|
" >>> OR run Linux 3.12 or later with perf support to collect data via userspace only");
|
|
handleException();
|
|
}
|
|
}
|
|
|
|
gSessionData->hwmon.setup();
|
|
|
|
// Handle child exit codes
|
|
signal(SIGCHLD, child_exit);
|
|
|
|
// Ignore the SIGPIPE signal so that any send to a broken socket will return an error code instead of asserting a signal
|
|
// Handling the error at the send function call is much easier than trying to do anything intelligent in the sig handler
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
// If the command line argument is a session xml file, no need to open a socket
|
|
if (gSessionData->mSessionXMLPath) {
|
|
child = new Child();
|
|
child->run();
|
|
delete child;
|
|
} else {
|
|
pthread_t answerThreadID;
|
|
if (pthread_create(&answerThreadID, NULL, answerThread, &cmdline)) {
|
|
logg->logError(__FILE__, __LINE__, "Failed to create answer thread");
|
|
handleException();
|
|
}
|
|
sock = new OlyServerSocket(cmdline.port);
|
|
// Forever loop, can be exited via a signal or exception
|
|
while (1) {
|
|
logg->logMessage("Waiting on connection...");
|
|
OlySocket client(sock->acceptConnection());
|
|
|
|
int pid = fork();
|
|
if (pid < 0) {
|
|
// Error
|
|
logg->logError(__FILE__, __LINE__, "Fork process failed. Please power cycle the target device if this error persists.");
|
|
} else if (pid == 0) {
|
|
// Child
|
|
sock->closeServerSocket();
|
|
child = new Child(&client, numSessions + 1);
|
|
child->run();
|
|
delete child;
|
|
exit(0);
|
|
} else {
|
|
// Parent
|
|
client.closeSocket();
|
|
|
|
pthread_mutex_lock(&numSessions_mutex);
|
|
numSessions++;
|
|
pthread_mutex_unlock(&numSessions_mutex);
|
|
|
|
// Maximum number of connections is 2
|
|
int wait = 0;
|
|
while (numSessions > 1) {
|
|
// Throttle until one of the children exits before continuing to accept another socket connection
|
|
logg->logMessage("%d sessions active!", numSessions);
|
|
if (wait++ >= 10) { // Wait no more than 10 seconds
|
|
// Kill last created child
|
|
kill(pid, SIGALRM);
|
|
break;
|
|
}
|
|
sleep(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanUp();
|
|
return 0;
|
|
}
|