meta-facebook/meta-wedge/recipes-wedge/bmc-log/files/bmc-log.c (417 lines of code) (raw):

/* * Copyright 2014-present Facebook. 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 as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <stdbool.h> #include <unistd.h> #include <string.h> #include <linux/serial.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/param.h> #include <time.h> #include <arpa/inet.h> #include <sys/stat.h> #include <pty.h> #include <errno.h> #include <signal.h> #include <netinet/in.h> #include <netdb.h> #include "bmc-log.h" FILE *error_file = NULL; bool kill_received = false; // To check if a killing interrupt is received speed_t baud_rate = B57600; // Default baud rate - change if user inputs a different one int fd_tty = -1, fd_soc = -1; /* Hostname and port of the server */ char *hostname; int port; struct termios orig_tty_state; char *get_time() { static char mytime[TIME_FORMAT_SIZE]; memset(mytime, 0, sizeof(mytime)); time_t this_time; struct tm *this_tm; this_time = time(NULL); this_tm = localtime(&this_time); snprintf(mytime, sizeof(mytime), "%04d-%02d-%02d %02d:%02d:%02d", 1900 + this_tm->tm_year, this_tm->tm_mon + 1, this_tm->tm_mday, this_tm->tm_hour, this_tm->tm_min, this_tm->tm_sec); return mytime; } void errlog(char *frmt, ...) { va_list args; va_start(args, frmt); struct stat st; char *time_now = get_time(); fprintf(stderr, "[%s] ", time_now); vfprintf(stderr, frmt, args); if (error_file) { stat(error_log_file, &st); if (st.st_size >= MAX_LOG_FILE_SIZE) { truncate(error_log_file, 0); } fprintf(error_file, "[%s] ", time_now); vfprintf(error_file, frmt, args); fflush(error_file); } } /* Get the address info of netcons server */ struct addrinfo *get_addr_info(int ip_version) { int ip_family = (ip_version == IPV4) ? AF_INET : AF_INET6; struct addrinfo hints; struct addrinfo *result; result = malloc(sizeof(*result)); if (!result) { errlog("Error: Unable to allocate memory - %m\n"); return NULL; } memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = ip_family; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; if (getaddrinfo(hostname, NULL, &hints, &result)) { errlog("Error: getaddrinfo failed - %m\n"); return NULL; } return result; } /* Prepare Ipv4 Socket */ bool prepare_sock(struct sockaddr_in * tgt_addr) { struct addrinfo *addr_info; memset(tgt_addr, 0, sizeof(*tgt_addr)); if ((addr_info = get_addr_info(IPV4)) == NULL) { errlog("Error: Unable to get address info\n"); return false; } tgt_addr->sin_addr = ((struct sockaddr_in *)addr_info->ai_addr)->sin_addr; tgt_addr->sin_port = htons(port); tgt_addr->sin_family = AF_INET; return true; } /* Prepare Ipv6 Socket */ bool prepare_sock6(struct sockaddr_in6 * tgt_addr6) { struct addrinfo *addr_info; memset(tgt_addr6, 0, sizeof(*tgt_addr6)); if ((addr_info = get_addr_info(IPV6)) == NULL) { errlog("Erorr: Unable to get address info\n"); return false; } tgt_addr6->sin6_addr = ((struct sockaddr_in6 *)addr_info->ai_addr)->sin6_addr; tgt_addr6->sin6_port = htons(port); tgt_addr6->sin6_family = AF_INET6; return true; } /* Set TTY to raw mode */ bool set_tty(int fd) { struct termios tty_state; if (tcgetattr(fd, &tty_state) < 0) { return false; } if (tcgetattr(fd, &orig_tty_state) < 0) // Save original settings { return false; } if (cfsetspeed(&tty_state, baud_rate) == -1) { errlog("Error: Baud Rate not set - %m\n"); return false; } tty_state.c_lflag &= ~(ICANON | IEXTEN | ISIG | ECHO); tty_state.c_iflag &= ~(IGNCR | ICRNL | INPCK | ISTRIP | IXON | BRKINT); tty_state.c_iflag |= (IGNCR | ICRNL); tty_state.c_oflag &= ~OPOST; tty_state.c_cflag |= CS8; tty_state.c_cc[VMIN] = 1; tty_state.c_cc[VTIME] = 0; if (tcsetattr(fd, TCSAFLUSH, &tty_state) < 0) { return false; } return true; } /* Create a pseudo terminal for other process to use (as this program is using up the actual TTY) */ int create_pseudo_tty() { int amaster, aslave; int flags; if (openpty(&amaster, &aslave, NULL, NULL, NULL) == -1) { errlog("Error: Openpty failed - %m\n"); return -1; } /* Set to non blocking mode */ flags = fcntl(amaster, F_GETFL); flags |= O_NONBLOCK; fcntl(amaster, F_SETFL, flags); FILE *pseudo_save_file = fopen(pseudo_tty_save_file, "w+"); if (!pseudo_save_file) { errlog("Error: Unable to open the pseudo info file - %m\n"); return -1; } /* Save the name of the created pseudo tty in a text file for other processes to use */ if (fprintf(pseudo_save_file, "%s\n", ttyname(aslave)) == -1) { errlog("Error writing to the pseudo info file\n"); fclose(pseudo_save_file); return -1; } fclose(pseudo_save_file); if (set_tty(aslave) == -1) { errlog("Error: Slave TTY not set properly\n"); return -1; } return amaster; } /* Prepare logs from the read_buf and send them to the server */ bool prepare_log_send(char *read_buf, int max_read, int fd_socket) { size_t buff_index = 0; // Index for the read_buf string static char line[LINE_LEN] = { 0 }; static size_t line_index = 0; // Index for the line string char msg[MSG_LEN] = { 0 }; // Message to be sent to the server /* Kernel Version */ static char kernel_version[KERNEL_VERSION_LEN] = "dummy_kernel"; static int kernel_search_pos = 0; while (buff_index < max_read && read_buf[buff_index] != '\0') { if (read_buf[buff_index] == 'L') // Check if there is a possibility of new kernel version { kernel_search_pos = line_index; } /* Send the log when a line is read */ if (read_buf[buff_index] == '\n') { if (kernel_search_pos > 0) { if (strncmp(line + kernel_search_pos, "Linux version ", kernel_search_len) == 0) { sscanf(line + kernel_search_pos + kernel_search_len, "%s", kernel_version); } kernel_search_pos = 0; } /* Prepare the message */ memset(msg, 0, sizeof(msg)); if (snprintf(msg, sizeof(msg), "%s %s %s %s", "kernel:", kernel_version, "- msg", line) < 0) { errlog("Error copying the message - %m\n"); return false; } /* Send message to the server */ if (write(fd_socket, msg, strlen(msg)) < 0) { errlog("Error: Write to socket failed - %m\n"); return false; } /* Reset the line buffer */ line_index = 0; memset(line, 0, sizeof(line)); buff_index++; continue; } /* If line is too big, send only the first few bytes and discard others. */ if (line_index >= sizeof(line)) { line[line_index - 1] = 0; buff_index++; continue; } line[line_index++] = read_buf[buff_index++]; } return true; } /* Read text from the TTY and send to send as logs */ bool read_send(int fd_tty, int fd_socket) { char read_buf[LINE_LEN] = { 0 }; // Buffer to be read into. int read_size = 0; fd_set readset; int sel; int fdmax; int pseudo_tty = create_pseudo_tty(); if (pseudo_tty == -1) { errlog("Error: Cannot create a psuedo terminal\n"); return false; } fdmax = MAX(fd_tty, pseudo_tty); while (!kill_received) { do { FD_ZERO(&readset); FD_SET(fd_tty, &readset); FD_SET(pseudo_tty, &readset); sel = select(fdmax + 1, &readset, NULL, NULL, NULL); } while (sel == -1 && errno == EINTR && !kill_received); memset(read_buf, 0, sizeof(read_buf)); if (FD_ISSET(fd_tty, &readset)) { read_size = read(fd_tty, read_buf, sizeof(read_buf) - 1); if (read_size == 0) { continue; } if (read_size < 0) { if (errno == EAGAIN) { continue; } errlog("Error: Read from tty failed - %m\n"); return false; } /* Send the read data to the pseudo terminal */ if (write(pseudo_tty, read_buf, read_size) < 0) { if (errno == EAGAIN) // Output buffer full - flush it. { tcflush(pseudo_tty, TCIOFLUSH); continue; } errlog("Error: Write to pseudo tty failed - %m\n"); return false; } /* Prepare log message and send to the server */ if (!prepare_log_send(read_buf, sizeof(read_buf), fd_socket)) { errlog("Error: Sending log failed - %m\n"); return false; } } /*if (FD_ISSET(fd_tty, &readset)) */ if (kill_received) { break; } /* Check if there is an data in the pseudo terminal's buffer */ if (FD_ISSET(pseudo_tty, &readset)) { read_size = read(pseudo_tty, read_buf, sizeof(read_buf) - 1); if (read_size == 0) { continue; } if (read_size < 0) { if (errno == EAGAIN) { continue; } errlog("Error: Read from pseudo tty failed - %m\n"); return false; } if (write(fd_tty, read_buf, read_size) < 0) { if (errno == EAGAIN) // Output buffer full - flush it. { tcflush(fd_tty, TCIOFLUSH); continue; } errlog("Error: Write to tty failed - %m\n"); return false; } } /*if (FD_ISSET(pseudo_tty,&readset)) */ } /*while (!kill_received) */ return true; } void cleanup() { remove(pseudo_tty_save_file); tcsetattr(fd_tty, TCSAFLUSH, &orig_tty_state); //Restore original settings close(fd_tty); close(fd_soc); fclose(error_file); } void sig_kill(int signum) { kill_received = true; } void register_kill() { struct sigaction sigact; sigset_t sigset; sigemptyset(&sigset); memset(&sigact, 0, sizeof sigact); sigact.sa_handler = sig_kill; sigact.sa_mask = sigset; sigaction(SIGHUP, &sigact, NULL); sigaction(SIGINT, &sigact, NULL); sigaction(SIGQUIT, &sigact, NULL); sigaction(SIGPIPE, &sigact, NULL); sigaction(SIGTERM, &sigact, NULL); sigaction(SIGKILL, &sigact, NULL); sigaction(SIGABRT, &sigact, NULL); } void usage(char *prog_name) { printf("Usage:\n"); printf("\t%s TTY ip_version(4 or 6) hostname port [baud rate (like 57600)]\n", prog_name); printf("\t%s -h : For this help\n", prog_name); printf("Example:\n\t./bmc-log /dev/ttyS1 4 netcons.any.facebook.com 1514\n"); printf("\tOR\n\t./bmc-log /dev/ttyS1 6 netcons6.any.facebook.com 1514 57600\n"); } bool parse_user_input(int nargs, char **args, char *read_tty, int read_tty_size, int *ip_version) { if (nargs < 5) { if ((nargs > 1) && ((strcmp(args[1], "-h") == 0) || (strcmp(args[1], "--help") == 0))) { usage(args[0]); return false; // Not an error but returning -1 for the main function to return } fprintf(stderr, "Error: Invalid number of arguments\n"); usage(args[0]); return false; } if (strlen(args[1]) > read_tty_size) { fprintf(stderr, "Error: TTY too long\n"); usage(args[0]); return false; } /* TTT to read the logs from */ strncpy(read_tty, args[1], read_tty_size); /* IP Version, IP Address and Port of the netcons server */ *ip_version = atoi(args[2]); if (*ip_version != IPV4 && *ip_version != IPV6) { fprintf(stderr, "Error: Invalid IP Version input\n"); usage(args[0]); return false; } hostname = args[3]; port = atoi(args[4]); baud_rate = B57600; if (nargs == 6) baud_rate = atoi(args[5]); return true; } int main(int argc, char **argv) { char read_tty[TTY_LEN] = { 0 }; int ip_version; int socket_domain = AF_UNSPEC; char cmd[COMMAND_LEN] = { 0 }; /* Open the error log file */ error_file = fopen(error_log_file, "a+"); if (!error_file) { printf("Error: Unable to open log file - %m\n"); return 1; } /* Register actions upon interrupts */ register_kill(); /* Parse the user input */ if (!parse_user_input(argc, argv, read_tty, sizeof(read_tty), &ip_version)) { return 2; } snprintf(cmd, sizeof(cmd), "%s %s", uS_console, "connect"); if (system(cmd) == -1) { errlog("Error: Unable to connect to the micro-server\n"); return 3; } /* Create a socket to communicate with the netcons server */ socket_domain = (ip_version == IPV4) ? AF_INET : AF_INET6; fd_soc = socket(socket_domain, SOCK_DGRAM, 0); if (fd_soc == -1) { errlog("Error: Socket creation failed - %m\n"); return 4; } if (ip_version == IPV4) { /* IPv4 */ struct sockaddr_in tgt_addr; if (!prepare_sock(&tgt_addr)) { close(fd_soc); errlog("Error: Socket not valid\n"); return 5; } if (connect(fd_soc, (struct sockaddr *)&tgt_addr, sizeof(tgt_addr)) == -1) { close(fd_soc); errlog("Error: Socket connection failed - %m\n"); return 6; } } else { /* IPv6 */ struct sockaddr_in6 tgt_addr6; if (!prepare_sock6(&tgt_addr6)) { close(fd_soc); errlog("Error: Socket not valid\n"); return 5; } if (connect(fd_soc, (struct sockaddr *)&tgt_addr6, sizeof(tgt_addr6)) == -1) { close(fd_soc); errlog("Error: Socket connection failed - %m\n"); return 6; } } /* TTY Operations */ if ((fd_tty = open(read_tty, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK)) == -1) { close(fd_soc); errlog("Error: Serial Port %s open failed - %m\n", read_tty); return 7; } if (!set_tty(fd_tty)) { errlog("Error: tty not set properly\n"); cleanup(); return 8; } /* Read, prepare and send the logs */ if (!read_send(fd_tty, fd_soc)) { errlog("Error: Sending logs failed\n"); cleanup(); return 9; } cleanup(); return 0; }