/* File: aceout.c * Author: Danielle et jean Thierry-Mieg (mieg@mrc-lmba.cam.ac.uk) * Copyright (C) J Thierry-Mieg and R Durbin, 1995 * ------------------------------------------------------------------- * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ------------------------------------------------------------------- * This file is part of the ACEDB genome database package, written by * Richard Durbin (MRC LMB, UK) rd@sanger.ac.uk, and * Jean Thierry-Mieg (CRBM du CNRS, France) mieg@crbm.cnrs-mop.fr * * Description: threadsafe version of freeout * Exported functions: see aceio.h * HISTORY: * Last edited: Aug 1 12:02 2003 (rnc) * * Dec 8 14:15 2000 (edgrif): Replace the static buffer with a * dynamic resizing one, needed because sometimes we * have to output single huge strings (e.g. dna object). * Created: Sun Aug 30 1999 (mieg) * CVS info: $Id: aceout.c,v 1.27 2003/08/14 09:27:45 rnc Exp $ *------------------------------------------------------------------- */ #if defined(SGI) || defined(SOLARIS) #include #endif #include #include #include /* provide incomplete (public) type */ /************************************************************/ /* We used to have a huge buffer within the aceout struct because we didn't */ /* want to fail all the time. Now, with dynamic buffers we can afford to be */ /* more parimonious I think. */ /* */ enum {ACEOUT_INITIAL_BUFSIZE = 16384} ; static magic_t ACEOUT_MAGIC = "ACEOUT"; struct AceOutStruct /* complete opaque type */ { magic_t *magic ; /* == &ACEOUT_MAGIC */ FILE *fil; /* internal - belongs to this struct */ char *filename; Stack s ; /* belongs to calling code */ int buf_len ; char *buf ; int line ; /* line number */ int pos ; /* char number in line */ long int byte ; /* total byte length */ STORE_HANDLE handle; } AceOutRec ; static magic_t ACETMP_MAGIC = "ACETMP"; struct AceTmpStruct { magic_t *magic; /* == &ACETMP_MAGIC */ ACEOUT fo; char *filename; /* the actual tmp filename. */ BOOL remove ; /* default TRUE => remove tmp files when ACETMP is destroyed. */ }; /***************/ static BOOL aceOutExists (ACEOUT fo); static ACEOUT aceOutSetFileStack (FILE *fil, Stack s, STORE_HANDLE handle); static void aceOutCloseFileStack(ACEOUT fo) ; static void aceOutFinalise (void *block); static int aceOutBuffer (ACEOUT fo, char *text); /* returns errno */ static void aceTmpFinalise (void *block); static BOOL aceTmpExists (ACETMP atmp); static void increaseBuffer(ACEOUT fo, int bytes_needed) ; /****************************************************************** ************************* public functions *********************** ******************************************************************/ ACEOUT aceOutCreateToFile (char *filename, const char *spec, STORE_HANDLE handle) /* spec is one of "w", "wb", "a" or "ab" */ { FILE *fil; ACEOUT fo = NULL; if (!filename) messcrash("aceOutCreateToFile() - NULL filename"); if (!spec) messcrash("aceOutCreateToFile() - NULL spec"); if (!(spec[0] == 'w' || spec[0] == 'a')) messcrash("aceOutCreateToFile() - non-'w' or 'a' spec"); fil = filopen(filename, "", (char*)spec); /* NOTE, will messerror on failure */ if (fil) { fo = aceOutSetFileStack (fil, 0, handle); fo->filename = strnew (filename, handle); } return fo; } /* aceOutCreateToFile */ ACEOUT aceOutCreateToURL (char *url, const char *spec, STORE_HANDLE handle) { ACEOUT fo = NULL; if (!url) messcrash("aceOutCreateToURL() - NULL url"); if (!spec) messcrash("aceOutCreateToURL() - NULL spec"); if (!(spec[0] == 'w' || spec[0] == 'a')) messcrash("aceOutCreateToURL() - non-'w' or 'a' spec"); if (strlen(url) > 8 && strncmp(url, "mailto:", 7) == 0) { char *address = url + 7; fo = aceOutCreateToMail (address, handle); } else if (strcmp(url, "stdout://") == 0) { fo = aceOutCreateToStdout (handle); } else if (strcmp(url, "stderr://") == 0) { fo = aceOutCreateToStderr (handle); } else if (strcmp(url, "stack://") == 0) { /* The word stack may be misleading, and after all it only * represents an extensible text-buffer */ messerror ("Cannot re-open output that was " "printing to a text-buffer"); } else /* filename */ { fo = aceOutCreateToFile (url, spec, handle); } return fo; } /* aceOutCreateToURL */ ACEOUT aceOutCreateToChooser (char *prompt, char *directory, char *filename, const char *extension, const char *spec, STORE_HANDLE handle) { FILE *fil; ACEOUT fo = NULL; if (!directory) messcrash("aceOutCreateToChooser() - NULL directory"); if (!filename) messcrash("aceOutCreateToChooser() - NULL filename"); if (!spec) messcrash("aceOutCreateToChooser() - NULL spec"); if (!(spec[0] == 'w' || spec[0] == 'a')) messcrash("aceOutCreateToChooser() - non-'w' or 'a' spec"); fil = filqueryopen (directory, filename, (char*)extension, (char*)spec, prompt); if (!fil) return NULL; /* user clicked "Cancel" etc. */ fo = aceOutSetFileStack (fil, 0, handle); /**** now set the URL *****/ /* until filqueryopen can tell us in a better way whether it * wrote to a mailaddress we have to hack here */ { extern Associator mailAddress ; char *address; if (mailAddress && assFind (mailAddress, fil, &address)) { fo->filename = halloc(strlen("mailto:") + strlen(address) + 1, fo->handle); sprintf (fo->filename, "mailto:%s", address); return fo; } } /* Assemble pathname : * Note, the assembled filename will be * /. * the filename will NOT incorporate the extension, * because on a Save-FileChooser it isn't editable by the user. */ fo->filename = halloc (strlen(directory) + strlen(SUBDIR_DELIMITER_STR) + strlen(filename) + strlen(extension) + 2, fo->handle); strcpy (fo->filename, directory); strcat (fo->filename, SUBDIR_DELIMITER_STR); strcat (fo->filename, filename); if (extension != NULL && strlen(extension) > 0) { strcat (fo->filename, "."); strcat (fo->filename, extension); } return fo; } /* aceOutCreateToChooser */ ACEOUT aceOutCreateToMail (char *address, STORE_HANDLE handle) { FILE *fil; ACEOUT fo = NULL; if (!address) messcrash("aceOutCreateToMail() - NULL address"); fil = filmail(address); if (fil) { fo = aceOutSetFileStack (fil, 0, handle); fo->filename = halloc (strlen("mailto:") + strlen(address) + 1, fo->handle); sprintf(fo->filename, "mailto:%s", address); } return fo; } /* aceOutCreateToMail */ ACEOUT aceOutCreateToStdout (STORE_HANDLE handle) { ACEOUT fo = NULL; fo = aceOutSetFileStack (stdout, 0, handle); fo->filename = strnew("stdout://", fo->handle); return fo; } /* aceOutCreateToStdout */ ACEOUT aceOutCreateToStderr (STORE_HANDLE handle) { ACEOUT fo = NULL; fo = aceOutSetFileStack (stderr, 0, handle); fo->filename = strnew("stderr://", fo->handle); return fo; } /* aceOutCreateToStderr */ ACEOUT aceOutCreateToStack (Stack s, STORE_HANDLE handle) { ACEOUT fo = NULL; if (!stackExists(s)) messcrash("aceOutCreateToStack() - bad stack"); fo = aceOutSetFileStack (0, s, handle); fo->filename = strnew("stack://", fo->handle); return fo; } /************************************************/ ACEOUT aceOutCopy (ACEOUT source_fo, STORE_HANDLE handle) { ACEOUT new_fo; if (source_fo->fil) new_fo = aceOutSetFileStack (source_fo->fil, 0, handle); else new_fo = aceOutSetFileStack (0, source_fo->s, handle); new_fo->filename = strnew(source_fo->filename, handle); return new_fo; } /* aceOutCopy */ /* Rather than constantly allocate/deallocate an entire ACEOUT because you are * perhaps looping and need top reuse one, you can just set a new stack with * this function. */ void aceOutSetNewStack(ACEOUT fo, Stack s) { messAssert(aceOutExists(fo) && stackExists(s)) ; aceOutCloseFileStack(fo) ; fo->s = s ; return ; } /************************************************/ char *aceOutGetURL (ACEOUT fo) { return fo->filename; } /************************************************/ char *aceOutGetFilename(ACEOUT fo) { return fo->filename; } /************************************************/ void uAceOutDestroy (ACEOUT fo) /* only to be called via macro */ { if (!aceOutExists(fo)) messcrash("uAceOutDestroy() - received invalid fo pointer"); messfree (fo) ; /* trigger aceOutFinalise */ return ; } /* uAceOutDestroy */ /************************************************/ BOOL aceOutRewind (ACEOUT fo) /* will only work on files */ { BOOL result = FALSE; if (!aceOutExists(fo)) messcrash("aceOutRewind() - received invalid fo pointer"); if (fo->fil && fo->fil != stdout && fo->fil != stderr) { if (fseek(fo->fil, 0, SEEK_SET) == 0) result = TRUE; else messerror ("Cannot fseek file %s (%s)", fo->filename, messSysErrorText()); } return result; } /* aceOutRewind */ /************************************************/ BOOL aceOutFlush (ACEOUT fo) /* will only work on files */ { BOOL result = FALSE; if (!aceOutExists(fo)) messcrash("aceOutRewind() - received invalid fo pointer"); if (fo->fil) { if (fflush(fo->fil) == 0) result = TRUE; else messerror ("Cannot fflush file %s (%s)", fo->filename, messSysErrorText()); } return result; } /* aceOutRewind */ /************************************************/ BOOL aceOutStreamPos (ACEOUT fo, long int *posp) { BOOL result = FALSE; if (fo->fil && fo->fil != stdout && fo->fil != stderr) { long int pos; pos = ftell (fo->fil); if (pos != -1) { *posp = pos; result = TRUE; } else { messerror ("Cannot ftell file %s (%s)", fo->filename, messSysErrorText()); } } else if (fo->s) { *posp = (long int)stackPos (fo->s); result = TRUE; } return result; } /* aceOutStreamPos */ /*************************************************************/ int aceOutBinary (ACEOUT fo, char *data, int size) /* copy a binary structure onto a text stack * RETURNS errno */ { int errno_result = ESUCCESS; int num_bytes_written = 0; if (!aceOutExists(fo)) messcrash("aceOutBinary() - received invalid fo pointer"); if (fo->fil) { num_bytes_written = fwrite(data,size,1,fo->fil); if (ferror (fo->fil) != 0) errno_result = errno; /* error occurred */ } else if (fo->s) { catBinary (fo->s,data,size); fo->pos = 0; fo->line++; } fo->byte += (long int)num_bytes_written; return errno_result; } /* aceOutBinary */ /*************************************************************/ /* Print output at specified position in a "teletext"-like way. */ /* */ int aceOutxy (ACEOUT fo, char *text, int x, int y) /* returns errno */ { int errno_result = ESUCCESS; int i, j, k ; int bytes_needed ; if (!aceOutExists(fo)) messcrash("aceOutxy() - received invalid fo pointer"); /* displacement from where we are now. */ i = x - fo->pos , j = y - fo->line, k = 0 ; /* The most we can write into buf is (i + j), so the buffer had better be */ /* at least that big (- terminating null). */ bytes_needed = i + j ; if (bytes_needed >= fo->buf_len) increaseBuffer(fo, bytes_needed) ; /* In theory we shouldn't need to do this but its very good for catching */ /* bugs... */ memset (fo->buf, 0, fo->buf_len) ; /* "move" to the right x,y position in the output stream using a combin- */ /* ation of blanks and newlines. */ if (i || j) { if (j > 0) { while (j--) fo->buf[k++] = '\n' ; i = x ; } if (i < 0) { fo->buf[k++] = '\n' ; i = x ; fo->line-- ; /* kludge, user should ignore this line feed */ } if (i > 0) { while (i--) fo->buf[k++] = ' ' ; } if (k >= fo->buf_len) messcrash("Internal coding error, aceOutxy has overwritten its buffer," "buffer length: %d, num bytes written: %d, positions: x=%d, y=%d", fo->buf_len, k, x, y) ; if (k) errno_result = aceOutBuffer (fo, fo->buf) ; } /* Now output the callers text at x,y */ if (errno_result == ESUCCESS) errno_result = aceOutBuffer (fo, text) ; return errno_result; } /* aceOutxy */ /************************************************/ /* Print simple text to stdout/file/whatever. * returns errno - ESUCCESS if all is OK */ int aceOutPrintStr(ACEOUT fo, char *simple_string) { int errno_result ; if (!aceOutExists(fo) || !simple_string) messcrash("aceOutPrintStr() - received %s %s", !aceOutExists(fo) ? "invalid fo pointer" : "", !simple_string ? "null string" : ""); errno_result = aceOutBuffer(fo, simple_string) ; return errno_result; } /************************************************/ /* Print text to stout/file/whatever. */ /* returns errno - ESUCCESS if all is OK */ /* */ int aceOutPrint(ACEOUT fo, char *format,...) { int errno_result ; int bytes_needed, bytes_written ; va_list args1, args2 ; if (!aceOutExists(fo)) messcrash("aceOutPrint() - received invalid fo pointer"); /* We would like to check that if there are no arguments after the format string, * then the format string should not contain a single "%" on its own. But this * is not possible with the va_args interface. */ va_start(args1, format) ; G_VA_COPY(args2, args1) ; /* check size of buffer required, if not big enough then allocate a buffer */ /* quite a bit bigger. NOTE, this is the absolute maximum...bytes written */ /* may be less, this includes terminating null. */ bytes_needed = g_printf_string_upper_bound(format, args1) ; if (bytes_needed > fo->buf_len) increaseBuffer(fo, bytes_needed) ; /* In theory we shouldn't need to do this but its very good for catching */ /* bugs... */ memset(fo->buf, 0, fo->buf_len) ; /* OK, format the string using this much better call which limits the */ /* chars written to our buffer length. Note bytes_written does _not_ */ /* include terminating null so max bytes_written = fo->buf_len - 1 */ bytes_written = g_vsnprintf(fo->buf, fo->buf_len, format, args2) ; if (bytes_written >= bytes_needed || bytes_written >= fo->buf_len) messcrash("A call to g_vsnprintf() failed - " "buffer size: %d, predicted bytes required: %d, bytes actually written: %d", fo->buf_len, bytes_needed, bytes_written) ; va_end(args1) ; va_end(args2) ; errno_result = aceOutBuffer(fo, fo->buf) ; return errno_result; } /* aceOutPrint */ /************************************************/ int aceOutLine (ACEOUT fo) /* how many lines have been written */ { if (!aceOutExists(fo)) messcrash("aceOutLine() - received invalid fo pointer"); return fo->line ; } long int aceOutByte (ACEOUT fo) /* how many bytes have been written */ { if (!aceOutExists(fo)) messcrash("aceOutByte() - received invalid fo pointer"); return fo->byte ; } int aceOutPos (ACEOUT fo) { if (!aceOutExists(fo)) messcrash("aceOutPos() - received invalid fo pointer"); return fo->pos ; } /********* ACETMP - writing to temporary files using ACEOUT ********/ /* Simply a cover func. for aceTmpCreateDir() */ /* */ ACETMP aceTmpCreate (const char *spec, STORE_HANDLE handle) { ACETMP result = NULL ; result = aceTmpCreateDir(NULL, spec, handle) ; return result ; } /* aceTmpCreate */ /* Creates a unique tmp file in one of: */ /* if dir is NULL the file is created in the system tmp dir, */ /* if dir is relative, then dir will be created under the system tmp dir */ /* if it doesn't exist and the file created there, */ /* if dir is absolute, then dir is created if it doesn't exist and the */ /* file created there. */ /* */ /* spec is one of "w" or "wb" */ ACETMP aceTmpCreateDir(const char *dir, const char *spec, STORE_HANDLE handle) { ACEOUT fo = NULL; ACETMP atmp = NULL, result = NULL ; char *dirname = NULL ; char *nameptr = NULL ; BOOL status = TRUE ; char *tmpfile = getenv("TEMP"); /* allow user to override location of temp files */ if (!spec) messcrash("aceTmpCreate() - received NULL spec"); if (spec[0] != 'w') messcrash("aceTmpCreate() - non-'w' spec"); if (!dir || !(*dir) || *dir != '/') { char *basename = #if defined(SUN) || defined(SOLARIS) (tmpfile ? tmpfile : "/var/tmp") ; #elif defined(__CYGWIN__) (tmpfile ? tmpfile : "/cygdrive/c/Temp") ; #else (tmpfile ? tmpfile : "/tmp") ; #endif dirname = hprintf(0, "%s%s%s", basename, ((dir && *dir && *dir == '/') ? "" : "/"), ((dir && *dir) ? dir : "")) ; } else dirname = strnew(dir, 0) ; if (!filCheckName(dirname, NULL, "x")) { if (mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) == -1) { messerror ("Failed to create parent directory \"%s\" for temporary files (%s)", dirname, messSysErrorText()) ; status = FALSE ; } } if (!(nameptr = tempnam(dirname, "ACEDB"))) { messerror ("Failed to create temporary file in directory \"%s\" (%s)", dirname, messSysErrorText()) ; status = FALSE ; } /* first check if we can actually create the file */ if (status) { fo = aceOutCreateToFile (nameptr, spec, 0); if (!fo) { messerror ("Failed to create ACEIN for temporary file \"%s\" (%s)", nameptr, messSysErrorText()) ; status = FALSE ; } } if (status) { atmp = halloc (sizeof(struct AceTmpStruct), handle); blockSetFinalise (atmp, aceTmpFinalise); atmp->magic = &ACETMP_MAGIC; atmp->fo = fo; atmp->filename = strnew (nameptr, 0); atmp->remove = TRUE ; result = atmp ; } /* tidy up */ if (nameptr) free(nameptr); /* N.B. was allocated by tempname using malloc */ if (dirname) messfree(dirname) ; return result ; } void uAceTmpDestroy (ACETMP atmp) { if (!aceTmpExists(atmp)) messcrash("aceTmpDestroy() - called with invalid ACETMP pointer"); messfree (atmp); /* trigger aceTmpFinalise */ return; } /* uAceTmpDestroy */ void aceTmpClose (ACETMP atmp) { /* NOTE: this zeroes the fo field */ aceOutDestroy (atmp->fo); return; } /* aceTmpClose */ ACEOUT aceTmpGetOutput (ACETMP atmp) /* The returned ACEOUT stream may be destroyed by the * calling code before this ACETMP is finalised. * This enables the user to close the tmp-file * before removing it. */ { if (!aceTmpExists (atmp)) messcrash("aceOutGetOutput() - received invalid atmp pointer"); return atmp->fo; } /* aceTmpGetOutput */ char *aceTmpGetFileName (ACETMP atmp) { if (!aceTmpExists (atmp)) messcrash("aceOutGetFileName() - received invalid atmp pointer"); return atmp->filename; } /* aceTmpGetFileName */ /* Set state so that tmp files will/will not be removed when the ACETMP is */ /* destroyed (contorted logic is because default state is to _remove_. */ void aceTmpNoRemove(ACETMP atmp, BOOL no_remove) { atmp->remove = !no_remove ; return ; } /****************************************************************** ************************ private functions *********************** ******************************************************************/ /* Increase size of our output buffer. */ /* Policy here is that if we need to make the buffer bigger, we make it 1.5x */ /* what is needed to try and avoid constant reallocation just because the */ /* next string is a bit bigger than the last. I don't think we need the */ /* usual malloc policy of doubling because much of what we use is strings */ /* which tend to be of a certain size depending on the objects/dna being */ /* looked at. */ static void increaseBuffer(ACEOUT fo, int bytes_needed) { fo->buf_len = bytes_needed + (bytes_needed / 2) ; messfree(fo->buf) ; fo->buf = (char *)halloc(fo->buf_len, fo->handle) ; return ; } static ACEOUT aceOutSetFileStack(FILE *fil, Stack s, STORE_HANDLE handle) { ACEOUT fo; fo = (ACEOUT)halloc(sizeof(AceOutRec), handle); blockSetFinalise (fo, aceOutFinalise); fo->magic = &ACEOUT_MAGIC ; fo->handle = handleCreate(); if (fil) fo->fil = fil ; else if (s) fo->s = s ; else messcrash("aceOutSetFileStack() - fil and s is NULL"); fo->buf_len = ACEOUT_INITIAL_BUFSIZE ; fo->buf = (char *)halloc(fo->buf_len, fo->handle) ; fo->line = fo->pos = fo->byte = 0 ; fo->filename = 0; return fo ; } /* aceOutSetFileStack */ /************************************************/ /* Just close everything, leave the ACEOUT otherwise intact. Note that if there * is a stack, it belongs to the caller so we don't free it. */ static void aceOutCloseFileStack(ACEOUT fo) { messAssert(aceOutExists(fo)) ; /* it'll check for stdout/stderr needed here to make sure the eventual filMail * is actually being sent */ if (fo->fil) filclose (fo->fil); fo->fil = NULL ; if (fo->filename) messfree(fo->filename) ; fo->filename = NULL ; fo->line = fo->pos = fo->byte = 0 ; return ; } /************************************************/ static void aceOutFinalise (void *block) { ACEOUT fo = (ACEOUT)block; if (!aceOutExists(fo)) messcrash("aceOutFinalise() - received invalid block pointer"); if (fo->fil) filclose (fo->fil); /* it'll check for stdout/stderr * needed here to make sure the eventual * filMail is actually being sent */ handleDestroy(fo->handle); fo->magic = 0; /* taint this memory segment as free'd * this will catch assertions if we * try to access this free'd bit * of memory */ return; } /* aceOutFinalise */ /************************************************/ static BOOL aceOutExists (ACEOUT fo) { if (fo && fo->magic == &ACEOUT_MAGIC) return TRUE; return FALSE; } /* aceOutExists */ /************************************************/ static int aceOutBuffer (ACEOUT fo, char *text) /* return errno, which is ESUCCESS, if all is OK */ { int errno_result = ESUCCESS; char *cp ; int pos = 0, line = 0, ln ; if (!aceOutExists(fo)) messcrash("aceOut() - received invalid fo pointer"); cp = text ; ln = strlen(text) ; while (*cp) if (*cp++ == '\n') { pos = 0 ; line++ ;} else pos++ ; if (fo->fil) { int num_bytes_written; /* Output the text, n.b. text may contain "%" which we don't want to */ /* be interpreted, hence our "%s" format string. */ num_bytes_written = fprintf(fo->fil, "%s", text) ; if (num_bytes_written < 0) errno_result = errno ; else ln = num_bytes_written ; } else if (fo->s) { catText(fo->s, text) ; } else messcrash("aceOutBuffer() - fo struct has neither ->fil nor ->s"); fo->byte += (long int)ln ; if (line) { fo->line += line ; fo->pos = pos ; } else fo->pos += pos ; return errno_result; } /* aceOutBuffer */ /************** ACETMP **********************/ static BOOL aceTmpExists (ACETMP atmp) { if (atmp && atmp->magic == &ACETMP_MAGIC) return TRUE; return FALSE; } /* aceTmpExists */ static void aceTmpFinalise (void *block) /* Note that the ACETMP structure has a longer life-time * than its output stream (atmp->fo). * The life-time of the ACETMP struct is the lifetime of the * tmp-file itself. The file _may_ be deleted upon destruction. * depending on the setting of remove */ { ACETMP atmp = (ACETMP)block; if (!aceTmpExists (atmp)) messcrash("aceTmpFinalise() - received invalid block pointer"); /* finish output to close the file, if not done already */ if (atmp->fo) aceOutDestroy (atmp->fo); /* remove the file */ if (atmp->remove) { if (unlink (atmp->filename) == -1) messerror ("Failed to remove tmp-file %s (%s)", atmp->filename, messSysErrorText()); } /* free the filename buffer */ messfree (atmp->filename); atmp->magic = 0; /* taint memory block as free'd */ return; } /* aceTmpFinalise */ /*********************** eof ********************************/