/* This file written by Matt Harang . */ /* * argusd.c * * The Argus sensor data server. * * Build with: * `cc -o argusd argusd.c ../libargus/libargus.c' * Add `-DDEBUG' to the above command if you want to see which function bails * out when there's an error. * * (The client library code gets linked into the server, because the server * calls argus_connect() during setup to ensure that the server isn't already * running.) * * This thing compiles and runs without crashing, but it doesn't do anything * useful. Essential things that need to be written or changed are flagged * with `xxx's and `todo's. * * TODO: * Implement process_client_listen_request(), etc. * * Finish add_client() (see the todo at the bottom). * * Add sensor-reading and update-notification code. * * Change all error handling to be more graceful. * * main() is too long; divide it up into subfunctions. */ #include /* errno */ #include /* O_RDONLY, for open() */ #include /* signal(), SIG* signal names */ #include /* va_list macros */ #include /* printf(), fprintf(), etc. */ #include /* malloc(), exit(), etc. */ #include /* strerror(), memmove(), etc. */ #include /* ioctl(), FIONREAD */ #include /* poll(), struct pollfd */ #include /* misc. types needed by syscalls */ #include /* socket(), accept(), connect(), etc. */ #include /* struct sockaddr_un */ #include /* open(), read(), getopt(), etc. */ #include "../libargus/libargus.h" /* * Type definitions. */ typedef struct pollfd pollfd_t; typedef struct { char* name; int value; } sensor_t; typedef struct { pollfd_t* pollfdp; unsigned char num_sensors; sensor_t** sensors; } client_t; typedef struct { pollfd_t* pollfdp; char* filename; int first_sensor_index; } port_t; /* * Global variables. */ const char* g_version = "20040402"; char* g_prog_name = "argusd"; /* default invoked name */ char* g_socket_path = "/tmp/argusd"; /* default socket path */ pollfd_t* g_socket_pollfdp = NULL; int g_num_pollfds = 0; pollfd_t* g_pollfds = NULL; int g_num_sensors = 0; sensor_t* g_sensors = NULL; int g_num_clients = 0; client_t* g_clients = NULL; int g_num_ports = 0; port_t* g_ports = NULL; /* Temporary buffers used for packet parsing and construction. */ size_t g_input_buf_size = 0; unsigned char* g_input_buf = NULL; size_t g_output_buf_size = 0; unsigned char* g_output_buf = NULL; /* * Functions. */ /* Print a formatted message to stderr. */ #define MSG(fmt, args...) msg(__FUNCTION__, fmt, ## args) void msg(const char* func_name, const char* fmt, ...) { va_list args; fprintf(stderr, "%s: ", g_prog_name); #if DEBUG fprintf(stderr, "%s(): ", func_name); #endif va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fputc('\n', stderr); } /* Print a formatted message to stderr, followed by a string describing the * error code in errno. */ #define MSG_PERROR(fmt, args...) msg_perror(__FUNCTION__, fmt, ## args) void msg_perror(const char* func_name, const char* fmt, ...) { va_list args; fprintf(stderr, "%s: ", g_prog_name); #if DEBUG fprintf(stderr, "%s(): ", func_name); #endif va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, ": %s\n", strerror(errno)); } /* A friendly replacement for realloc(). */ int xrealloc(void* ptr, size_t size) { void** p = ptr; void* tmp; tmp = realloc(*p, size); if (size != 0 && tmp == NULL) { errno = ENOMEM; return -1; } *p = tmp; return 0; } /* Close all open connections and delete all data structures. */ void cleanup(void) { int i; if (g_socket_pollfdp != NULL) close(g_socket_pollfdp->fd); for (i = 0; i < g_num_clients; i++) close(g_clients[i].pollfdp->fd); free(g_clients); /* TODO: free(sensors); */ for (i = 0; i < g_num_ports; i++) close(g_ports[i].pollfdp->fd); free(g_ports); free(g_pollfds); free(g_input_buf); free(g_output_buf); } void quit(int signum) { cleanup(); exit(EXIT_SUCCESS); } /* Send sensor values watched by the specified client to that client. */ void send_sensor_values(client_t* client) { int e, i; unsigned char command; unsigned short packet_len; int fd; fd = client->pollfdp->fd; /* Build packet header. */ command = 1; packet_len = client->num_sensors; /* Catenate sensor values in a linear array so they can be sent with * one write() call. */ if (g_output_buf_size < packet_len) { if (xrealloc(&g_output_buf, sizeof(*g_output_buf) * packet_len) == -1) { MSG_PERROR("could not grow packet buffer: realloc()"); cleanup(); exit(EXIT_FAILURE); } g_output_buf_size = packet_len; } for (i = 0; i < client->num_sensors; i++) g_output_buf[i] = client->sensors[i]->value; /* Send packet header to client. */ e = write(fd, &command, sizeof(command)); if (e == -1) { MSG_PERROR("could not send packet command to client: write()"); cleanup(); exit(EXIT_FAILURE); } e = write(fd, &packet_len, sizeof(packet_len)); if (e == -1) { MSG_PERROR("could not send packet length to client: write()"); cleanup(); exit(EXIT_FAILURE); } /* Send sensor values to client. */ e = write(fd, g_output_buf, packet_len); if (e == -1) { MSG_PERROR("could not send update packet to client: write()"); cleanup(); exit(EXIT_FAILURE); } } /* Notify watching clients that the specified sensor has been updated. */ void notify_sensor_update(sensor_t* sensor) { int c, s; /* This loop is "inefficient", but it was probably a lot easier to * write than adding an array of back-pointers to watching clients * in each sensor would have been. Keep it simple, stupid. */ for (c = 0; c < g_num_clients; c++) { for (s = 0; s < g_clients[c].num_sensors; s++) { if (g_clients[c].sensors[s] == sensor) { send_sensor_values(&g_clients[c]); } } } } void process_client_listen_request(client_t* client) { } void process_client_names_request(client_t* client) { } void process_client_shm_request(client_t* client) { } /* Read and process a packet from a client. */ void process_client_packet(client_t* client) { int e; int fd = client->pollfdp->fd; unsigned char command; unsigned short packet_len; /* Read packet header from client. */ e = read(fd, &command, sizeof(command)); if (e == -1) { MSG_PERROR("could not read command from client: read()"); cleanup(); exit(EXIT_FAILURE); } e = read(fd, &packet_len, sizeof(packet_len)); if (e == -1) { MSG_PERROR("could not read from client: read()"); cleanup(); exit(EXIT_FAILURE); } /* XXX: Should we check for bogus commands? * If a command is bogus, should we read the packet anyway? */ /* If necessary, resize the input packet buffer to hold the * incoming packet. */ if (g_input_buf_size < packet_len) { if (xrealloc(&g_input_buf, sizeof(*g_input_buf) * packet_len) == -1) { MSG_PERROR("could not grow packet buffer: realloc()"); cleanup(); exit(EXIT_FAILURE); } g_input_buf_size = packet_len; } /* Read the body of the incoming packet into the packet buffer. */ e = read(fd, g_input_buf, packet_len); if (e == -1) { MSG_PERROR("could not read packet from client: read()"); cleanup(); exit(EXIT_FAILURE); } /* Process the packet. */ switch (command) { case 0: /* listen request */ process_client_listen_request(client); break; case 2: /* sensor name list request */ process_client_names_request(client); break; case 3: /* shm request */ process_client_shm_request(client); break; } } /* Accept a connection request, and create a new client. */ void add_client(void) { int fd; int p, c; struct sockaddr_un addr; socklen_t addr_len = sizeof(addr); /* Accept the incoming connection request. */ fd = accept(g_socket_pollfdp->fd, (struct sockaddr*)&addr, &addr_len); if (fd == -1) { MSG_PERROR("could not accept connection from client"); cleanup(); exit(EXIT_SUCCESS); } if (connect(fd, (struct sockaddr*)&addr, addr_len) == -1) { MSG_PERROR("could not connect to client"); cleanup(); exit(EXIT_SUCCESS); } /* Grow pollfds[]. */ p = g_num_pollfds; if (xrealloc(&g_pollfds, sizeof(*g_pollfds) * (g_num_pollfds + 1)) == -1) { MSG("could not grow connection list: Out of memory"); cleanup(); exit(EXIT_SUCCESS); } g_num_pollfds++; /* Create a new client struct by growing clients[]. */ c = g_num_clients; if (xrealloc(&g_clients, sizeof(*g_clients) * (g_num_clients + 1)) == -1) { MSG("Could not grow client list: Out of memory"); cleanup(); exit(EXIT_SUCCESS); } g_num_clients++; /* Initialize the new client. */ g_clients[c].pollfdp = &g_pollfds[p]; g_clients[c].pollfdp->fd = fd; g_clients[c].pollfdp->events = POLLIN; g_clients[c].num_sensors = 0; g_clients[c].sensors = NULL; /* TODO: Get version from client, and send version to client. */ } /* Remove a client: close its socket and delete its data structures. */ void remove_client(client_t* client) { int i; close(client->pollfdp->fd); /* Remove the client's socket fd from the pollfds array. */ memmove(client->pollfdp, client->pollfdp + 1, sizeof(*client->pollfdp) * (client->pollfdp - g_pollfds - 1)); g_num_pollfds--; if (xrealloc(&g_pollfds, sizeof(*g_pollfds) * g_num_pollfds) == -1) { MSG("could not shrink connection list"); cleanup(); exit(EXIT_FAILURE); } for (i = 0; i < client->num_sensors; i++) free(client->sensors[i]); free(client->sensors); /* Delete the client by shrinking clients[]. */ memmove(client, client + 1, sizeof(*client) * (client - g_clients - 1)); g_num_clients--; if (xrealloc(&g_clients, sizeof(*g_clients) * g_num_clients) == -1) { MSG("could not shrink client list"); cleanup(); exit(EXIT_FAILURE); } } /* Wait for I/O; process connection requests and commands from clients, and * process sensor data packets from the ports. */ void main_loop(void) { int e, i; /* Clear old received events. */ for (i = 0; i < g_num_pollfds; i++) g_pollfds[i].revents = 0; /* Wait for input. */ e = poll(g_pollfds, g_num_pollfds, -1); if (e == -1) { MSG_PERROR("could not poll for I/O events: poll()"); cleanup(); exit(EXIT_FAILURE); } /* Check each port for incoming sensor data. */ for (i = 0; i < g_num_ports; i++) { if (e <= 0) return; if (g_ports[i].pollfdp->revents & (POLLERR | POLLHUP | POLLNVAL)) { MSG("poll error on port %s", g_ports[i].filename); cleanup(); exit(EXIT_FAILURE); } if (g_ports[i].pollfdp->revents & POLLIN) { /* TODO: Read sensor data from ports. */ e--; } } /* Check each client socket for commands. */ for (i = 0; i < g_num_clients; i++) { if (e <= 0) return; if (g_clients[i].pollfdp->revents & (POLLERR | POLLHUP | POLLNVAL)) { MSG("poll error on client descriptor"); cleanup(); exit(EXIT_FAILURE); } if (g_clients[i].pollfdp->revents & POLLIN) { int n; /* Get # bytes waiting on this client socket. */ ioctl(g_clients[i].pollfdp->fd, FIONREAD, &n); if (n == 0) { /* Client disconnected; close the server end * of the socket and delete client data. */ remove_client(&g_clients[i]); return; } /* Otherwise, the client sent a packet; process it. */ process_client_packet(&g_clients[i]); e--; } } if (e <= 0) return; /* Check the server socket for connection requests. */ if (g_socket_pollfdp->revents & (POLLERR | POLLHUP | POLLNVAL)) { MSG("poll error on server socket"); cleanup(); exit(EXIT_FAILURE); } if (g_socket_pollfdp->revents & POLLIN) { /* Connection request received; add a new client. */ add_client(); } } void show_help(void) { printf( "argusd -- argus sensor data server\n" "syntax:\n" " %s [-H | -V]\n" " %s SERVER_SOCKET SENSOR_DEVICE...\n" "options:\n" " -H Show this help message and exit.\n" " -V Show version information and exit.\n" " SERVER_SOCKET Socket to listen to for client conenction requests;\n" " for example, `/tmp/argusd'.\n" " SENSOR_DEVICE... Device node(s) to read sensor data from;\n" " for example, `/dev/ttyS0'.\n", g_prog_name, g_prog_name); } void show_version(void) { printf("argusd version %s, compiled on %s at %s\n", g_version, __DATE__, __TIME__); } /* Initialize data structures and set up communications links; * start communication loop. */ int main(int argc, char* argv[]) { argus_t ctx; struct sockaddr_un addr; int c; g_prog_name = argv[0]; /* Parse command-line parameters. */ opterr = 0; while ((c = getopt(argc, argv, "HV")) != -1) { opterr = 0; switch (c) { case 'H': show_help(); exit(EXIT_SUCCESS); break; case 'V': show_version(); exit(EXIT_SUCCESS); break; case '?': MSG("unknown option `-%c'", optopt); MSG("type `%s -H' for help", g_prog_name); exit(EXIT_FAILURE); } } if (argc < 3) { MSG("not enough arguments"); MSG("type `%s -H' for help", g_prog_name); exit(EXIT_FAILURE); } g_socket_path = argv[1]; signal(SIGINT, quit); signal(SIGTERM, quit); signal(SIGQUIT, quit); /* Make sure that the server isn't already running. */ if (argus_connect(&ctx, g_socket_path) == 0) { argus_disconnect(&ctx); MSG("argusd is already running on Unix socket %s", g_socket_path); cleanup(); exit(EXIT_FAILURE); } /* If a stale socket file still exists, delete it. */ if (unlink(g_socket_path) == -1 && errno != ENOENT) { MSG_PERROR("could not delete stale socket: unlink()"); cleanup(); exit(EXIT_FAILURE); } /* Create pollfd array. */ g_pollfds = malloc(sizeof(*g_pollfds) * 1); if (g_pollfds == NULL) { MSG_PERROR("could not allocate connection list: malloc()"); cleanup(); exit(EXIT_FAILURE); } g_num_pollfds = 1; g_socket_pollfdp = &g_pollfds[0]; g_socket_pollfdp->events = POLLIN; /* Create ports array and open ports. */ for (c = 2; c < argc; c++) { int g; int p; int fd; fd = open(argv[c], O_RDONLY); if (fd == -1) { MSG_PERROR("could not open device %s", argv[c]); cleanup(); exit(EXIT_FAILURE); } p = g_num_pollfds; if (xrealloc(&g_pollfds, sizeof(*g_pollfds) * (g_num_pollfds + 1)) == -1) { MSG_PERROR("could not grow connection list: " "realloc()"); cleanup(); exit(EXIT_FAILURE); } g_num_pollfds++; g = g_num_ports; if (xrealloc(&g_ports, sizeof(*g_ports) * (g_num_ports + 1)) == -1) { MSG_PERROR("could not grow port list: realloc()"); cleanup(); exit(EXIT_FAILURE); } g_num_ports++; g_ports[g].pollfdp = &g_pollfds[p]; g_ports[g].pollfdp->fd = fd; g_ports[g].pollfdp->events = POLLIN; g_ports[g].filename = argv[c]; /* TODO: Set up sensors for new port. */ } /* Open a Unix domain socket for client communication. */ g_socket_pollfdp->fd = socket(PF_UNIX, SOCK_STREAM, 0); if (g_socket_pollfdp->fd == -1) { MSG_PERROR("could not create server socket: socket()"); cleanup(); exit(EXIT_FAILURE); } /* Bind the socket to a local filename. */ addr.sun_family = AF_UNIX; strncpy(addr.sun_path, g_socket_path, 108); if (bind(g_socket_pollfdp->fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { MSG_PERROR("could not bind socket to filename: bind()"); cleanup(); exit(EXIT_FAILURE); } /* Listen for connection requests on the socket. */ if (listen(g_socket_pollfdp->fd, 5) == -1) { MSG_PERROR("could not listen for connections: listen()"); cleanup(); exit(EXIT_FAILURE); } /* Communication loop. */ while (1) { main_loop(); } } /* EOF */