/* This file is part of "reprepro" * Copyright (C) 2005,2007,2008,2009 Bernhard R. Link * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include "error.h" #include "mprintf.h" #include "strlist.h" #include "names.h" #include "dirs.h" #include "database.h" #include "target.h" #include "exports.h" #include "configparser.h" #include "filecntl.h" static const char *exportdescription(const struct exportmode *mode,char *buffer,size_t buffersize) { char *result = buffer; enum indexcompression ic; static const char* compression_names[ic_count] = { "uncompressed" ,"gzipped" #ifdef HAVE_LIBBZ2 ,"bzip2ed" #endif }; bool needcomma = false, needellipsis = false; assert( buffersize > 50 ); *buffer++ = ' '; buffersize--; *buffer++ = '('; buffersize--; for( ic = ic_first ; ic < ic_count ; ic++ ) { if( (mode->compressions & IC_FLAG(ic)) != 0 ) { size_t l = strlen(compression_names[ic]); assert( buffersize > l+3 ); if( needcomma ) { *buffer++ = ','; buffersize--; } memcpy(buffer, compression_names[ic], l); buffer += l; buffersize -= l; needcomma = true; } } /* should be long enough for the previous things in all cases */ assert( buffersize > 10 ); if( mode->hooks.count > 0 ) { int i, left; if( needcomma ) { *buffer++ = ','; buffersize--; } strcpy(buffer, "script: "); buffer += 8; buffersize -= 8; needcomma = false; for( i = 0 ; i < mode->hooks.count ; i++ ) { const char *hook = dirs_basename(mode->hooks.values[i]); size_t l = strlen(hook); left = buffersize - 2 - (mode->hooks.count - i - 1); if( buffersize < 6 ) { needellipsis = true; break; } if( needcomma ) { *buffer++ = ','; buffersize--; } if( l > buffersize - 5 ) { memcpy(buffer, hook, buffersize-5); buffer += (buffersize-5); buffersize -= (buffersize-5); needellipsis = true; break; } else { memcpy(buffer, hook, l); buffer += l; buffersize -= l; assert( buffersize >= 2 ); } needcomma = true; } } if( needellipsis ) { /* moveing backward here is easier than checking above */ if( buffersize < 5 ) { buffer -= (5 - buffersize); buffersize = 5; } *buffer++ = '.'; buffersize--; *buffer++ = '.'; buffersize--; *buffer++ = '.'; buffersize--; } assert( buffersize >= 2 ); *buffer++ = ')'; buffersize--; *buffer = '\0'; return result; } retvalue exportmode_init(/*@out@*/struct exportmode *mode, bool uncompressed, /*@null@*/const char *release, const char *indexfile) { strlist_init(&mode->hooks); mode->compressions = IC_FLAG(ic_gzip) | (uncompressed?IC_FLAG(ic_uncompressed):0); mode->filename = strdup(indexfile); if( mode->filename == NULL ) return RET_ERROR_OOM; if( release == NULL ) mode->release = NULL; else { mode->release = strdup(release); if( mode->release == NULL ) return RET_ERROR_OOM; } return RET_OK; } // TODO: check for scripts in confdir early... retvalue exportmode_set(struct exportmode *mode, struct configiterator *iter) { retvalue r; char *word; r = config_getword(iter, &word); if( RET_WAS_ERROR(r) ) return r; if( r == RET_NOTHING ) { fprintf(stderr, "Error parsing %s, line %u, column %u: Unexpected end of field!\n" "Filename to use for index files (Packages, Sources, ...) missing.\n", config_filename(iter), config_markerline(iter), config_markercolumn(iter)); return RET_ERROR_MISSING; } assert( word[0] != '\0' ); if( word[0] == '.' ) { free(word); fprintf(stderr, "Error parsing %s, line %u, column %u: filename for index files expected!\n", config_filename(iter), config_markerline(iter), config_markercolumn(iter)); return RET_ERROR; } free(mode->filename); mode->filename = word; r = config_getword(iter, &word); if( RET_WAS_ERROR(r) ) return r; if( r == RET_NOTHING ) word = NULL; if( r != RET_NOTHING && word[0] != '.' ) { assert( word[0] != '\0' ); free(mode->release); mode->release = word; r = config_getword(iter, &word); if( RET_WAS_ERROR(r) ) return r; } if( r == RET_NOTHING ) { fprintf(stderr, "Error parsing %s, line %u, column %u: Unexpected end of field!\n" "Compression identifiers ('.', '.gz' or '.bz2') missing.\n", config_filename(iter), config_markerline(iter), config_markercolumn(iter)); return RET_ERROR; } if( word[0] != '.' ) { fprintf(stderr, "Error parsing %s, line %u, column %u:\n" "Compression extension ('.', '.gz' or '.bz2') expected.\n", config_filename(iter), config_markerline(iter), config_markercolumn(iter)); free(word); return RET_ERROR; } mode->compressions = 0; while( r != RET_NOTHING && word[0] == '.' ) { if( word[1] == '\0' ) mode->compressions |= IC_FLAG(ic_uncompressed); else if( word[1] == 'g' && word[2] == 'z' && word[3] == '\0') mode->compressions |= IC_FLAG(ic_gzip); #ifdef HAVE_LIBBZ2 else if( word[1] == 'b' && word[2] == 'z' && word[3] == '2' && word[4] == '\0') mode->compressions |= IC_FLAG(ic_bzip2); #endif else { fprintf(stderr, "Error parsing %s, line %u, column %u:\n" "Unsupported compression extension '%s'!\n", config_filename(iter), config_markerline(iter), config_markercolumn(iter), word); free(word); return RET_ERROR; } free(word); r = config_getword(iter, &word); if( RET_WAS_ERROR(r) ) return r; } while( r != RET_NOTHING ) { if( word[0] == '.' ) { fprintf(stderr, "Error parsing %s, line %u, column %u:\n" "Scripts starting with dot are forbidden to avoid ambiguity ('%s')!\n" "Try to put all compressions first and then all scripts to avoid this.\n", config_filename(iter), config_markerline(iter), config_markercolumn(iter), word); free(word); return RET_ERROR; } else if( word[0] == '/' ) { r = strlist_add(&mode->hooks, word); if( RET_WAS_ERROR(r) ) return r; } else { char *fullfilename = calc_conffile(word); free(word); if( FAILEDTOALLOC(fullfilename) ) return RET_ERROR_OOM; r = strlist_add(&mode->hooks, fullfilename); if( RET_WAS_ERROR(r) ) return r; } r = config_getword(iter, &word); if( RET_WAS_ERROR(r) ) return r; } return RET_OK; } static retvalue gotfilename(const char *relname, size_t l, struct release *release) { if( l > 12 && memcmp(relname+l-12,".tobedeleted",12) == 0) { char *filename; filename = strndup(relname,l-12); if( filename == NULL ) return RET_ERROR_OOM; return release_adddel(release,filename); } else if( l > 4 && memcmp(relname+(l-4),".new",4) == 0 ) { char *filename,*tmpfilename; filename = strndup(relname,l-4); if( filename == NULL ) return RET_ERROR_OOM; tmpfilename = strndup(relname,l); if( tmpfilename == NULL ) { free(filename); return RET_ERROR_OOM; } return release_addnew(release,tmpfilename,filename); } else if( l > 5 && memcmp(relname + (l-5), ".new.", 5) == 0 ) { char *filename, *tmpfilename; filename = strndup(relname, l-5); if( FAILEDTOALLOC(filename) ) return RET_ERROR_OOM; tmpfilename = strndup(relname, l-1); if( FAILEDTOALLOC(tmpfilename) ) { free(filename); return RET_ERROR_OOM; } return release_addsilentnew(release, tmpfilename, filename); } else if( l > 5 && memcmp(relname + (l-5), ".keep", 5) == 0 ) { return RET_OK; } else { char *filename; filename = strndup(relname,l); if( filename == NULL ) return RET_ERROR_OOM; return release_addold(release,filename); } } static retvalue callexporthook(/*@null@*/const char *hook, const char *relfilename, const char *mode, struct release *release) { pid_t f,c; int status; int io[2]; char buffer[1000]; int already = 0; if( hook == NULL ) return RET_NOTHING; status = pipe(io); if( status < 0 ) { int e = errno; fprintf(stderr, "Error %d creating pipe: %s!\n", e, strerror(e)); return RET_ERRNO(e); } f = fork(); if( f < 0 ) { int e = errno; (void)close(io[0]); (void)close(io[1]); fprintf(stderr, "Error %d while forking for exporthook: %s\n", e, strerror(e)); return RET_ERRNO(e); } if( f == 0 ) { char *reltmpfilename; int e; if( dup2(io[1],3) < 0 ) { e = errno; fprintf(stderr, "Error %d dup2'ing fd %d to 3: %s\n", e, io[1], strerror(e)); exit(255); } /* "Doppelt haelt besser": */ if( io[0] != 3 ) (void)close(io[0]); if( io[1] != 3 ) (void)close(io[1]); closefrom(4); /* backward compatibilty */ reltmpfilename = calc_addsuffix(relfilename,"new"); if( reltmpfilename == NULL ) { exit(255); } setenv("REPREPRO_BASE_DIR", global.basedir, true); setenv("REPREPRO_OUT_DIR", global.outdir, true); setenv("REPREPRO_CONF_DIR", global.confdir, true); setenv("REPREPRO_DIST_DIR", global.distdir, true); setenv("REPREPRO_LOG_DIR", global.logdir, true); (void)execl(hook, hook, release_dirofdist(release), reltmpfilename, relfilename, mode, ENDOFARGUMENTS); e = errno; fprintf(stderr, "Error %d while executing '%s': %s\n", e, hook, strerror(e)); exit(255); } close(io[1]); markcloseonexec(io[0]); if( verbose > 6 ) printf("Called %s '%s' '%s.new' '%s' '%s'\n", hook,release_dirofdist(release),relfilename,relfilename,mode); /* read what comes from the client */ while( true ) { ssize_t r; int last,j; r = read(io[0],buffer+already,999-already); if( r < 0 ) { int e = errno; fprintf(stderr, "Error %d reading from exporthook: %s!\n", e, strerror(e)); break; } already += r; if( r == 0 ) { buffer[already] = '\0'; already++; } last = 0; for( j = 0 ; j < already ; j++ ) { if( buffer[j] == '\n' || buffer[j] == '\0' ) { int next = j+1; int e = (j>0)?(j-1):j; retvalue ret; while( last < j && xisspace(buffer[last]) ) last++; if( last >= j ) { last = next; continue; } while( xisspace(buffer[e]) ) { e--; assert( e >= last ); } ret = gotfilename(buffer+last,e-last+1,release); if( RET_WAS_ERROR(ret) ) { (void)close(io[0]); return ret; } last = next; } } if( last > 0 ) { if( already > last ) memmove(buffer,buffer+last,already-last); already -= last; } if( r == 0 ) break; } (void)close(io[0]); do { c = waitpid(f,&status,WUNTRACED); if( c < 0 ) { int e = errno; fprintf(stderr, "Error %d while waiting for hook '%s' to finish: %s\n", e, hook, strerror(e)); return RET_ERRNO(e); } } while( c != f ); if( WIFEXITED(status) ) { if( WEXITSTATUS(status) == 0 ) { if( verbose > 6 ) printf("Exporthook successfully returned!\n"); return RET_OK; } else { fprintf(stderr,"Exporthook failed with exitcode %d!\n",(int)WEXITSTATUS(status)); return RET_ERROR; } } else { fprintf(stderr,"Exporthook terminated abnormally. (status is %x)!\n",status); return RET_ERROR; } } retvalue export_target(const char *relativedir, struct target *target, struct database *database, const struct exportmode *exportmode, struct release *release, bool onlyifmissing, bool snapshot) { retvalue r; struct filetorelease *file; const char *status; char *relfilename; char buffer[100]; const char *chunk; size_t chunk_len; struct target_cursor iterator; relfilename = calc_dirconcat(relativedir,exportmode->filename); if( relfilename == NULL ) return RET_ERROR_OOM; r = release_startfile(release,relfilename,exportmode->compressions,onlyifmissing,&file); if( RET_WAS_ERROR(r) ) { free(relfilename); return r; } if( RET_IS_OK(r) ) { if( release_oldexists(file) ) { if( verbose > 5 ) printf(" replacing '%s/%s'%s\n", release_dirofdist(release), relfilename, exportdescription(exportmode, buffer, 100)); status = "change"; } else { if( verbose > 5 ) printf(" creating '%s/%s'%s\n", release_dirofdist(release), relfilename, exportdescription(exportmode, buffer, 100)); status = "new"; } r = target_openiterator(target, database, READONLY, &iterator); if( RET_WAS_ERROR(r) ) { release_abortfile(file); free(relfilename); return r; } while( target_nextpackage_len(&iterator, NULL, &chunk, &chunk_len) ) { if( chunk_len == 0 ) continue; (void)release_writedata(file, chunk, chunk_len); (void)release_writestring(file, "\n"); if( chunk[chunk_len-1] != '\n' ) (void)release_writestring(file, "\n"); } r = target_closeiterator(&iterator); if( RET_WAS_ERROR(r) ) { release_abortfile(file); free(relfilename); return r; } r = release_finishfile(release,file); if( RET_WAS_ERROR(r) ) { free(relfilename); return r; } } else { if( verbose > 9 ) printf(" keeping old '%s/%s'%s\n", release_dirofdist(release), relfilename, exportdescription(exportmode, buffer, 100)); status = "old"; } if( !snapshot ) { int i; for( i = 0 ; i < exportmode->hooks.count ; i++ ) { const char *hook = exportmode->hooks.values[i]; r = callexporthook(hook, relfilename, status, release); if( RET_WAS_ERROR(r) ) { free(relfilename); return r; } } } free(relfilename); return RET_OK; } void exportmode_done(struct exportmode *mode) { assert( mode != NULL); free(mode->filename); strlist_done(&mode->hooks); free(mode->release); }