/* 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 . */ #include "distrenjob.h" #include "slavefuncs.h" #include "common/asprintf.h" #include "common/execio.h" #include "common/protocol.h" #include "common/remoteio.h" #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG 0 /** Sends the server a single request (see protocol.h) */ int sendSignal(struct remoteio *rem, char signal) { size_t written; size_t towrite; char *ssignal; _distren_asprintf(&ssignal, "%c", signal); towrite = strlen(ssignal); while( towrite && !remoteio_write(rem, ssignal, towrite, &written)) { fprintf(stderr, "Sending request...\n"); towrite -= written; } if(written) return 0; /** if remoteio_write returned 1, the connection is probably dead or there was a real error */ return 1; } /** Sends the server an extended signal (request + data) */ int sendExtSignal(struct remoteio *rem, char signal, char *data){ size_t written; size_t towrite; char *ssignal; _distren_asprintf(&ssignal, "%c%s", signal, data); // Just append the data FIXME: We should do this differently towrite = strlen(ssignal); while( towrite && !remoteio_write(rem, ssignal, towrite, &written)) { fprintf(stderr, "Sending request...\n"); towrite -= written; } if(written) return 0; /** if remoteio_write returned 1, the connection is probably dead or there was a real error */ return 1; } /** Grabs the xml DOM node reached by an XPath. @param path an XPath that lead to DOM node @return the first node associated with the path or NULL if there is no match */ xmlNodePtr xml_quickxpath(xmlXPathContextPtr xpathctxt, xmlChar *path) { xmlNodePtr toreturn; xmlXPathObjectPtr xmlxpathobjptr; xmlxpathobjptr = xmlXPathEval(path, xpathctxt); if(!xmlxpathobjptr || !xmlxpathobjptr->nodesetval->nodeNr) { fprintf(stderr, "XPath resolution failed for ``%s'' in ``%s'' (``%s'')\n", path, xpathctxt->doc->name, xpathctxt->doc->URL); return (xmlNodePtr)NULL; } toreturn = *(xmlxpathobjptr->nodesetval->nodeTab); xmlXPathFreeObject(xmlxpathobjptr); return toreturn; } /** Stub for deleting job data from the disk. @TODO: unstubify me! */ int delete_jobdata(int jobnum, char *datadir) { char *jobpath; _distren_asprintf(&jobpath, "%s/%d", datadir, jobnum); // rmdir(jobpath); fprintf(stderr, "Please manually remove %s. Automatic removal is currently not implemented.\n", jobpath); return 0; } /** Function referenced by curlget() to write data to disk. */ size_t curl_writetodisk(void *ptr, size_t size, size_t nmemb, FILE *stream) { return fwrite(ptr, size, nmemb, stream); } /** Helper function for cURL's progress display */ int curl_progress(char *Bar, double t, double d, double ultotal, double ulnow) { fprintf(stderr,"Downloading: %f%% complete\r",d/t*100); return 0; } /** Retrieves a URL with cURL and saves it to disk */ CURLcode curlget(char *url, char *out){ fprintf(stderr,"Preparing to download %s\n",url); double *Bar; // Stores cURL progress display info CURL *curl; CURLcode res; FILE *outfile; res = CURLE_FAILED_INIT; curl = curl_easy_init(); if(curl) { outfile = fopen(out, "w"); // Open where we're writing to if(outfile == NULL) return 2; // Output dir doesn't exist curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEDATA, outfile); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_writetodisk); // this MUST be set for win32 compat. curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, (curl_progress_callback)&curl_progress); curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &Bar); res = curl_easy_perform(curl); curl_easy_cleanup(curl); fclose(outfile); } fprintf(stderr,"\n"); // Clears out he progressbar's carriage return return res; // 0 is OK, 1 is 404 or other error } /** Posts a file to a url with cUrl */ CURLcode curlpost(char *filename, char *url, int jobnum, int framenum, int slavekey) { char *targetname = "uploadedfile"; // Name of the target in the php file on the server (Don't change me unless you have different PHP code) CURL *curl; CURLcode res; struct curl_httppost *formpost=NULL; struct curl_httppost *lastptr=NULL; struct curl_slist *headerlist=NULL; static const char buf[] = "Expect:"; char *sjobnum; char *sframenum; char *sslavekey; if(DEBUG) fprintf(stderr,"Uploading to %s\n", url); _distren_asprintf(&sjobnum,"%d",jobnum); _distren_asprintf(&sframenum,"%d",framenum); _distren_asprintf(&sslavekey,"%d",slavekey); curl_global_init(CURL_GLOBAL_ALL); /* jobnum field... */ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "jobnum", CURLFORM_COPYCONTENTS, sjobnum, CURLFORM_END); /* framenum field... */ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "framenum", CURLFORM_COPYCONTENTS, sframenum, CURLFORM_END); /* slavekey field... */ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "slavekey", CURLFORM_COPYCONTENTS, sslavekey, CURLFORM_END); /* upload field... */ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, targetname, CURLFORM_FILE, filename, CURLFORM_END); /* filename field... */ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "filename", CURLFORM_COPYCONTENTS, filename, CURLFORM_END); /* submit field, not usually needed, just in case... */ curl_formadd(&formpost, &lastptr, CURLFORM_COPYNAME, "submit", CURLFORM_COPYCONTENTS, "send", CURLFORM_END); res = CURLE_FAILED_INIT; curl = curl_easy_init(); headerlist = curl_slist_append(headerlist, buf); if(curl) { /* Setting the URL to get the post, and the contents of the post */ curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); res = curl_easy_perform(curl); curl_easy_cleanup(curl); /* cleanup the formpost junk */ curl_formfree(formpost); curl_slist_free_all (headerlist); free(sjobnum); free(sframenum); free(sslavekey); } return res; } /** Logs the user into the server after ensuring that keys exist */ int login_user(char *username) { // @TODO: Put some telnet-style auth code here unless this is obselete return 1; // success } /** Replaces wordtoreplace with replacewith in conffile (relative to SYSCONFDIR) */ int conf_replace(char *conffile, char *wordtoreplace, char *replacewith){ int maxlinelen = 120; char *fileOrig; char *fileRepl; _distren_asprintf(&fileOrig, "%s/%s", SYSCONFDIR, conffile); _distren_asprintf(&fileRepl, "%s%s.edited", SYSCONFDIR, conffile); char buffer[maxlinelen+2]; char *buff_ptr, *find_ptr; FILE *fp1, *fp2; size_t find_len = strlen(wordtoreplace); fp1 = fopen(fileOrig,"r"); fp2 = fopen(fileRepl,"w"); if (fp1 ==NULL){ fprintf(stderr, "%s doesn't exist\n",fileOrig); return 0; } else if(fp2 ==NULL){ fprintf(stderr, "Can't write a file to disk! Check permissions.\n"); return 0; } else{ while(fgets(buffer,maxlinelen+2,fp1)) { buff_ptr = buffer; while ((find_ptr = strstr(buff_ptr,wordtoreplace))) { while(buff_ptr < find_ptr) fputc((int)*buff_ptr++,fp2); fputs(replacewith,fp2); buff_ptr += find_len; } fputs(buff_ptr,fp2); } rename(fileRepl, fileOrig); } fclose(fp2); fclose(fp1); fprintf(stderr,"Wrote conf file...\n"); return 1; // Success } /* Executors */ /** Executor function for Blender operations */ int exec_blender(char *input, char *output, int frame) { int ret; char *frame_str; _distren_asprintf(&frame_str, "%i", frame); char *command = "blender"; // @TODO: We currently expect this to be in PATH char *cmd[] = { command, "-b", input, "-o", output, "-f", frame_str, "-t", "0", (char *)NULL }; // arguments for blender if(DEBUG) fprintf(stderr,"Preparing to execute command: %s -b %s -o %s -f %s\n", command, input, output, frame_str); char buf[20]; struct execio *testrem; size_t readlen; if(DEBUG) fprintf(stderr,"Executing: %s\n", frame_str); ret = execio_open(&testrem, command, cmd); if(ret) { fprintf(stderr, "Error running blender\n"); return 1; } buf[19] = '\0'; while(!execio_read(testrem, buf, 19, &readlen)) { buf[readlen] = '\0'; if(DEBUG) fprintf(stderr, "read \"%s\"\n", buf); } execio_close(testrem); free(frame_str); return ret; } void xmlinit() { xmlInitParser(); xmlXPathInit(); } void xmlcleanup() { xmlCleanupParser(); } /** Creates directories recursively */ int distren_mkdir_recurse(char *dirname) { size_t counter; char *nextdir; nextdir = strdup(dirname); for(counter = 0; nextdir[counter]; counter ++) { /** @TODO OS-portabalize the path-separators */ if(nextdir[counter] == '/') { nextdir[counter] = '\0'; mkdir(nextdir, S_IRWXU | S_IRGRP | S_IROTH); nextdir[counter] = '/'; } } return 0; } /** @TODO: Use for constructing path to job data locally and/or on data.distren.org */ int job_build_path(char *filename, unsigned int jobnum) { return 0; } int downloadTar(char *url, char *destinationPath){ // Prepare to download the job tar if it isn't present struct stat buffer; int fstatus = stat(destinationPath, &buffer); if(fstatus == -1) { // Download the Tar int ret = curlget(url, destinationPath); if(ret == 0){ fprintf(stderr, "Job data retrieved successfully\n"); // free(url); @FIXME: causes doublefree! Curl must free the url? return 0; } else if(ret == 1){ fprintf(stderr, "Downloading job data from %s failed. Check your network connection.\n",url); free(url); return 1; // Eventually make a retry loop } } fprintf(stderr, "Tar already exists! Download cancelled.\n"); return 2; } int uploadOutput(char *pathtoOutput, char *urltoOutput, int jobnum, int framenum, int slavekey){ //fprintf(stderr,"Uploading output %s to url %s for job %d and frame %d for slavekey %d", pathtoOutput, urltoOutput, jobnum, framenum, slavekey); if( !curlpost(pathtoOutput, urltoOutput, jobnum, framenum, slavekey)) // Uploads output { if(DEBUG) fprintf(stderr,"Upload successful, removing old output...\n"); else fprintf(stderr,"Upload completed\n"); remove(pathtoOutput); // Delete the file after its uploaded return 0; } else { fprintf(stderr,"Upload failed. Check your network connection. Retrying upload...\n"); int tries=1; while(tries<=10 && curlpost(pathtoOutput, urltoOutput, jobnum, framenum, slavekey)) { fprintf(stderr, "Upload failed. Trying again in 10 seconds... (attempt %d of 10)\n", tries); tries++; sleep(10); } return 1; // Upload failed after multiple tries // @FUTURE: Keep track of files that we were unable to upload, and upload them later } } /** Extracts archive to the specified directory, creating this directory if necessary (but this directory will not be recursively created). @param outdir output directory @param pathtoTar filename of the archive to extract */ int unpackJob(char *outdir, char *pathtoTar) { int ret; struct archive *a; struct archive_entry *ae; int astatus; /* ignore return because directory may exist already */ mkdir(outdir, 0700); ret = chdir(outdir); if(ret == -1) { perror("chdir"); return 1; } a = archive_read_new(); ae = archive_entry_new(); archive_read_support_compression_all(a); archive_read_support_format_raw(a); astatus = archive_read_open_filename(a, pathtoTar, 8192); if (astatus != ARCHIVE_OK) { fprintf(stderr, "error opening archive\n"); return 1; } for(astatus = ARCHIVE_OK; astatus == ARCHIVE_OK || astatus == ARCHIVE_WARN; ) { astatus = archive_read_next_header2(a, ae); if(astatus == ARCHIVE_WARN) fprintf(stderr, "Encountered nonfatal read error somehow\n"); if(astatus == ARCHIVE_OK || astatus == ARCHIVE_WARN) astatus = archive_read_extract(a, ae, ARCHIVE_EXTRACT_NO_OVERWRITE | ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_SECURE_NODOTDOT); } archive_entry_free(ae); archive_read_finish(a); if(astatus != ARCHIVE_EOF) { fprintf(stderr, "Error reading archive\n"); return 1; } return 0; } void prepareJobPaths(int jobnum, int framenum, char *outputExt, char *datadir, char **urltoTar,char **pathtoTar,char **pathtoTardir,char **pathtoJob, char **pathtoJobfile, char **urltoJobfile, char **urltoOutput,char **pathtoOutput, char **pathtoRenderOutput, char **pathtoOutdir) { // Variable Preparation char *jobdatapath; _distren_asprintf(&jobdatapath, "job%d", jobnum); _distren_asprintf(urltoTar, "http://data.distren.org/job%d/job%d.tar.gz", jobnum, jobnum); // Prepares URL to download from _distren_asprintf(urltoJobfile, "http://data.distren.org/job%d/job.blend", jobnum); // Prepares URL to download from _distren_asprintf(pathtoTar, "%s/%s/job%d.tar.gz", datadir, jobdatapath, jobnum); // Prepares destination to save to _distren_asprintf(pathtoTardir, "%s/%s/", datadir, jobdatapath); // Prepares destination to save to _distren_asprintf(pathtoJob, "%s/%s/", datadir, jobdatapath); _distren_asprintf(pathtoJobfile, "%s/%s/job.blend", datadir, jobdatapath ); // Prepares the path to the jobfile _distren_asprintf(urltoOutput, "http://dev.distren.org/slaveUpload.php?jobkey=%d&framekey=%d",jobnum,framenum); // Prepares the URL where output is posted _distren_asprintf(pathtoRenderOutput, "%s/%s/output/job%d-frame#.%s", datadir, jobdatapath, jobnum, outputExt ); // Note: the # is for blender to put in framenum in output file _distren_asprintf(pathtoOutput, "%s/%s/output/job%d-frame%d.%s", datadir, jobdatapath, jobnum, framenum, outputExt ); // Note: the # is for blender to put in framenum in output file _distren_asprintf(pathtoOutdir, "%s/%s/output", datadir, jobdatapath); free(jobdatapath); } int checkUsername(char *username) { if(username == NULL || strcmp(username, "!username") == 0 ) { fprintf(stderr, "\nPlease ensure that your username is present in distrenslave.conf\n"); return 1; } else if( username != NULL || strcmp(username, "!username") != 0 ) { // Log in the user if(login_user(username) == 1){ // Logged in OK return 0; } else { fprintf(stderr, "Login failed, please check your username. If you have not registered, please do so on the DistRen website.\n"); return 1; } } else { fprintf(stderr, "Please check your distrenslave.conf, it appears to be incorrectly formatted.\n"); return 1; } } void slaveTest(char *datadir) { int command; int test = 1; int jobnum = 0; int framenum = 0; int slavekey = 0; fprintf(stderr,"Hello!\n"); char tmpString1[100] = ""; char tmpString2[100] = ""; char *uploadURL = "http://distren.org/slaveUpload.php"; while(test == 1) { fprintf(stderr, "Welcome to DistRenSlave Alpha Interactive Test Mode\n\n"); fprintf(stderr, "\t1 \tTest posting (uploading) data\n"); fprintf(stderr, "\t2 \tTest getting frame from server\n"); fprintf(stderr, "\t3 \tTest data downloading\n"); fprintf(stderr, "\t4 \tTest archive decompression\n"); fprintf(stderr, "\t5 \tQuit\n"); scanf("%d", &command); switch(command) { case 1: fprintf(stderr,"Path to output file to upload: "); scanf("%99s", tmpString1); fprintf(stderr,"Framenum that is being uploaded: "); scanf("%d", &framenum); fprintf(stderr,"Jobnum that frame belongs to: "); scanf("%d", &jobnum); fprintf(stderr,"Slavekey that rendered this output: "); scanf("%d", &slavekey); uploadOutput(tmpString1, uploadURL, jobnum, framenum, slavekey); break; case 2: fprintf(stderr,"Remotio not implemented yet!\n"); break; case 3: fprintf(stderr,"URL to file: "); scanf("%s", tmpString1); fprintf(stderr,"Local save path (including filename): "); scanf("%s", tmpString2); if(downloadTar(tmpString1, tmpString2)) fprintf(stderr,"Error downloading!\n"); else fprintf(stderr,"Success!\n"); break; case 4: fprintf(stderr,"Jobnum to decompress: "); scanf("%d", &jobnum); fprintf(stderr,"Path to compressed data: "); scanf("%s", tmpString2); unpackJob(datadir, tmpString1); break; case 5: test = 0; break; default: fprintf(stderr, "Invalid input, please try again.\n"); break; } } } /* int runBenchmark(int slavekey, char *datadir){ // Execute blender if(exec_blender(pathtoJobfile, pathtoOutput, framenum)) { fprintf(stderr,"Error running Blender. Check your installation and/or your PATH.\n"); _web_resetframe(slavekey, password, jobnum, framenum); // Unassign the frame on the server so other slaves can render it return 1; } free(pathtoJobfile); struct stat buffer; int fstatus = stat(pathtoOutput, &buffer); if(fstatus == -1){ fprintf(stderr,"*** Frame was not rendered correctly! Scene may not have camera, or your blender installation is not working.\n"); _web_resetframe(slavekey, password, jobnum, framenum); // Unassign the frame on the server so other slaves can render it return 1; } } */ /* simpleSlave functions */ /** Memory struct for curl */ struct _web_memorystruct { char *memory; size_t size; }; void *_web_myrealloc(void *ptr, size_t size); void *_web_myrealloc(void *ptr, size_t size) { /* There might be a realloc() out there that doesn't like reallocing NULL pointers, so we take care of it here */ if(ptr) return realloc(ptr, size); else return malloc(size); } size_t _web_writememorycallback(void *ptr, size_t size, size_t nmemb, void *data) { size_t realsize = size * nmemb; struct _web_memorystruct *mem = (struct _web_memorystruct *)data; mem->memory = _web_myrealloc(mem->memory, mem->size + realsize + 1); if (mem->memory) { memcpy(&(mem->memory[mem->size]), ptr, realsize); mem->size += realsize; mem->memory[mem->size] = 0; } return realsize; } struct _web_memorystruct _web_getrequest(char *url){ // fprintf(stderr,"Preparing to get request at %s",url); CURL *curl; CURLcode res; struct _web_memorystruct chunk; chunk.memory=NULL; /* we expect realloc(NULL, size) to work */ chunk.size = 0; /* no data at this point */ curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _web_writememorycallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0"); res = curl_easy_perform(curl); curl_easy_cleanup(curl); } /* we're done with libcurl, so clean it up */ curl_global_cleanup(); return chunk; // 0 is OK, 1 is 404 or other error } void _web_finishframe(int slavekey, char *slavepass, int jobnum, int framenum){ char *url; _distren_asprintf(&url,"http://dev.distren.org/slave/act.php?mode=finishframe&slavekey=%d&slavepass=%s&jobnum=%d&framenum=%d", slavekey, slavepass, jobnum, framenum); struct _web_memorystruct data = _web_getrequest(url); free(url); fprintf(stderr,"%s\n", data.memory); if(data.memory) free(data.memory); } void _web_resetframe(int slavekey, char *slavepass, int jobnum, int framenum){ fprintf(stderr,"Resetting frame %d in job %d on server... ",framenum,jobnum); char *url; _distren_asprintf(&url,"http://dev.distren.org/slave/act.php?mode=resetframe&slavekey=%d&slavepass=%s&jobnum=%d&framenum=%d", slavekey, slavepass, jobnum, framenum); struct _web_memorystruct data = _web_getrequest(url); free(url); fprintf(stderr,"%s\n", data.memory); if(data.memory) free(data.memory); } void _web_startframe(int slavekey, char *slavepass, int jobnum, int framenum){ if(DEBUG) fprintf(stderr,"Marking frame %d started on server... ",framenum); char *url; _distren_asprintf(&url,"http://dev.distren.org/slave/act.php?mode=startframe&slavekey=%d&slavepass=%s&jobnum=%d&framenum=%d", slavekey, slavepass, jobnum, framenum); struct _web_memorystruct data = _web_getrequest(url); free(url); fprintf(stderr,"%s\n", data.memory); if(data.memory) free(data.memory); } int _web_getwork(int slavekey, char *slavepass, int *jobnum, int *framenum){ char *url; _distren_asprintf(&url,"http://dev.distren.org/slave/act.php?mode=getwork&slavekey=%d&slavepass=%s", slavekey, slavepass); struct _web_memorystruct data = _web_getrequest(url); free(url); if(!data.memory || !strcmp(data.memory,",")){ fprintf(stderr,"*** No work available on server! In other news, really weird things are happening. Check it out. You shouldn't be seeing this.\n"); return 0; } else if(!strcmp(data.memory, "ERROR_BADKEY")){ fprintf(stderr,"*** Slave %d does not exist!\n",slavekey); free(data.memory); return 0; } // Compare to PACKAGE_VERSION else{ char *tmp; char *serverversion; tmp = strtok (data.memory,",,"); if(tmp != NULL){ // make sure work is available *jobnum = atoi(tmp); tmp = strtok (NULL, ","); if(tmp == NULL) return 0; // no work *framenum = atoi(tmp); tmp = strtok (NULL, ","); if(tmp == NULL) return 0; // no work serverversion = tmp; // @TODO: This should be called every time, not just on fail. if(strcmp(PACKAGE_VERSION,serverversion)){ fprintf(stderr,"Your distren package is out of date! Please acquire a newer version. (%s local vs %s remote)\n", PACKAGE_VERSION, serverversion); return 0; } if(DEBUG) fprintf(stderr,"Software versions: %s local vs %s remote\n", PACKAGE_VERSION, serverversion); free(data.memory); return 1; } else return 0; // error } } void _web_setrenderpower(int slavekey, char *slavepass, int renderpower){ fprintf(stderr,"Setting render power on server... "); char *url; _distren_asprintf(&url,"http://dev.distren.org/slave/act.php?mode=setrenderpower&slavekey=%d&slavepass=%s&renderpower=%d", slavekey, slavepass, renderpower); struct _web_memorystruct data = _web_getrequest(url); free(url); fprintf(stderr,"%s\n", data.memory); if(data.memory) free(data.memory); } int slaveBenchmark(char *datadir, int *benchmarkTime, int *renderPower){ int ret; int frameToRender = 1; char *frame_str; _distren_asprintf(&frame_str, "%d", frameToRender); // Render frame 1 char *output; _distren_asprintf(&output, "%s/benchmark#.jpg", datadir); // Where to save benchmark output char *realOutput; _distren_asprintf(&realOutput, "%s/benchmark%d.jpg", datadir, frameToRender); // Where to save benchmark output char *input; _distren_asprintf(&input, "%s/benchmark.blend", datadir); // Input file fprintf(stderr,"Downloading benchmark data...\n"); curlget("http://data.distren.org/benchmark.blend", input); // Download to input file location char *command = "blender"; // @TODO: We currently expect this to be in PATH char *cmd[] = { command, "-b", input, "-o", output, "-f", frame_str, "-t", "0", (char *)NULL }; // arguments for blender // fprintf(stderr,"Preparing to execute command: %s -b %s -o %s -f %s\n", command, input, output, frame_str); fprintf(stderr,"Running benchmark...\n"); long startTime; long endTime; time(&startTime); char buf[20]; struct execio *testrem; size_t readlen; ret = execio_open(&testrem, command, cmd); if(ret) { fprintf(stderr, "Error executing blender\n"); return 1; } buf[19] = '\0'; while(!execio_read(testrem, buf, 19, &readlen)) { buf[readlen] = '\0'; fprintf(stderr, "read \"%s\"\n", buf); } execio_close(testrem); time(&endTime); struct stat buffer; int ostatus = stat(realOutput, &buffer); if(ostatus == -1){ ret = 1; // Return error if output wasn't generated } else remove(output); *benchmarkTime = abs(difftime(startTime,endTime)); float tmp = *benchmarkTime; tmp = (1/tmp) * 50000; *renderPower = (int)tmp; free(realOutput); free(frame_str); free(input); free(output); return ret; }