/** * @file generator.c * @date 2018-05-21 * * @brief Client program which generates solutions and submits them. */ #include "common.h" #include /** * @details Global variable for the program name. */ static const char *pname; /** * @details Global struct for shared memory. */ static struct circ_buf *shared; /** * @details Global file descriptor for shared memory. */ static int shmfd; /** * @details Semaphore for tracking the used space in shared memory. */ static sem_t *sUsedSpace; /** * @details Semaphore for tracking the free space in shared memory. */ static sem_t *sFreeSpace; /** * @details Semaphore for exclusive access to shared memory for generators. */ static sem_t *sWriteEnd; /** * @details Struct for signal handler. */ static struct sigaction sa; /** * @details struct which stores a node in an adjacency list. */ struct AdjListNode { int dest; struct AdjListNode *next; }; /** * @details Struct which respresents an adjacency list. Stores a pointer to * the first element. */ struct AdjList { struct AdjListNode *head; }; /** * @details Struct which stores a graph made up of the count of nodes and an * array for the adjacency lists. */ struct Graph { int V; struct AdjList *array; }; /** * @details Global graph which gets populated by parse(). */ struct Graph graph; /** * @details Variable which gets set on signal received. */ static volatile sig_atomic_t quit = 0; /** * @details Signal handler. * @param signum The number of the received signal. * @return This function has no return value. */ static void sig_handler(int signum) { quit = 1; } /** * @details Prints the synopsis. * @param void This function takes no parameters. * @return Always non-zero. */ static void usage(void) { fprintf(stderr, "SYNOPSIS\n" "\tgenerator EDGE1...\n" "EXAMPLE\n" "\tgenerator 0-1 1-2 1-3 1-4 2-4 3-6 4-3 4-5 6-0\n"); exit(EXIT_FAILURE); } /** * @details Function which creates a new adjacency list node. * @param dest The value of the node. * @return Returns the new node. */ static struct AdjListNode *newAdjListNode(int dest) { struct AdjListNode *newNode = malloc(sizeof(struct AdjListNode)); newNode->dest = dest; newNode->next = NULL; return newNode; } /** * @details Function which creates a new graph. * @param V The number of nodes in the new graph. * @return This function has no return value. */ static void createGraph(int V) { graph.V = V; graph.array = malloc(V * sizeof(struct AdjList)); for (int i = 0; i < V; i++) graph.array[i].head = NULL; } /** * @details This function adds a new edge from src to dest. * @param src The source node of the edge. * @param dest The destination node of the edge. * @return This function has no return value. */ static void addEdge(int src, int dest) { struct AdjListNode *newNode = newAdjListNode(dest); newNode->next = graph.array[src].head; graph.array[src].head = newNode; } /** * @details Function which safely converts a string to an integer. * @param str The string to be converted. * @return -2 if out of range (long). -1 if out of range (int). Converted value * on success. */ static int strToInt(char *str) { char *endptr; long val; errno = 0; val = strtol(str, &endptr, 10); if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || (errno != 0)) { perror("[./generator] strtol"); return -2; } if (val < INT_MIN || val > INT_MAX) { fprintf(stderr, "[%s] Please choose a smaller node: ", pname); return -1; } return (int) val; } /** * @details Function which parses the commandline arguments and populates the graph. * @param argc The number of supplied commandline arguments. * @param argv The array of supplied commandline arguments. * @return This function has no return value. */ static void parse(int argc, char *argv[]) { char *token; int V = 0; int src; int dest; int edges_counter = 0; int edges[(argc - 1) * 2]; for (int i = 1; i < argc; i++) { char const *edge = argv[i]; char *str, *orig_copy; str = orig_copy = strdup(edge); int count = 0; while ((token = strsep(&str, "-"))) { if (strcmp(token, edge) == 0 && str == NULL) { fprintf(stderr, "[%s] An edge consists of two " "nodes separated by a dash\n", pname); free(orig_copy); return; } if (count == 0) { src = strToInt(token); edges[edges_counter] = src; edges_counter++; if (src > V) V = src; } if (count == 1) { dest = strToInt(token); edges[edges_counter] = dest; edges_counter++; if (dest > V) V = dest; } if (count > 1) { fprintf(stderr, "[%s] An edge consists of two " "nodes separated by a dash\n", pname); free(orig_copy); return; } switch (src) { case -2: free(orig_copy); return; break; case -1: fprintf(stderr, "%s\n", edge); free(orig_copy); return; break; default: break; } count++; } free(orig_copy); } createGraph(V+1); for (int i = 0; i < (sizeof(edges) / sizeof(int)); i+=2) { addEdge(edges[i], edges[i+1]); } } /** * @details Cleanup function which frees memory and closes shared memory and * semaphores. * @param void This function takes no parameters. * @return 0 on success, non-zero on failure. */ static void cleanup(void) { sem_post(sWriteEnd); sem_post(sFreeSpace); for (int v = 0; v < graph.V; v++) { struct AdjListNode *pCrawl = graph.array[v].head; struct AdjListNode *tmp; while (pCrawl) { tmp = pCrawl; pCrawl = pCrawl->next; free(tmp); } } free(graph.array); if (munmap(shared, sizeof(*shared)) == -1) { perror("munmap"); exit(EXIT_FAILURE); } close(shmfd); sem_close(sUsedSpace); sem_close(sFreeSpace); sem_close(sWriteEnd); exit(EXIT_SUCCESS); } /** * @details Custom sem_wait function which listens for signals. * @param sem Semaphore to be decremented. * @return This function has no return value. */ static void wait_sem(sem_t *sem) { while (sem_wait(sem) == -1) { // interrupted by syscall? if (errno == EINTR) { if (quit == 1) { cleanup(); exit(EXIT_SUCCESS); } else continue; } cleanup(); exit(EXIT_FAILURE); } return; } /** * @details Function which writes a solution to the shared memory buffer. * @param solution The solution to be written to shared memory. * @return This function has no return value. */ static void putSolution(int *solution) { wait_sem(sWriteEnd); if (shared->quit == 1) { quit = 1; cleanup(); } for (int i = 0; i < MAX_ITEMS; i++) { wait_sem(sFreeSpace); if (shared->quit == 1) { quit = 1; cleanup(); } shared->data[shared->tail] = solution[i]; shared->tail = (shared->tail + 1) % MAX_ITEMS; sem_post(sUsedSpace); } sem_post(sWriteEnd); } /** * @details Function which finds the index of a specific value in an array. * @param array The array in which the desired value resides. * @param value The value to search for. * @param size The size of the array. * @return -1 if value not found in array. The index of value if found. */ static int findIndex(int array[], int value, int size) { int index = 0; while (index < size && array[index] != value) index++; return (index == size ? -1 : index); } /** * @details Function which receives a random permutation of the nodes and finds * a random set of edges which - if removed - leave an acyclic graph. * @param permutation The random permutation of the nodes. * @param V The number of nodes in the graph. * @param solution Contains the solution of edges at the end. * @return This function has no return value. */ static void genSolution(int permutation[], int V, int *solution) { if (shared->quit == 1) { quit = 1; cleanup(); } int r; // initialize array to indices for (int i = 0; i < V; i++) { permutation[i] = i; } // loop from end to start, choose random and switch // with number at current index. for (int i = (V - 1); i > 0; i--) { r = rand() % (i+1); int tmp = permutation[r]; permutation[r] = permutation[i]; permutation[i] = tmp; } int count = 0; for (int i = 0; i < MAX_ITEMS; i++) { solution[i] = -1; } for (int u = 0; u < V; u++) { int idx = findIndex(permutation, u, V); struct AdjListNode *pCrawl = graph.array[u].head; while (pCrawl) { int idx_v = findIndex(permutation, pCrawl->dest, V); if (idx > idx_v) { solution[count++] = u; solution[count++] = pCrawl->dest; if (count > MAX_ITEMS) return; break; } pCrawl = pCrawl->next; } } } /** * The main entry point of the program. * @details Sets up signal handler, shared memory and semaphores, parses the * commandline arguments and periodically generates a new solution and * submits it to the shared memory. * @param argc The number of commandline arguments. * @param argv The array of commandline arguments. * @return 0 on success, non-zero on failure. */ int main(int argc, char *argv[]) { if (argc == 1) usage(); pname = argv[0]; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = &sig_handler; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); shmfd = shm_open(SHM_NAME, O_RDWR, PERMISSION); if (shmfd == -1) { perror("[./generator] shm_open"); exit(EXIT_FAILURE); } shared = mmap(NULL, sizeof(*shared), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); if (shared == MAP_FAILED) { perror("[./generator] mmap"); exit(EXIT_FAILURE); } sFreeSpace = sem_open(SEM_FREE_SPACE, 0); sUsedSpace = sem_open(SEM_USED_SPACE, 0); sWriteEnd = sem_open(SEM_WRITE_END, 0); if (sFreeSpace == SEM_FAILED || sUsedSpace == SEM_FAILED || sWriteEnd == SEM_FAILED) { perror("[./generator] sem_open"); exit(EXIT_FAILURE); } parse(argc, argv); // seed differently for each process srand(time(NULL) * getpid()); int permutation[graph.V]; while (quit == 0) { if (shared->quit == 1) break; int solution[MAX_ITEMS]; genSolution(permutation, graph.V, solution); putSolution(solution); } cleanup(); return 0; }