Logo Search packages:      
Sourcecode: fhist version File versions

subroutine.c

/*
 *    fhist - file history and comparison tools
 *    Copyright (C) 1991-1994, 1998-2002 Peter Miller;
 *    All rights reserved.
 *
 *    Derived from a work
 *    Copyright (C) 1990 David I. Bell.
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    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., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: Commonly used generic functions for source control program.
 */

#include <ac/ctype.h>
#include <ac/errno.h>
#include <ac/stdio.h>
#include <ac/string.h>
#include <ac/unistd.h>

#include <cmalloc.h>
#include <compare.h>
#include <error_intl.h>
#include <fcheck.h>
#include <fhist.h>
#include <fileio.h>
#include <isdir.h>
#include <str.h>
#include <subroutine.h>


int
history_file_exists(void)
{
    FILE            *fp;

    fp = fopen(sc.historyname, "rb");
    if (fp)
    {
      fclose_and_check(fp, sc.historyname);
      return 1;
    }
    if (errno != ENOENT)
    {
      sub_context_ty    *scp;

      scp = sub_context_new();
      sub_errno_set(scp);
      sub_var_set_charstar(scp, "File_Name", sc.historyname);
      fatal_intl(scp, i18n("open \"$filename\": $errno"));
    }
    return 0;
}


/*
 * Routine used to open the history file and read the first line.
 * This saves the information about the first and last edit numbers,
 * and the position of the position table in variables.
 *
 * The history (.e) file is binary, because we need to seek in it.
 * The source (.s) file is text, because we don't need to seek in it.
 * The input files are text, by definition.
 * The output files are text, by definition.
 */

FILE *
openhistoryfile(int mode)
{
    FILE            *fp;            /* file handle for history file */
    char            *cp;            /* current line of file */
    sub_context_ty  *scp;

    fp = fopen(sc.historyname, (mode == OHF_READ ? "rb" : "r+b"));
    if (!fp)
    {
      if (errno == ENOENT)
      {
          scp = sub_context_new();
          sub_var_set_charstar(scp, "Module", sc.modulename);
          fatal_intl(scp, i18n("module \"$module\" does not exist"));
      }
      scp = sub_context_new();
      sub_errno_set(scp);
      sub_var_set_charstar(scp, "File_Name", sc.historyname);
      fatal_intl(scp, i18n("open \"$filename\": $errno"));
    }
    cp = get_a_line(fp, 0L, T_HEADER, sc.historyname);
    if ((sc.linelen + 2) != HEADERLINELENGTH)
    {
      fatal_with_filename
      (
          sc.historyname,
          0,
          i18n("header line has wrong length")
      );
    }
    cp = getnumber(cp, &sc.firstedit);
    cp = getnumber(cp, &sc.lastedit);
    cp = getnumber(cp, &sc.lastfile);
    cp = getnumber(cp, &sc.tablepos);
    if (cp == NULL)
    {
      fatal_with_filename(sc.historyname, 0, i18n("bad header line"));
    }
    if (sc.firstedit < 0 || sc.lastedit < sc.firstedit || sc.tablepos <= 0)
    {
      fatal_with_filename
      (
          sc.historyname,
          0,
          i18n("bad edit values in header line")
      );
    }
    return fp;
}


/*
 * Find an edit number in the history file given the edit name string.
 * This string is either a straight number, a straight edit name, or a
 * combination of the two.  A combination is indicated by an imbedded plus
 * or minus sign.  Examples:  "45", "good2", "nice+5".  The resulting
 * edit number is returned if it is valid.
 */

long
findeditnumber(FILE *fp, char *editname)
{
    char            *cp;      /* current character */
    char            *name;    /* edit name */
    char            *endname; /* end of name */
    char            *testname;      /* name to test with */
    long            edit;     /* resulting edit number */
    long            reledit;  /* relative edit number */
    long            curedit;  /* current edit number */
    long            tempedit; /* edit number just read */
    long            temppos;  /* position number just read */
    short           isneg;    /* 1 if negative number given */
    sub_context_ty  *scp;

    if (!editname)
      return sc.lastedit;
    edit = 0;
    reledit = 0;
    isneg = 0;
    name = NULL;
    cp = editname;

    /*
     * First, see if there is a name present.  If so, remember the beginning
     * of the name and skip over it to where a number can start.
     */
    endname = 0;
    if ((*cp != '-') && (*cp != '+') && !isdigit((unsigned char)*cp))
    {
      name = cp;
      while (*cp && (*cp != '-') && (*cp != '+'))
          cp++;
      endname = cp;
    }

    /*
     * Now look for an edit number offset.
     */
    if ((*cp == '+') || (*cp == '-'))
      isneg = (*cp++ == '-');
    while (isdigit((unsigned char)*cp))
      reledit = reledit * 10 + (*cp++ - '0');
    if (*cp || (reledit < 0))
    {
      scp = sub_context_new();
      sub_var_set_charstar(scp, "Module", sc.modulename);
      sub_var_set_charstar(scp, "Name", editname);
      fatal_intl
      (
          scp,
          i18n("bad edit name \"$name\" given for module \"$module\"")
      );
    }
    if (isneg)
      reledit = -reledit;

    /*
     * If a name was specified, then search the history file for that name.
     * Remember the corresponding edit number if found.
     * Handle the magic 'oldest' and 'newest' edit names specially.
     */
    if (name)
    {
      if (endname)
          *endname = '\0';
      if (strcmp(name, OLDESTNAME) == 0)
      {
          edit = sc.firstedit;
          goto foundedit;
      }
      if (strcmp(name, NEWESTNAME) == 0)
      {
          edit = sc.lastedit;
          goto foundedit;
      }
#if 0
      if (fc.verbosity > VERBOSE_DEFAULT)
          error_raw("[Searching for edit name \"%s\"]", editname);
#endif
      curedit = sc.lastedit + 1;
      seekf(fp, sc.tablepos, sc.historyname);
      while (edit == 0)
      {
          if (--curedit < sc.firstedit)
          {
                  fclose(fp);
                  scp = sub_context_new();
                  sub_var_set_charstar(scp, "Module", sc.modulename);
                  sub_var_set_charstar(scp, "Name", editname);
                  fatal_intl
                  (
                scp,
             i18n("edit name \"$name\" does not exist for module \"$name\"")
                  );
          }
          cp = get_a_line(fp, NOSEEK, T_POSITION, sc.historyname);
          cp[sc.linelen - 1] = '\0';
          cp = getnumber(cp, &tempedit);
          cp = getnumber(cp, &temppos);
          if ((cp == NULL) || (tempedit != curedit))
          {
            scp = sub_context_new();
            sub_var_set_long(scp, "Number", curedit);
            fatal_with_filename
            (
                sc.historyname,
                scp,
                i18n("bad position line for edit $number")
            );
          }

          /*
           * Search the line for the edit name
           */
          while (*cp)
          {
            while (isspace((unsigned char)*cp))
                cp++;
            testname = cp;
            while (*cp && !isspace((unsigned char)*cp))
                cp++;
            *cp++ = '\0';
            if ((*testname == *name) && (strcmp(testname, name) == 0))
                edit = curedit;
          }
      }
    }

    /*
     * We either found the name, or else we just had a number.
     * Combine the two values, and complain if out of range.
     * When given just a number, negative means backwards from last one.
     */
foundedit:
    if ((edit == 0) && (reledit <= 0))
      edit = sc.lastedit;
    edit += reledit;
    if ((edit < sc.firstedit) || (edit > sc.lastedit))
    {
      fclose(fp);
      scp = sub_context_new();
      sub_var_set_charstar(scp, "Module", sc.modulename);
      sub_var_set_long(scp, "Number", edit);
      sub_var_set_long(scp, "First", sc.firstedit);
      sub_var_set_long(scp, "Last", sc.lastedit);
      fatal_intl
      (
          scp,
          i18n
          (
            "edit number $number for module \"$module\" is not in the "
            "range $first to $last"
          )
      );
    }
    return edit;
}


/*
 * Read in the position table from the history file and save it into memory.
 * This reserves the first entry of the table for a possible new entry.
 * The history file was already opened and the header line was read.
 * Returns a pointer to the beginning of the table.
 */
POS *
readpostable(FILE *fp)
{
    char            *cp;      /* current line of file */
    POS             *pp;      /* current position table */
    POS             *postable;      /* beginning of position table */
    long            edit;     /* current edit number */
    long            tempedit; /* temporary edit number */
    long            temppos;  /* temporary position */
    long            eofpos;   /* end of file position */
    int             len;      /* length of string */
    sub_context_ty  *scp;

#if 0
    if (fc.verbosity > VERBOSE_DEFAULT)
      error("[Saving old position table]");
#endif
    pp =
      (POS *)
      cm_alloc_and_check(sizeof(POS) * (sc.lastedit - sc.firstedit + 2));
    postable = pp;
    pp->p_pos = -1;
    pp->p_names = NULL;
    pp++;

    /*
     * Read the position table lines from the file and save the positions.
     * Save the names also if there are any.
     */
    seekf(fp, sc.tablepos, sc.historyname);
    for (edit = sc.lastedit; edit >= sc.firstedit; edit--)
    {
      cp = get_a_line(fp, NOSEEK, T_POSITION, sc.historyname);
      cp = getnumber(cp, &tempedit);
      cp = getnumber(cp, &temppos);
      if (cp == NULL)
      {
          scp = sub_context_new();
          sub_var_set_long(scp, "Number", edit);
          fatal_with_filename
          (
            sc.historyname,
            scp,
            i18n("bad numbers in position entry for edit $number")
          );
      }
      if (tempedit != edit)
      {
          scp = sub_context_new();
          sub_var_set_long(scp, "Number", edit);
          fatal_with_filename
          (
            sc.historyname,
            scp,
            i18n("wrong edit number in position entry for edit $number")
          );
      }
      pp->p_pos = temppos;
      pp->p_names = NULL;
      while (*cp == ' ')
          cp++;
      if (*cp != '\n')
      {
          /* have a name so save it */
          len = strlen(cp);
          cp[len - 1] = '\0';
          pp->p_names = allocstr((unsigned long) len);
          strcpy(pp->p_names, cp);
      }
      pp++;
    }

    /*
     * That should have been all of the position lines.
     * Now verify that the next line is the end of file line.
     */
    eofpos = ftell(fp);
    cp = get_a_line(fp, NOSEEK, T_EOF, sc.historyname);
    cp = getnumber(cp, &temppos);
    if (cp == NULL)
    {
      fatal_with_filename
      (
          sc.historyname,
          0,
          i18n("no position in end of file line")
      );
    }
    if (eofpos != temppos)
    {
      scp = sub_context_new();
      sub_var_set_long(scp, "Number", eofpos);
      fatal_with_filename
      (
          sc.historyname,
          scp,
          i18n("wrong position $number in end of file line")
      );
    }
    return postable;
}


/*
 * Find the position of the beginning of an edit in the history file,
 * position to it, and verify that it is correct.  Then if infoline is
 * non-NULL, read the first remark line for the edit (which is information
 * about the edit), and return that into infoline.  The file position is
 * left ready to read the remainder of the edit sequence.
 */
void
startedit(FILE *fp, long num, char *infoline)
{
    char            *cp;      /* current line of file */
    long            editnumber;     /* current edit number read */
    long            editpos;  /* position of beginning of edit */
    long            temp;     /* temporary value read */

    cp = get_a_line(fp, sc.tablepos, T_POSITION, sc.historyname);
    cp = getnumber(cp, &editnumber);
    if (cp == NULL)
    {
      fatal_with_filename
      (
          sc.historyname,
          0,
          i18n("no edit number in start of position table")
      );
    }
    if (editnumber != sc.lastedit)
    {
      fatal_with_filename
      (
          sc.historyname,
          0,
          i18n("wrong edit number at start of position table")
      );
    }
    while (editnumber != num)
    {
      editnumber--;
      cp = get_a_line(fp, NOSEEK, T_POSITION, sc.historyname);
      cp = getnumber(cp, &temp);
      if (cp == NULL)
      {
          fatal_with_filename
          (
                  sc.historyname,
                  0,
                  i18n("no edit number in position entry")
          );
      }
      if (temp != editnumber)
      {
          fatal_with_filename
          (
                  sc.historyname,
                  0,
                  i18n("non-decreasing position table entry")
          );
      }
    }
    cp = getnumber(cp, &editpos);
    if (cp == NULL)
      fatal_with_filename(sc.historyname, 0, i18n("no edit position"));
    cp = get_a_line(fp, editpos, T_BEGINEDIT, sc.historyname);
    cp = getnumber(cp, &temp);
    if (temp != editnumber)
    {
      fatal_with_filename
      (
          sc.historyname,
          0,
          i18n("not at beginning of correct edit")
      );
    }
    if (infoline == NULL)
      return;
    cp = get_a_line(fp, NOSEEK, T_REMARK, sc.historyname);
    strncpy(infoline, cp, MAX_INFO);
    infoline[MAX_INFO-1] = '\0';
    infoline[MAX_INFO-2] = '\0';
}


/*
 * Open the source file and verify that it has the correct version number
 * stored in the first line of it.  Further reads from the file will then
 * read in the real lines of the source file.
 *
 * The history (.e) file is binary, because we need to seek in it.
 * The source (.s) file is text, because we don't need to seek in it.
 * The input files are text, by definition.
 * The output files are text, by definition.
 */

FILE *
opensourcefile(void)
{
    FILE            *fp;      /* file handle for history file */
    char            *cp;      /* first line of file */
    long            tempedit; /* edit number read from first line */

    fp = fopen_and_check(sc.sourcename, "r");
    cp = get_a_line(fp, NOSEEK, T_BEGINEDIT, sc.sourcename);
    if ((getnumber(cp, &tempedit) == NULL))
    {
      fatal_with_filename(sc.sourcename, 0, i18n("no number in first line"));
    }
    if (tempedit != sc.lastedit)
    {
      sub_context_ty    *scp;

      scp = sub_context_new();
      sub_var_set_long(scp, "Number1", tempedit);
      sub_var_set_long(scp, "Number2", sc.lastedit);
      sub_var_set_charstar(scp, "Module", sc.modulename);
      fatal_with_filename
      (
          sc.sourcename,
          scp,
          i18n("edit $number1 instead of $number2 for module \"$module\"")
      );
    }
    return fp;
}


/*
 * Rename the temporary file with the EXT_NEW extension to the file with
 * the specified extension, deleting the destination file.  This is used
 * for atomically changing the history file (with extension EXT_HISTORY)
 * or the latest source file (with extension EXT_SOURCE).  This uses the
 * temporary file name with the extension EXT_OLD so that at all times
 * either the old or the new file is in existence.  Returns nonzero if
 * the rename failed.  Breaks should be off during this call.
 */
int
renamefiles(char *extension)
{
    string_ty       *destname;      /* name of destination file */
    string_ty       *newname; /* name of current new file */
    string_ty       *oldname; /* name of old file */
    int             result;

    result = -1;
#if 0
    if (fc.verbosity > VERBOSE_DEFAULT)
      error("[Renaming files and deleting old version source]");
#endif
    destname = str_format("%s%s", sc.basename, extension);
    newname = str_format("%s%s", sc.basename, EXT_NEW);
    oldname = str_format("%s%s", sc.basename, EXT_OLD);
    unlink(oldname->str_text);
    if (rename(destname->str_text, oldname->str_text) < 0)
      goto done;
    if (rename(newname->str_text, destname->str_text) < 0)
    {
      rename(oldname->str_text, destname->str_text);
      goto done;
    }
    unlink(oldname->str_text);
    result = 0;
done:
    str_free(destname);
    str_free(newname);
    str_free(oldname);
    return result;
}


/*
 * Make sure that a file name is new.  If not, ask to see if we can
 * overwrite it.  Returns nonzero if no.  If the forcewrite flag is
 * specified, then skip this check.  If the nowrite flag is set, then
 * never overwrite it.
 */
int
checknewfile(char *name)
{
    if (sc.forcewriteflag || (name == NULL))
      return 0;
    if (sc.nowriteflag)
      return 1;
    if (access(name, 0) < 0)
      return 0;
    printf("Output file \"%s\" already exists.  Ok to overwrite it? ", name);
    return queryuser();
}


/*
 * Read a yes-no response from the user after a question has been asked.
 * Returns nonzero on a NO or null response.
 */
int
queryuser(void)
{
    int             ch;       /* current input character */
    int             result;   /* result of question */

    fflush(stdout);
    result = -1;
    for (;;)
    {
      ch = getchar();
      if (ch == '\n')
          break;
      if ((result >= 0) || isspace(ch))
          continue;
      result = ((ch == 'y') || (ch == 'Y'));
    }
    return (result <= 0);
}


/*
 * Routine to read a single line from the edit file, and verify that
 * its type is as desired.  If type is negative, then it is not checked
 * and the whole line is returned.  If type is nonnegative, then it is
 * checked and skipped over, and NULL is returned if it is wrong.
 * Length of the line is returned in the global variable linelen.
 * If type is text, then the line is returned fully allocated.
 */

char *
get_a_line(FILE *fp, long seekpos, int type, const char *filename)
{
    char            *cp;
    long            pos;
    int             bin;            /* IGNORED: edit file should be OK */

    if (seekpos != NOSEEK)
      seekf(fp, seekpos, filename);
    pos = ftell(fp);
    cp = readlinef(fp, &sc.linelen, (type == T_TEXT), filename, &bin);
    if (cp == NULL)
      fatal_with_filename(filename, 0, i18n("premature end of file"));
    if (sc.linelen == 2)
    {
      /* pad out empty text lines */
      sc.linelen = 3;
      cp[1] = ' ';
      cp[2] = '\n';
      cp[3] = '\0';
    }
    if (fc.debugflag)
      printf("%5ld: %s", pos, cp);
    if (type < 0)
      return cp;
    sc.linelen -= 2;
    if ((sc.linelen <= 0) || (type != cp[0]) || (cp[1] != ' '))
      return NULL;
    return &cp[2];
}


/*
 * Routine to read a number from a string preceeded by optional spaces and
 * followed by a space or an end of line.  If the scanning is successful,
 * it is indirectly returned and the next position in the string is returned.
 * If the number was bad or if no string is given, NULL is returned.
 */
char *
getnumber(char *cp, long *value)
{
    int             nonum;          /* 1 if no number detected */
    int             isneg;          /* 1 if number is negative */

    if (cp == NULL)
      return NULL;
    nonum = 1;
    isneg = 0;
    *value = 0;
    while (isspace((unsigned char)*cp))
      cp++;
    isneg = (*cp == '-');
    if (isneg)
      cp++;
    while (isdigit((unsigned char)*cp))
    {
      *value = (*value * 10) + *cp++ - '0';
      if (*value < 0)
          return NULL;
      nonum = 0;
    }
    if (isneg)
      *value = -*value;
    if (nonum || (*cp && !isspace((unsigned char)*cp)))
      cp = NULL;
    return cp;
}


int
pathconf_name_max(char *path)
{
    long            result;

#ifdef _PC_NAME_MAX
    /*
     * The pathconf system call may return -1 without setting errno
     * on some systems.  Usually this means ``I don't know''.
     * Pre-set errno with a suitable ``I don't know'' value to cope
     * with this behaviour.
     */
    errno = EINVAL;
    result = pathconf(path, _PC_NAME_MAX);
    if
    (
      result < 0
    &&
      (
          errno == EINVAL
      ||
          errno == ENOSYS 
#ifdef EOPNOTSUPP
      ||
          errno == EOPNOTSUPP
#endif
      )
    )
    {
      /*
       * probably NFS mounted
       * (defualt to 14 if root is also NFS mounted)
       */
      path = "/";
      errno = EINVAL;
      result = pathconf(path, _PC_NAME_MAX);
      if
      (
          result < 0
      &&
          (
                  errno == EINVAL
          ||
                  errno == ENOSYS
#ifdef EOPNOTSUPP
          ||
                  errno == EOPNOTSUPP
#endif
          )
      )
      {
#if HAVE_LONG_FILE_NAMES
          result = 255;
#else
          result = 14;
#endif
      }
    }
    if (result < 0)
    {
      sub_context_ty    *scp;

      scp = sub_context_new();
      sub_errno_set(scp);
      sub_var_set_charstar(scp, "File_Name", path);
      fatal_intl(scp, i18n("pathconf(\"$filename\", {NAME_MAX}): $errno"));
    }
#else /* _PC_NAME_MAX */
#ifdef DOS
    result = 12;
#else
#if HAVE_LONG_FILE_NAMES
    result = 255;
#else
    result = 14;
#endif
#endif
#endif /* _PC_NAME_MAX */
    return result;
}

Generated by  Doxygen 1.6.0   Back to index