#include #include #include #include #include #include #include #include #include #include "nfcommander.h" #include "command.h" #include "config.h" #include "reader.h" #define COMMAND_ALIVE 1 #define COMMAND_STOPPED 2 #define INPUT_BUFFER_MAX 2048 typedef struct { uint8_t flags; pid_t child; int iopipe[2]; nfc_tag_info_t* tag; size_t input_head; uint8_t input[INPUT_BUFFER_MAX]; } command_t; static size_t ncommands = 0; static command_t* commands = NULL; //TODO timeouting / SIGKILL static int command_scan_input(command_t* cmd, size_t bytes){ size_t n = 0; //check for line ending for(n = 0; n < bytes; n++){ if(cmd->input[cmd->input_head + n] == '\r' || cmd->input[cmd->input_head + n] == '\n'){ //terminate string cmd->input[cmd->input_head + n] = 0; //handle line if(!strncmp((char*) cmd->input, "UPDATE ", strlen("UPDATE "))){ if(cmd->tag && strlen((char*) (cmd->input + 7)) < cmd->tag->dynamic_max){ free(cmd->tag->dynamic_data); cmd->tag->dynamic_data = (uint8_t*) strdup((char*) (cmd->input + 7)); cmd->tag->dynamic_length = strlen((char*) cmd->tag->dynamic_data); reader_write(cmd->tag, TAG_WRITE_DYNAMIC); } else{ printf("Failed to update tag dynamic data\n"); } } //find beginning of next line n++; for(; n < bytes && !isprint(cmd->input[cmd->input_head + n]); n++){ } if(n == bytes){ cmd->input_head = 0; return 0; } //move back memmove(cmd->input, cmd->input + cmd->input_head + n, bytes - n); cmd->input_head = 0; //try to find another sentence return command_scan_input(cmd, bytes - n); } else if(!isprint(cmd->input[cmd->input_head + n])){ cmd->input[cmd->input_head + n] = ' '; } } if(cmd->input_head + bytes > INPUT_BUFFER_MAX - 10){ //line too long - ignore it entirely until the next linebreak cmd->input_head = 1; cmd->input[0] = 0; //fail any test for UPDATE return 0; } //no newline cmd->input_head += bytes; return 0; } int command_handle(int fd){ size_t n = 0; ssize_t bytes; //find matching command for(n = 0; n < ncommands; n++){ if(commands[n].flags & COMMAND_ALIVE && fd == commands[n].iopipe[0]){ bytes = read(fd, commands[n].input + commands[n].input_head, sizeof(commands[n].input) - commands[n].input_head - 1); if(bytes < 0){ perror("cmd/recv"); } else if(bytes == 0){ printf("Command %lu pipe closed\n", n); core_manage_fd(commands[n].iopipe[0], 0, system_command); close(commands[n].iopipe[0]); commands[n].iopipe[0] = -1; commands[n].input_head = 0; } else{ //printf("%ld bytes from command %lu\n", bytes, n); command_scan_input(commands + n, bytes); } return 0; } } printf("Unhandled command fd\n"); return 1; } void command_reap(){ int wait_status; pid_t status; size_t n; do { status = waitpid(-1, &wait_status, WNOHANG); if(status < 0 && errno != ECHILD){ perror("cmd/reap"); } if(status <= 0){ return; } //find matching command if(status){ for(n = 0; n < ncommands; n++){ if(commands[n].child == status){ commands[n].flags = 0; commands[n].child = -1; if(commands[n].iopipe[0] >= 0){ core_manage_fd(commands[n].iopipe[0], 0, system_command); close(commands[n].iopipe[0]); commands[n].iopipe[0] = -1; } printf("Command %lu terminated\n", n); } } } } while(status); } static int command_spawn(command_t* cmd, char* command_name, char* command_static){ char* workdir = config_get("command", "chdir"); char* handler = config_get("command", "handler"); char tag_uid[12] = "", dynsize[10] = ""; size_t bytes; char* dynamic = (cmd->tag->dynamic_length) ? calloc(cmd->tag->dynamic_length + 1, sizeof(uint8_t)) : NULL; printf("Starting command %s, static %s\n", command_name, command_static); if(pipe(cmd->iopipe)){ perror("cmd/pipe"); return 1; } if(fcntl(cmd->iopipe[0], F_SETFD, FD_CLOEXEC)){ perror("cmd/fcntl"); } cmd->child = fork(); switch(cmd->child){ case -1: perror("cmd/fork"); return 1; case 0: //child if(workdir && chdir(workdir)){ perror("child/chdir"); _exit(1); } //make child a session leader setpgrp(); //connect stdout while((dup2(cmd->iopipe[1], STDOUT_FILENO) == -1) && (errno == EINTR)) { } close(cmd->iopipe[1]); //set environment bytes = snprintf(tag_uid, sizeof(tag_uid), "%02X%02X%02X%02X", cmd->tag->uid[0], cmd->tag->uid[1], cmd->tag->uid[2], cmd->tag->uid[3]); if(cmd->tag->uid_length > 4){ bytes += snprintf(tag_uid + bytes, sizeof(tag_uid) - bytes, "%02X%02X%02X", cmd->tag->uid[4], cmd->tag->uid[5], cmd->tag->uid[6]); } snprintf(dynsize, sizeof(dynsize), "%ld", cmd->tag->dynamic_max); setenv("TAG_UID", tag_uid, 1); setenv("MAX_DATA_LENGTH", dynsize, 1); if(dynamic){ memcpy(dynamic, cmd->tag->dynamic_data, cmd->tag->dynamic_length); } //run actual command if(handler){ execl(handler, handler, command_name, command_static, dynamic, NULL); } else{ execl(command_name, command_name, command_static, dynamic, NULL); } perror("child/exec"); _exit(1); default: //parent cmd->flags |= COMMAND_ALIVE; close(cmd->iopipe[1]); core_manage_fd(cmd->iopipe[0], 1, system_command); } return 0; } int command_start(nfc_tag_info_t* info){ size_t n = 0, p = ncommands; uint8_t* command_name = NULL, *command_static = NULL; for(n = 0; n < ncommands; n++){ //if command already in list, do nothing if(commands[n].tag == info){ //this should not normally happen return 0; } //mark available slot if(!commands[n].tag && !commands[n].flags){ p = n; } } //cant run without an actual command if(!info->static_data || !info->static_length){ printf("Tag contains no command data\n"); return 0; } //parse out command data command_name = calloc(info->static_length + 1, sizeof(uint8_t)); if(!command_name){ printf("Failed to allocate memory\n"); return 1; } memcpy(command_name, info->static_data, info->static_length); for(n = 0; n < info->static_length; n++){ if(!isalnum(command_name[n])){ command_name[n] = 0; if(info->static_length - n > 1){ command_static = command_name + n + 1; } break; } } if(!strlen((char*) command_name)){ printf("Tag contains invalid command data\n"); free(command_name); return 1; } if(p == ncommands){ commands = realloc(commands, (p + 1) * sizeof(command_t)); if(!commands){ ncommands = 0; printf("Failed to allocate memory\n"); free(command_name); return 1; } memset(commands + p, 0, sizeof(command_t)); ncommands++; } commands[p].tag = info; commands[p].input_head = 0; if(command_spawn(commands + p, (char*) command_name, (char*) command_static)){ //TODO clean up and free command instance } free(command_name); return 0; } static void command_stop_internal(size_t n){ //send SIGTERM to process group if(kill(-commands[n].child, (commands[n].flags & COMMAND_STOPPED) ? SIGKILL : SIGTERM)){ perror("cmd/stop"); } commands[n].flags |= COMMAND_STOPPED; } int command_stop(nfc_tag_info_t* info){ size_t n = 0; for(n = 0; n < ncommands; n++){ if(commands[n].tag == info){ commands[n].tag = NULL; if(commands[n].flags & COMMAND_ALIVE){ command_stop_internal(n); } } } return 0; } void command_free(){ size_t n = 0; printf("Shutting down any remaining commands\n"); for(n = 0; n < ncommands; n++){ while(commands[n].flags & COMMAND_ALIVE){ command_stop_internal(n); command_reap(); } } ncommands = 0; free(commands); commands = NULL; }