/* Copyright 2010 Nathan Phillip Brink, Ethan Zonca, Matthew Orlando This file is a part of DistRen. DistRen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. DistRen 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with DistRen. If not, see . */ /* This file contains the code which both processes (renders) jobs as a slave, and the code which distributes frames to slaves after receiving them from the client portion of the codebase. */ #include "common/config.h" #include "distrenjob.h" #include "listen.h" #include "mysql.h" #include "slavefuncs.h" #include "user_mgr.h" #include "common/asprintf.h" #include "common/execio.h" #include "common/options.h" #include "common/protocol.h" #include "common/request.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ******************* Structs ************************ */ struct general_info { struct distrenjob head; distrend_mysql_conn_t conn; struct distrend_config *config; struct { /** general_info.xml */ char *geninfo; /** * \brief Where to store the user listing. */ char *userlist; /** * where to store in-progress uploads or things that should * otherwise be on the same filesystem as the rest of the datadir * so that it may be rename()d. */ char *tmpdir; } files; int jobs_in_queue; unsigned int free_clients; unsigned int rendering_clients; unsigned int total_finished_jobs; unsigned int total_frames_rendered; unsigned int highest_jobnum; int hibernate; time_t timestamp; unsigned long total_render_power; unsigned long total_priority_pieces; user_mgr_t user_mgr; }; /* ********************************************* Function Prototypes ********************************************* */ /* ************General Functions************* */ int distrend_do(); int distrend_do_config(int argc, char *argv[], struct distrend_config **config, multiio_context_t multiio); int distrend_config_free(struct distrend_config *config); int distrend_handle_request(struct distrend_listens *listens, struct distrend_client *client, struct distren_request *req, void *reqdata, struct general_info *geninfo); /** * client request handlers */ int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); int distrend_handle_pass(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); int distrend_handle_file_post_start(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); int distrend_handle_file_post(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); int distrend_handle_file_post_finish(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data); /* functions of some generic sort ...ish */ int distrend_handle_successful_upload(struct distrend_client *client, struct distrend_client_file_post *client_file_post); /* **************XML Functions**************** */ void update_general_info(struct general_info *geninfo); int import_general_info(struct general_info *general_info); int update_xml_joblist(struct general_info *geninfo); /* **************Test Functions**************** */ int interactiveTest(int test, multiio_context_t multiio, struct general_info *general_info); /* **************** Main ********************* */ int main(int argc, char *argv[]) { /* Parse arguments */ int counter; int test = 0; /*< Interactive mode if 1 */ int tmp; struct general_info general_info; multiio_context_t multiio; enum clientstatus { CLIENTSTATUS_UNINITIALIZED = 0, CLIENTSTATUS_BUSY = 1, CLIENTSTATUS_IDLE = 2 } clientstatus; fprintf(stderr, PACKAGE_STRING "\n\ Nathan Phillip Brink \n\ Ethan Zonca \n\ \n"); #ifdef HAVE_LIST_BRAG fprintf(stderr, "Using %s\n", list_brag); #endif clientstatus = CLIENTSTATUS_UNINITIALIZED; // xmlinit(); for(counter = 0; counter < argc; counter ++) { if(strcmp(argv[counter], "-h") == 0) { fprintf(stderr, "Usage: distrend [option] \nStarts the distrend server\n\t-h\tshow this help\n\t-t\tlaunches queue testing interface \n"); return 2; } else if(strcmp(argv[counter], "-t") == 0) { fprintf(stderr, "Entering into test mode...\n\n"); test = 1; } } multiio = multiio_context_new(); if(distrend_do_config(argc, argv, &general_info.config, multiio)) return 1; /** preset paths */ _distren_asprintf(&general_info.files.geninfo, "%s/general_info.xml", general_info.config->datadir); _distren_asprintf(&general_info.files.tmpdir, "%s/tmp", general_info.config->datadir); distren_mkdir_recurse(general_info.files.tmpdir); _distren_asprintf(&general_info.files.userlist, "%s/users.xml", general_info.config->datadir); /** configuraton stuff that depends on the paths being calculated, such as loading data */ general_info.user_mgr = user_mgr_init(general_info.files.userlist); if(!general_info.user_mgr) { fprintf(stderr, "Error initializing user_mgr\n"); return 1; } /** MySQL Connection */ fprintf(stderr,"Connecting to mysql...\n"); if(mysqlConnect(&general_info.conn, general_info.config->mysql_user, general_info.config->mysql_host, general_info.config->mysql_pass, general_info.config->mysql_database) ) { fprintf(stderr, "%s:%d: mysqlConnect() failed\n", __FILE__, __LINE__); fprintf(stderr, "don't test mysql stuff\n"); interactiveTest(test, multiio, &general_info); return 1; } fprintf(stderr,"Finished connecting!\n"); /** Execute test function */ interactiveTest(test, multiio, &general_info); general_info.config->listens = distrend_listens_new(multiio, &general_info, general_info.config->options); if(!general_info.config->listens) { fprintf(stderr, "error initializing listens\n"); return 1; } remoteio_generic_data_set(general_info.config->options->remoteio, general_info.config->listens); for(counter = 0; general_info.config->listen_ports[counter]; counter ++) { tmp = distrend_listen_add(general_info.config->listens, general_info.config->listen_ports[counter]); if(tmp) { fprintf(stderr, "Error listening on port %d\n", general_info.config->listen_ports[counter]); return 1; } } distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_VERSION, &distrend_handle_version, (uint8_t)DISTREND_CLIENT_PREVERSION); distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_PASS, &distrend_handle_pass, (uint8_t)DISTREND_CLIENT_PREAUTH); distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_START, &distrend_handle_file_post_start, (uint8_t)DISTREND_CLIENT_GOOD); distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST, &distrend_handle_file_post, (uint8_t)DISTREND_CLIENT_GOOD); distrend_listen_handler_add(general_info.config->listens, DISTREN_REQUEST_FILE_POST_FINISH, &distrend_handle_file_post_finish, (uint8_t)DISTREND_CLIENT_GOOD); /* Main Loop */ general_info.config->die = 0; while(!general_info.config->die) { multiio_poll(multiio, 15000); tabletennis_serve(general_info.config->listens->tabletennis); /* Run the watchdog, @TODO: like every 10 mins or something */ frame_watchdog(general_info.conn); } distrend_listen_free(general_info.config->listens); distrend_config_free(general_info.config); xmlcleanup(); /** free() paths */ free(general_info.files.geninfo); free(general_info.files.tmpdir); free(general_info.files.userlist); mysqlDisconnect(general_info.conn); return 0; } /* ********************** Functions ************************* */ int distrend_handle_version(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) { char *tmp_str; struct distren_request_version version; if(distren_request_parse_version(req, req_data, &version)) { distrend_send_disconnect(client, "Invalid DISTREN_REQUEST_VERSION packet."); return 1; } if(client->state != DISTREND_CLIENT_PREVERSION) { distrend_send_disconnect(client, "You have already sent the VERSION command."); return 1; } if(!strncmp(PACKAGE_STRING, version.package_string, DISTREN_REQUEST_VERSION_PACKAGE_STRING_LEN)) { /** * The client and I claim to be of the same version of distren :-D * Now we will mark the client as valid. * * We won't increment his time to live, though, because it shouldn't take * him that long to auth. */ client->state = DISTREND_CLIENT_PREAUTH; } else { /** * The client claims to be of a different version of distren. * Now we will just send a disconnect packet and disconnect the client. */ _distren_asprintf(&tmp_str, "You have tried to connect to a %s server when your client claims to be running %s. Bye ;-)\n", PACKAGE_STRING, version.package_string); if(tmp_str) { distrend_send_disconnect(client, tmp_str); free(tmp_str); } else distrend_send_disconnect(client, "Invalid PACKAGE_VERSION :-|."); return 1; } return 0; } /** * Handle a DISTREN_REQUEST_PASS request. */ int distrend_handle_pass(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) { struct distren_request_pass *pass_req; char username[DISTREN_REQUEST_PASS_USERNAME_LEN + 1]; char pass[DISTREN_REQUEST_PASS_PASS_LEN + 1]; struct user *user; if(req->len < sizeof(struct distren_request_pass)) { distrend_send_disconnect(client, "You tried to send too short of a DISTREN_REQUEST_PASS."); return 1; } pass_req = req_data; memcpy(username, pass_req->username, DISTREN_REQUEST_PASS_USERNAME_LEN); username[DISTREN_REQUEST_PASS_USERNAME_LEN] = '\0'; memcpy(pass, pass_req->pass, DISTREN_REQUEST_PASS_PASS_LEN); pass[DISTREN_REQUEST_PASS_PASS_LEN] = '\0'; user = user_find(geninfo->user_mgr, username); if(!user || strcmp(user->username, username) || strcmp(user->pass, pass)) { distrend_send_disconnect(client, "Invalid username or password."); return 1; } client->state = DISTREND_CLIENT_GOOD; return 0; } /** * Traversal helper for distrend_client_find_post(). */ int distrend_client_find_post_traverse(uint32_t *post_id, struct distrend_client_file_post *client_file_post) { if(*post_id == client_file_post->post_id) return FALSE; return TRUE; } /** * Find the record for an in-progress client's file posting. */ struct distrend_client_file_post *distrend_client_find_post(struct distrend_client *client, uint32_t post_id) { if(list_traverse(client->file_post_list, &post_id, (list_traverse_func_t)&distrend_client_find_post_traverse, LIST_ALTR | LIST_FORW | LIST_FRNT) != LIST_EXTENT) return list_curr(client->file_post_list); return NULL; } /** * Finds a post_context based on the post_id and client. * * Compatible the distren_request_parse_file_post_find_context_func_t. */ static distren_request_file_post_context_t distrend_client_find_file_post_context(uint32_t post_id, void *client) { struct distrend_client_file_post *client_file_post; client_file_post = distrend_client_find_post(client, post_id); if(client_file_post) return client_file_post->post_context; return NULL; } /** * Clean up and free a client_file_post * * Whenever calling this functino, you almost _always_ have to call * list_remove_element(client->file_post_list, client_file_post); * first. */ void distrend_client_file_post_free(struct distrend_client_file_post *client_file_post) { fclose(client_file_post->fd); free(client_file_post->filename); unlink(client_file_post->file_save_path); free(client_file_post->file_save_path); free(client_file_post); } int distrend_handle_file_post_start(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) { struct distrend_client_file_post *client_file_post; distren_request_file_post_context_t post_context; uint32_t post_id; char *filename; char *str_tmp; int ret; /** * @todo access check! */ fprintf(stderr, __FILE__ ":%d:distrend_handle_file_post_start(): You need to check if a client is actually allowed to upload files somehow!\n", __LINE__); /* * other servers should be excluded from this check, but we don't * store the servertype in client yet. */ if(list_size(client->file_post_list) > 1) { distrend_send_disconnect(client, "You are trying to upload too many files at once!"); return 1; } ret = distren_request_parse_file_post_start(req, req_data, &post_context, &post_id, &filename); if(ret) { distrend_send_disconnect(client, "You sent me an invalid DISTREN_REQUEST_FILE_POST_START packet"); return 1; } if(distrend_client_find_post(client, post_id)) { _distren_asprintf(&str_tmp, "Err accepting file: You are trying to upload using post_id=%d while you have already started another upload using the same post_id", post_id); distrend_send_disconnect(client, str_tmp); free(str_tmp); distren_request_file_post_context_free(post_context); return 1; } client_file_post = malloc(sizeof(struct distrend_client_file_post)); if(!client_file_post) { distrend_send_disconnect(client, "Error accepting file: out of memory"); distren_request_file_post_context_free(post_context); return 1; } client_file_post->post_context = post_context; client_file_post->post_id = post_id; client_file_post->filename = filename; _distren_asprintf(&client_file_post->file_save_path, "%s/conn-%d_file_post-%d", geninfo->files.tmpdir, client->connection_id, post_id); client_file_post->fd = fopen(client_file_post->file_save_path, "w"); if(!client_file_post->fd) { perror("fopen"); fprintf(stderr, "error: Unable to open ``%s''. See above ``fopen'' error for more details.\n", client_file_post->file_save_path); distrend_send_disconnect(client, "Error accepting file: unable to store the file n disk"); distren_request_file_post_context_free(post_context); list_remove_element(client->file_post_list, client_file_post); distrend_client_file_post_free(client_file_post); return 1; } list_insert_after(client->file_post_list, client_file_post, 0); return 0; } int distrend_handle_file_post(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) { struct distrend_client_file_post *client_file_post; void *file_data; size_t file_data_len; uint32_t post_id; char *tmp_str; size_t written_len; int ret; ret = distren_request_parse_file_post(req, req_data, &post_id, &distrend_client_find_file_post_context, client, &file_data, &file_data_len); if(ret) { distrend_send_disconnect(client, "You sent me an invalid DISTREN_REQUEST_FILE_POST packet"); return 1; } client_file_post = distrend_client_find_post(client, post_id); if(!client_file_post) { _distren_asprintf(&tmp_str, "You are attempting to upload post_id=%d when you haven't given a DISTREN_REQUEST_FILE_POST_START packet", post_id); distrend_send_disconnect(client, tmp_str); free(tmp_str); return 1; } written_len = fwrite(file_data, 1, file_data_len, client_file_post->fd); if(written_len < file_data_len) { distrend_send_disconnect(client, "Error saving upload: error while writing to the temporary upload file"); list_remove_element(client->file_post_list, client_file_post);; /* closes the file being written, and free()s everything, unlinks the file */ distrend_client_file_post_free(client_file_post); return 1; } return 0; } /** * here be magic */ int distrend_handle_file_post_finish(struct general_info *geninfo, struct distrend_client *client, struct distren_request *req, void *req_data) { struct distrend_client_file_post *client_file_post; uint32_t post_id; int ret; ret = distren_request_parse_file_post_finish(req, req_data, &post_id, &distrend_client_find_file_post_context, client); if(ret) { switch(ret) { case 2: /* client asked to cancel a file upload */ client_file_post = distrend_client_find_post(client, post_id); if(client_file_post) { list_remove_element(client->file_post_list, client_file_post); distrend_client_file_post_free(client_file_post); } return 0; case 3: /* checksuming failed */ distrend_send_disconnect(client, "You have uploaded a file that doesn't match its checksum somehow... which should be pretty much impossible"); client_file_post = distrend_client_find_post(client, post_id); if(client_file_post) { list_remove_element(client->file_post_list, client_file_post); distrend_client_file_post_free(client_file_post); } return 1; default: distrend_send_disconnect(client, "You sent me an invalid DISTREN_REQUEST_FILE_POST_FINISH packet"); } return 1; } client_file_post = distrend_client_find_post(client, post_id); if(!client_file_post) return 1; /* * Here it is... manage a file being submitted for rendering... or * for whatever purpose it was uploaded for somehow... */ distrend_handle_successful_upload(client, client_file_post); list_remove_element(client->file_post_list, client_file_post); distrend_client_file_post_free(client_file_post); return 0; } /** * Does stuff with file uploads after they've been successfully acquired. * * @todo this should probably be genericized to handle 1. file uploads and 2. server-initiated file _downloads_, i.e., using libcurl. In that case, struct distrend_client_file_post shouldn't be passed directly to ourself but some common struct might be passed here. */ int distrend_handle_successful_upload(struct distrend_client *client, struct distrend_client_file_post *client_file_post) { fprintf(stderr, __FILE__ ":%d: STUB: I don't know what to do with %s[%s] :-/\n", __LINE__, client_file_post->filename, client_file_post->file_save_path); return 0; } /** Performs command stored in a client's request. @TODO: Fill stub */ int distrend_do() { return 0; } /* Grabs config info from confs */ int distrend_do_config(int argc, char *argv[], struct distrend_config **config, multiio_context_t multiio) { unsigned int counter; cfg_opt_t myopts_listen[] = { CFG_SIMPLE_STR("type", NULL), CFG_SIMPLE_STR("path", NULL), CFG_SIMPLE_INT("port", NULL), CFG_END() }; cfg_opt_t myopts[] = { CFG_SEC("listen", /* this must be imported into struct listens (which must still be declared) */ myopts_listen, CFGF_MULTI), CFG_SIMPLE_STR("datadir", NULL), CFG_STR_LIST("render_types", NULL, CFGF_NONE), CFG_SIMPLE_STR("mysql_user", NULL), CFG_SIMPLE_STR("mysql_host", NULL), CFG_SIMPLE_STR("mysql_pass", NULL), CFG_SIMPLE_STR("mysql_database", NULL), CFG_END() }; cfg_t *cfg_listen; fprintf(stderr, "%s:%d: running config\n", __FILE__, __LINE__); *config = malloc(sizeof(struct distrend_config)); myopts[1].simple_value = &(*config)->datadir; myopts[3].simple_value = &(*config)->mysql_user; myopts[4].simple_value = &(*config)->mysql_host; myopts[5].simple_value = &(*config)->mysql_pass; myopts[6].simple_value = &(*config)->mysql_database; if(options_init(argc, argv, &(*config)->mycfg, myopts, "daemon", &(*config)->options, multiio)) return 1; /** grab listen blocks: */ (*config)->listen_ports = malloc(sizeof(int) * (cfg_size((*config)->mycfg, "listen") + 1)); for(counter = 0; counter < cfg_size((*config)->mycfg, "listen"); counter ++) { cfg_listen = cfg_getnsec((*config)->mycfg, "listen", counter); (*config)->listen_ports[counter] = cfg_getint(cfg_listen, "port"); } (*config)->listen_ports[counter] = 0; fprintf(stderr, "using %s as datadir\n", (*config)->datadir); return 0; } int distrend_config_free(struct distrend_config *config) { distrend_listen_free(config->listens); options_free(config->options); free(config->listen_ports); free(config); return 0; } /* ************************** XML Functions ************************* */ // writes the general_info.xml file which is a copy of the general_info structure // except that it doesn't hold free_clients and rendering_clients void update_general_info(struct general_info *geninfo) { xmlTextWriterPtr writer; char *tmp; writer = xmlNewTextWriterFilename(geninfo->files.geninfo, 0); xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL); xmlTextWriterStartElement(writer, (xmlChar*)"general_info"); _distren_asprintf(&tmp, "%d", geninfo->jobs_in_queue); xmlTextWriterWriteElement(writer, (xmlChar*)"jobs_in_queue", (xmlChar*)tmp); free(tmp); _distren_asprintf(&tmp, "%d", geninfo->total_finished_jobs); xmlTextWriterWriteElement(writer, (xmlChar*)"total_finished_jobs", (xmlChar*)tmp); free(tmp); _distren_asprintf(&tmp, "%d", geninfo->total_frames_rendered); xmlTextWriterWriteElement(writer, (xmlChar*)"total_frames_rendered", (xmlChar*)tmp); free(tmp); _distren_asprintf(&tmp, "%d", geninfo->highest_jobnum); xmlTextWriterWriteElement(writer, (xmlChar*)"highest_jobnum", (xmlChar*)tmp); free(tmp); xmlTextWriterEndDocument(writer); xmlFreeTextWriter(writer); } /** Reads general state information from general_info.xml into the general_info structure. */ int import_general_info(struct general_info *general_info) { xmlDocPtr doc; xmlNodePtr cur; doc = xmlParseFile(general_info->files.geninfo); cur = xmlDocGetRootElement(doc); if (xmlStrcmp(cur->name, (xmlChar*)"general_info")) { fprintf(stderr, "xml document is wrong type"); xmlFreeDoc(doc); return 1; } cur = cur->xmlChildrenNode; general_info->jobs_in_queue = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)); cur = cur->next; general_info->total_finished_jobs = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)); cur = cur->next; general_info->total_frames_rendered = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)); cur = cur->next; general_info->highest_jobnum = atoi((char*)xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)); general_info->hibernate = 0; general_info->free_clients = 0; general_info->rendering_clients = 0; general_info->timestamp = 0; general_info->total_render_power = 0; general_info->total_priority_pieces = 0; xmlFreeDoc(doc); return 0; } /** updates job_list.xml which lists all the jobs in the queue @return 0 means success */ // @QUERY: Likely obselete (don't remove at request of ohnobinki) int update_xml_joblist(struct general_info *geninfo) { struct distrenjob *job; xmlTextWriterPtr writer; char *tmp; int counter; /** update timestamp */ geninfo->timestamp ++; if(geninfo->timestamp > 65530) geninfo->timestamp = 0; _distren_asprintf(&tmp, "%s/job_list.xml", geninfo->config->datadir); writer = xmlNewTextWriterFilename(tmp, 0); free(tmp); xmlTextWriterStartDocument(writer, NULL, "utf-8", NULL); /** create root element job_list */ xmlTextWriterStartElement(writer, (xmlChar*)"job_list"); _distren_asprintf(&tmp, "%d", geninfo->timestamp); xmlTextWriterWriteAttribute(writer, (xmlChar*)"timestamp", (xmlChar*)tmp); free(tmp); geninfo->total_priority_pieces = 0; counter = 0; for(job = geninfo->head.next; job; job = job->next) { _distren_asprintf(&tmp, "%d", job->jobnum); xmlTextWriterWriteElement(writer, (xmlChar*)"jobnum", (xmlChar*)tmp); free(tmp); /** this is needed for the new frame finder to work Why the random constant numeral 11? --ohnobinki */ geninfo->total_priority_pieces = geninfo->total_priority_pieces + job->priority; counter++; } xmlTextWriterEndElement(writer); /** close elements and end document */ xmlTextWriterEndDocument(writer); /** free writer and save xml file to disk */ xmlFreeTextWriter(writer); return 0; } /* ************************** Test Functions ************************* */ /** Interactive test for the queuing system */ /* @QUEUE: Test uses methods not present in C code using mysql web-based system */ int interactiveTest(int test, multiio_context_t multiio, struct general_info *geninfo) { int command; int32_t slaveKey = 1; jobnum_t jobKey = 0; int32_t frameNum = 0; int32_t newPriority = 0; int tmp = 0; fprintf(stderr,"Hello!\n"); while(test == 1) { fprintf(stderr, "Welcome to DistRen Alpha Interactive Test Mode\n\n"); fprintf(stderr, "\t1 \tGet a frame to render\n"); fprintf(stderr, "\t2 \tChange job priority\n"); fprintf(stderr, "\t3 \tSet frame finished\n"); fprintf(stderr, "\t4 \tSet frame started\n"); fprintf(stderr, "\t5 \tStart listener\n"); fprintf(stderr, "\t0 \tQuit\n"); scanf("%d", &command); switch(command) { case 1: fprintf(stderr,"Slave Key: "); scanf("%d", &slaveKey); fprintf(stderr, "Got frame: "); if(find_jobframe(geninfo->conn, slaveKey, &jobKey, &frameNum)) fprintf(stderr,"No frames available to render!\n"); else if(jobKey == -1) fprintf(stderr,"Slave %d has no render power!", slaveKey); else fprintf(stderr, "jobKey: %d, frameNum: %d\n",jobKey,frameNum); break; case 2: fprintf(stderr,"Job key: "); scanf("%d", &tmp); jobKey = tmp; fprintf(stderr,"New priority: "); scanf("%d", &tmp); newPriority = tmp; change_job_priority(geninfo->conn, jobKey, newPriority); fprintf(stderr,"Changed!"); break; case 3: fprintf(stderr,"Slave Key: "); scanf("%d", &tmp); slaveKey = tmp; fprintf(stderr,"Job Key: "); scanf("%d", &tmp); jobKey = tmp; fprintf(stderr,"Frame Number: "); scanf("%d", &tmp); frameNum = tmp; finish_frame(geninfo->conn, slaveKey, jobKey, frameNum); fprintf(stderr,"Finished Frame!\n"); break; case 4: fprintf(stderr,"Slave Key: "); scanf("%d", &tmp); slaveKey = tmp; fprintf(stderr,"Job Key: "); scanf("%d", &tmp); jobKey = tmp; fprintf(stderr,"Frame Number: "); scanf("%d", &tmp); frameNum = tmp; start_frame(geninfo->conn, slaveKey, jobKey, frameNum); fprintf(stderr,"Started Frame!\n"); break; case 5: while(1) { multiio_poll(multiio, 15000); tabletennis_serve(geninfo->config->listens->tabletennis); } break; case 0: test = 0; break; default: fprintf(stderr, "Invalid input, please try again.\n"); break; } } return 0; }