Logo Search packages:      
Sourcecode: magic version File versions  Download package

DBio.c

/*
 * DBio.c --
 *
 * Reading and writing of cells
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1985, 1990 Regents of the University of California. * 
 *     * Permission to use, copy, modify, and distribute this              * 
 *     * software and its documentation for any purpose and without        * 
 *     * fee is hereby granted, provided that the above copyright          * 
 *     * notice appear in all copies.  The University of California        * 
 *     * makes no representations about the suitability of this            * 
 *     * software for any purpose.  It is provided "as is" without         * 
 *     * express or implied warranty.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-7.5/database/DBio.c,v 1.16 2009/01/15 15:57:11 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __STDC__
#include <unistd.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/file.h>

#ifdef HAVE_DIRENT_H
#include <dirent.h>
#include <unistd.h>
#define direct dirent
#else
#include <sys/dir.h>
#endif

#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>

#ifdef HAVE_PATHS_H
#include <paths.h>
#endif

#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/utils.h"
#include "utils/hash.h"
#include "database/database.h"
#include "database/databaseInt.h"
#include "database/fonts.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "utils/tech.h"
#include "textio/textio.h"
#include "drc/drc.h"
#include "utils/undo.h"
#include "utils/malloc.h"
#include "utils/signals.h"

#ifndef _PATH_TMP
#define _PATH_TMP "/tmp"
#endif

extern char *Path;

/* Suffix for all Magic files */
char *DBSuffix = ".mag";

/* Magic units per lambda (2 integers, representing (n / d) */
int DBLambda[2] = {1, 1};

/* If set to FALSE, don't print warning messages. */
bool DBVerbose = TRUE;

/* Global name of backup file for this session */
static char *DBbackupFile = (char *)NULL;

/* Forward declarations */
char *dbFgets();
FILE *dbReadOpen();
int DBFileOffset;
bool dbReadLabels();
bool dbReadElements();
bool dbReadProperties();
bool dbReadUse();

/*
 * ----------------------------------------------------------------------------
 *
 *   file_is_not_writeable -- 
 *
 *  Check to see if file is not writeable. (wen-king@cs.caltech.edu)
 *  Modified to deal with changed semantics of access() (rajit@cs.caltech.edu)
 *
 * ---------------------------------------------------------------------------
 */

static int
file_is_not_writeable(name)
    char *name;
{
    struct stat sbuf;

    /* attempt to read stat buffer for file */

    if (lstat(name,&sbuf) < 0) return(-1);

    /* regular file? if not, error */
    if (!S_ISREG(sbuf.st_mode)) { errno = EACCES; return(-1); }

    /* can we write to it? */
    if (access(name, W_OK) < 0) return(-1);

    /* the OS thinks we can write to the file;
       but does the file think so?
    */
    if (geteuid() == sbuf.st_uid) {
      if (sbuf.st_mode & S_IWUSR) return (0);
      /* I own the file, but I don't have write permission */
      errno = EACCES;
      return (-1);
    }

    if (!(sbuf.st_mode & (S_IWOTH|S_IWGRP))) {
      errno = EACCES;
      return (-1);
    }

    return(0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbCellReadDef --
 *
 *
 * Read in the paint for a cell from its associated disk file.
 * If a filename for the cell is specified, we try to open it
 * somewhere in the search path.  Otherwise, we try the filename
 * already associated with the cell, or the name of the cell itself
 * as the name of the file containing the definition of the cell.
 *
 * Mark the cell definition as "read in" (CDAVAILABLE), and
 * recompute the bounding box.
 *
 *                      WARNING:
 *
 * It is the responsibility of the caller to call DBReComputeBbox(cellDef)
 * at a convenient place, as we do not set the bounding box of the cell def.
 * If we were to update the bounding box here, this CellDef would first have
 * to be ripped out of each parent subcell tile plane in which it appears, and
 * then relinked in after its bounding box had changed.  Since DBCellRead() may
 * be called from in the middle of a database search, however, the database
 * modification resulting from this could ruin the search context and crash
 * the system.
 *
 * Results:
 *    TRUE if the cell could be read successfully, FALSE
 *    otherwise.
 *
 * Side effects:
      Clears the cell's MODIFIED bit.
 *    Updates the tile planes for the cell definition.
 *    In the event of an error while reading in the cell,
 *    the external integer errno is set to the UNIX error
 *    encountered.
 *
 *    If newTechOk is TRUE and the cell's technology is different
 *    from the current one, the current technology is changed.
 *
 * Errors:
 *    If incomplete specs are given either for a rectangle or for
 *    a cell use, then we immediately stop reading the file.
 *
 * File Format:
 *
 *    1. The first line of the file contains the string "magic".
 *
 *    2. Next comes an optional technology line, with the format
 *    "tech <tech>".  <tech> is the technology of the cell.
 *
 *    3. Next comes an optional scale line, with the format
 *    "magscale <n> <d>".  <n> and <d> give the number of magic
 *    internal units per lambda as the ratio <n>/<d>.  If this
 *    line is absent, the scale ratio is assumed to be 1-to-1.
 *
 *    4. Next comes an optional line giving the cell's timestamp
 *    (the last time it or any of its children changed, as far as
 *    we know).  The syntax is "timestamp <value>", where <value>
 *    is an integer as returned by the library function time().
 *
 *    5. Next come groups of lines describing rectangles of the
 *    Magic tile types.  Each group is headed with a line of the
 *    form "<< layer >>".     The layer name is matched against the
 *    current technology.  Each line after the header has the
 *    format "rect <xbot> <ybot> <xtop> <ytop>".
 *    Nonmanhattan geometry is covered by the entry
 *    "try <xbot> <ybot> <xtop> <ytop> <dir>" with <dir> indicating
 *    the direction of the corner made by the right triangle.
 *    If the split tile contains more than one type, separate entries
 *    are output for each.
 *    
 *    6. Zero or more groups of lines describing cell uses.  Each group
 *    is of the form
 *          use <filename> <id>
 *          array <xlo> <xhi> <xsep> <ylo> <yhi> <ysep>
 *          timestamp <int>
 *          transform <a> <b> <c> <d> <e> <f>
 *          box <xbot> <ybot> <xtop> <ytop>
 *    Each group may be preceded by one or more separator lines.  Note
 *    that <id> is optional and is omitted if there is to be no
 *    instance id for the cell use.  If it is omitted, an instance
 *    identifier is generated internally.  The "array" line may be
 *    omitted     if the cell use is not an array.  The "timestamp" line is
 *    optional;  if present, it gives the last time the parent
 *    was aware that the child changed.
 *
 *    7. If the cell contains labels, then the labels are preceded
 *    by the line "<< labels >>".  Each label is one line of the form
 *          rlabel <layer> <xbot> <ybot> <xtop> <ytop> <position> <text>
 *           or
 *          flabel <layer> <xbot> <ybot> <xtop> <ytop> <position> <fontname>
 *                <size> <rotation> <offsetx> <offsety> <text>
 *           or
 *          label <layer> <x> <y> <position> <text>
 *    (the last form is obsolete and is for point labels only).
 *
 *    Ports are declared in the label line after the label they refer
 *    to.  The syntax is
 *          port <index> <dirs>
 *    where <dir> is a string with one to four characters "nsew",
 *    denoting the position of allowed connections to the port.  The
 *    index must be unique to each port in a cell.
 *
 *    9. Elements are saved in a section "<< elements >>".
 *
 *     10. The file is terminated by the line "<< end >>".
 *
 *    Note: be careful about any changes to this format:  there are
 *    several previous file formats still in use in old files, and
 *    the current one is backwardly compatible with all of them.
 *
 *
 * ----------------------------------------------------------------------------
 */

bool
dbCellReadDef(f, cellDef, name, ignoreTech)
    FILE *f;            /* The file, already opened by the caller */
    CellDef *cellDef;   /* Pointer to definition of cell to be read in */
    char *name;         /* Name of file from which to read definition.
                   * If NULL, then use cellDef->cd_file; if that
                   * is NULL try the name of the cell.
                   */
    bool ignoreTech;    /* If FALSE then the technology of the file MUST
                   * match the current technology, or else the
                   * subroutine will return an error condition
                   * without reading anything.  If TRUE, a
                   * warning will be printed if the technology
                   * names do not match, but an attempt will be
                   * made to read the file anyway.
                   */
{
    int cellStamp = 0, rectCount = 0, rectReport = 10000;
    char line[2048], tech[50], layername[50];
    PaintResultType *ptable;
    bool result = TRUE, scaleLimit = FALSE;
    Rect *rp;
    int c;
    TileType type, rtype, loctype;
    TileTypeBitMask *rmask, typemask;
    Plane *plane;
    Rect r;
    int n = 1, d = 1;

    /*
     * It's very important to disable interrupts during the body of
     * this routine.  Otherwise, if the user types the interrupt key
     * only part of the file will be read in, and if he then writes
     * the cell out, the disk copy will get trashed.
     */
    SigDisableInterrupts();

    /*
     * Process the header of the Magic file:
     * make sure that this is a Magic-format file and that it
     * has the right technology.  Magic files have a first line
     * of "magic".
     */
    if (dbFgets(line, sizeof line, f) == NULL)
      goto badfile;

    if (strncmp(line, "magic", 5) != 0)
    {
      TxError("First line in file must be \"magic\"; instead saw: %s", line);
      goto badfile;
    }
    if (dbFgets(line, sizeof line, f) == NULL)
      goto badfile;

    if ((line[0] != '<') && (line[0] != '\0'))
    {
      if (sscanf(line, "tech %49s", tech) != 1)
      {
          TxError("Malformed \"tech\" line: %s", line);
          goto badfile;
      }
      if (strcmp(DBTechName, tech) != 0)
      {
          TxError("Cell %s has technology \"%s\", but current "
                  "technology is \"%s\"\n", cellDef->cd_name,
                  tech, DBTechName);
          if (ignoreTech)
            TxPrintf("Will attempt to read cell anyway.\n");
          else
          {
            TxError("Use command \"tech load\" if you want to switch"
                  " technologies, or use\n");
            TxError("\"cellname delete %s\" and \"load %s -force\" to"
                  " force the cell to load as technology %s\n",
                  cellDef->cd_name, cellDef->cd_name, DBTechName);
            SigEnableInterrupts();
            return (FALSE);
          }
      }
      if (dbFgets(line, sizeof line, f) == NULL)
          goto badfile;
      
      if (line[0] == 'm')
      {
          if (!strncmp(line, "magscale", 8))
          {
            if (sscanf(line, "magscale %d %d", &n, &d) != 2)
            {
                TxError("Expected two arguments to magscale;  ignoring\n");
                n = d = 1;
            }
          }
            
          /* For backward compatibility, accept (and throw away) lines
           * whose first word is "maxlabscale".
           */
          else if (!strncmp(line, "maxlabscale", 11))
            TxError("Deprecated keyword \"maxlabscale\" in input file.\n");
          else 
            TxError("Expected magscale but got: %s", line);
          if (dbFgets(line, sizeof line, f) == NULL)
            goto badfile;
      }
      if (line[0] == 't')
      {
          if (sscanf(line, "timestamp %d", &cellStamp) != 1)
            TxError("Expected timestamp but got: %s", line);
          if (dbFgets(line, sizeof line, f) == NULL)
            goto badfile;
      }
    }

    /*
     * Determine scalefactor between file and database.  Adjust scale of the
     * file and/or the database accordingly.
     */

    n *= DBLambda[1];
    d *= DBLambda[0];
    ReduceFraction(&n, &d);
    scaleLimit = CIFTechLimitScale(n, d);

    if (!scaleLimit && (d > 1))
    {
      CIFTechInputScale(1, d, TRUE);
      CIFTechOutputScale(1, d);
      DRCTechScale(1, d);
      PlowAfterTech();
      ExtTechScale(1, d);
      WireTechScale(1, d);
#ifdef LEF_MODULE
      LefTechScale(1, d);
#endif
#ifdef ROUTE_MODULE
      RtrTechScale(1, d);
      MZAfterTech();
      IRAfterTech();
#endif
      DBScaleEverything(d, 1);
      DBLambda[1] *= d;
      TxPrintf("Input cell %s scales magic internal geometry by factor of %d\n",
                  cellDef->cd_name, d);
      d = 1;
    }
    if (n > 1)
    {
      TxPrintf("Scaled magic input cell %s geometry by factor of %d", 
            cellDef->cd_name, n);
      if (d > 1) 
      {
          TxPrintf("/ %d", d);
          TxError("Warning:  Geometry may be lost because internal grid"
                  "cannot be reduced.\n");
      }
      TxPrintf("\n");
    }

    /*
     * Next, get the paint, subcells, and labels for this cell.
     * While we are generating paints to the database, we want
     * to disable the undo package.
     */
    rp = &r;
    UndoDisable();
    while (TRUE)
    {
      /*
       * Read the header line to get the layer name, then read as
       * many rectangles as are specified on consecutive lines.
       * If not a layer header line, then it should be a cell
       * use header line.
       */
      if (sscanf(line, "<< %s >>", layername) != 1)
      {
          if (!dbReadUse(cellDef, line, sizeof line, f, n, d))
            goto badfile;
          continue;
      }

      TTMaskZero(&typemask);
      rmask = &typemask;
      type = DBTechNameType(layername);
      if (type < 0)
      {
          /*
           * Look for special layer names:
           *            labels         -- begins a list of labels and ports
           *            elements   -- begins a list of elements
           *            properties -- begins a list of properties
           *            end      -- marks the end of this file
           */
          if (!strcmp(layername, "labels"))
          {
            if (!dbReadLabels(cellDef, line, sizeof line, f, n, d)) goto badfile;
            continue;
          }
          else if (!strcmp(layername, "elements"))
          {
            if (!dbReadElements(cellDef, line, sizeof line, f, n, d)) goto badfile;
            continue;
          }
          else if (!strcmp(layername, "properties"))
          {
            if (!dbReadProperties(cellDef, line, sizeof line, f)) goto badfile;
            continue;
          }
          else if (!strcmp(layername, "end")) goto done;
          else
            DBTechNoisyNameMask(layername, rmask);

          //      TxError("Unknown layer %s ignored in %s\n", layername,
          //            cellDef->cd_name);
      }

      /*
       * Record presence of material in cell.
       */
          
      if (DBPlane(type) > 0)
      {
          if (type < DBNumUserLayers)
          {
            TTMaskSetType(&cellDef->cd_types, type);
            TTMaskSetType(rmask, type);
          }
          else
          {
            /* Separate stacked contact types into their components */
              rmask = DBResidueMask(type);
            for (rtype = TT_SPACE + 1; rtype < DBNumUserLayers; rtype++)
                if (TTMaskHasType(rmask, rtype))
                  TTMaskSetType(&cellDef->cd_types, type);
          }
      }

      /*
       * The following loop is executed once for each line
       * in the file beginning with 'r'.
       */
nextrect:
#ifdef NONMANHATTAN 
      while (((c = getc(f)) == 'r') || (c == 't'))
      {
          TileType dinfo;
          int dir;
#else
      while (((c = getc(f)) == 'r') || (c == 't'))
      {
#endif
          /*
           * GetRect actually reads the rest of the line up to
           * a trailing newline or EOF.
           */
          if (c == 't')
          {
#ifdef NONMANHATTAN  
            if ((dir = GetRect(f, 3, rp, n, d)) == 0) goto badfile;
            dir >>= 1;
            dinfo = TT_DIAGONAL | ((dir & 0x2) ? TT_SIDE : 0) |
                  ((((dir & 0x2) >> 1) ^ (dir & 0x1)) ?
                  TT_DIRECTION : 0);
#else
            if (!GetRect(f, 3, rp, n, d)) goto badfile;
            TxPrintf("Error: cell %s contains non-manhattan geometry\n",
                  cellDef->cd_name);
            TxPrintf("unsupported by this magic executable.  Skipping.\n");
            fflush(stdout);
            continue;
#endif
          }
            else  
          {
#ifdef NONMANHATTAN  
            dinfo = 0;
#endif
            if (!GetRect(f, 4, rp, n, d)) goto badfile;
          }

          if ((++rectCount % rectReport == 0) && DBVerbose)
          {
            TxPrintf("%s: %d rects\n", cellDef->cd_name, rectCount);
            fflush(stdout);
          }

          /*
           * Only add a new rectangle if it is non-null, and if the
           * layer is reasonable.
           */
          if (!GEO_RECTNULL(rp))
          {
            /*------------------------------------------------------*/
            /* The complicated use of DBPaintPlane() has been     */
            /* replaced with a simpler call to DBPaint().  HOWEVER      */
            /* there are instances where this can cause unexpected      */
            /* behavior.  Namely, magic-7.2 allows, e.g., m456c   */
            /* painted over m3c;  this apparently works although  */
            /* image on the metal4 plane cannot represent both    */
            /* contact types.  magic-7.3 disallows this, so for the     */
            /* same technology file (no stackable types), m3c gets      */
            /* eliminated by the paint rules!  This condition is  */
            /* left AS-IS.  Caveat end-user.                */
            /*                            Tim 7/8/04  */
            /*------------------------------------------------------*/

            for (rtype = TT_SPACE + 1; rtype < DBNumUserLayers; rtype++)
            {
                if (TTMaskHasType(rmask, rtype))
                {
                  loctype = rtype;
#ifdef NONMANHATTAN
                  if (dinfo & TT_SIDE) loctype <<= 14;
                  loctype |= dinfo;
#endif
                  DBPaint(cellDef, rp, loctype);
                }
            }
          }
      }

      /*
       * Ignore comments.
       * Note we use fgets() since we only want to discard this line.
       */
      if (c == '#')
      {
          (void) fgets(line, sizeof line, f);
          goto nextrect;
      }

      /*
       * We reach here if the first character on a line is not
       * 'r', meaning that we have reached the end of this
       * section of rectangles.
       */
      if (c == EOF) goto badfile;
      line[0] = c;
      if (dbFgets(&line[1], sizeof line - 1, f) == NULL) goto badfile;
    }

done:

    cellDef->cd_flags &= ~(CDMODIFIED|CDBOXESCHANGED|CDGETNEWSTAMP);

    /*
     * Assign instance-ids to cell uses that didn't contain
     * explicit use identifiers.  Warn about duplicate instance
     * ids as well, changing these to unique ones.  We do this
     * here instead of on-the-fly during cell read-in, to avoid
     * an N**2 algorithm that blows up for large #s of subcells.
     */
    DBGenerateUniqueIds(cellDef, TRUE);

    /*
     * If the timestamp in the cell didn't match expectations,
     * notify the timestamp manager.  Note:  it's possible that
     * this cell is used only as the root of windows.  If that
     * is the case, then don't trigger a timestamp mismatch (we
     * can tell this by whether or not there are any parent uses
     * with non-null parent defs.  If the cell on disk had a zero
     * timestamp, then force the cell to be written out with a
     * correct timestamp.
     */
    if ((cellDef->cd_timestamp != cellStamp) || (cellStamp == 0))
    {
      CellUse *cu;
      for (cu = cellDef->cd_parents; cu != NULL; cu = cu->cu_nextuse)
      {
          if (cu->cu_parent != NULL)
          {
            DBStampMismatch(cellDef, &cellDef->cd_bbox);
            break;
          }
      }
    }
    cellDef->cd_timestamp = cellStamp;
    if (cellStamp == 0)
    {
      TxError("\"%s\" has a zero timestamp; it should be written out\n",
          cellDef->cd_name);
      TxError("    to establish a correct timestamp.\n");
      cellDef->cd_flags |= CDSTAMPSCHANGED|CDGETNEWSTAMP;
    }

    UndoEnable();
    DRCCheckThis(cellDef, TT_CHECKPAINT, (Rect *) NULL);
    SigEnableInterrupts();
    return (result);

badfile:
    TxError("File %s contained format error\n", cellDef->cd_name);
    DRCCheckThis(cellDef, TT_CHECKPAINT, (Rect *) NULL);
    result = FALSE;
    goto done;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBRemoveBackup() --
 *
 * Remove any crash backup file.  This routine is normally called either on
 * normal program exit, or after the successful read-in of a crash backup.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    If DBbackupFile is non-NULL, then memory is freed, and the backup
 *    file is removed from the filesystem temp directory.  Otherwise,
 *    nothing happens.
 *    
 * ----------------------------------------------------------------------------
 */

void
DBRemoveBackup()
{
    if (DBbackupFile != (char *)NULL)
    {
      unlink(DBbackupFile);
      freeMagic(DBbackupFile);
      DBbackupFile = (char *)NULL;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBFileRecovery() --
 *
 * Get the name of the first backup file found in the /tmp directory,
 * prompt for action, and if action is "read", then load it.
 *
 * Results:
 *    None.
 *
 * Side Effects:
 *    Prompts for action.
 *    Loads the contents of the backup file into the database.
 *
 * ----------------------------------------------------------------------------
 */

void
DBFileRecovery(filename)
    char *filename;
{
    DIR *cwd;
    struct direct *dp;
    struct stat sbuf;
    uid_t userid = getuid();
    time_t recent = 0;
    char *snptr, *tempdir, tempname[256];
    int pid;
    static char *actionNames[] = {"read", "cancel", 0 };
    char *prompt;
    int action;

    if (DBbackupFile != NULL)
    {
      TxError("Error:  Backup file in use for current session.\n");
      return;
    }

    if (filename == NULL)
    {

      tempdir = getenv("TMPDIR");
      if (tempdir == NULL) tempdir = _PATH_TMP;

      cwd = opendir(tempdir);
      if (cwd == NULL) return;

      /* Find the most recent crash file in the temp directory */

      while ((dp = readdir(cwd)) != NULL)
      {
          char *doslash = (tempdir[strlen(tempdir) - 1] == '/') ? "" : "/";
          sprintf(tempname, "%s%s%s", tempdir, doslash, dp->d_name);
          snptr = tempname + strlen(tempdir);
          if (!strncmp(snptr, "MAG", 3))
          {
            char *dotptr = strchr(snptr, '.');
              pid = -1;
            if (dotptr && dotptr > snptr + 3)
            {
                *dotptr = '\0';
                if (sscanf(snptr + 3, "%d", &pid) != 1)
                  pid = -1;
                *dotptr = '.';
            }
            if ((!stat(tempname, &sbuf)) && (sbuf.st_uid == userid))
            {
                if ((recent == 0) || (sbuf.st_ctime > recent))
                {
                  /* If the PID encoded in the name belongs to an */
                  /* active process, then we should not try to    */
                  /* open it.                         */

                  if (pid != -1)
                      if (SigCheckProcess(pid) == TRUE)
                        continue;

                  recent = sbuf.st_ctime;
                  DBbackupFile = StrDup(&DBbackupFile, tempname);
                }
            }  
          } 
      }
      closedir(cwd);
    }
    else
    {
      DBbackupFile = StrDup(&DBbackupFile, filename);
      recent = 1;
    }

    if (recent > 0)
    {             /* There exists at least one temporary file     */
                  /* belonging to this user.  Ask to recover      */
                  /* the most recent one.                   */

      prompt = TxPrintString("Recover from backup file %s?", DBbackupFile);
      action = TxDialog(prompt, actionNames, 0);

      switch(action)
      {
          case 0: /* Read */
            if (DBReadBackup(DBbackupFile) == TRUE)
                DBRemoveBackup();
            break;
          case 1: /* Cancel */
            break;
      }
    }

    /* Make sure we've cleared out the backup filename */

    if (DBbackupFile != NULL)
    {
      freeMagic(DBbackupFile);
      DBbackupFile = (char *)NULL;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBReadBackup --
 *
 * This file reads a backup file containing multiple cell definitions.
 *
 * Results:
 *    TRUE if the backup file was read successfully, FALSE otherwise.
 *
 * Side Effects:
 *    Side effects are the side effects caused by dbCellReadDef()
 *    (see above).  As many cells as are listed in the backup file
 *    are created and added to the database.
 *
 * ----------------------------------------------------------------------------
 */

bool
DBReadBackup(name)
    char *name;         /* Name of the backup file */
{
    FILE *f;
    char *filename, *rootname, *chrptr;
    char line[256];
    CellDef *cellDef;
    bool result = TRUE;

    if ((f = PaOpen(name, "r", NULL, "", NULL, NULL)) == NULL)
    {
      TxError("Cannot open backup file \"%s\"\n", name);
      return FALSE;
    }

    if (dbFgets(line, sizeof(line), f) == NULL)
    {
      TxError("Bad backup file %s; can't restore!\n", name);
      return FALSE;
    }

    while (strncmp(line, "end", 3) != 0)
    {
      if (strncmp(line, "file", 4) == 0)
      {
          filename = line + 4;

          /* Remove any trailing return character */
          chrptr = strrchr(filename, '\n');
          if (chrptr != NULL) *chrptr = '\0';

          /* Remove any trailing file extension */
          chrptr = strstr(filename, ".mag");
          if (chrptr != NULL) *chrptr = '\0';
          rootname = strrchr(filename, '/');
          if (rootname == NULL)
            rootname = filename;
          else
            rootname++;

          /* Remove any leading whitespace */
          while (isspace(*rootname) && *rootname != '\0') rootname++;
          if (strlen(rootname) == 0) return FALSE;

          cellDef = DBCellLookDef(rootname);
          if (cellDef == (CellDef *)NULL)
            cellDef = DBCellNewDef(rootname, (char *)NULL);

          cellDef->cd_flags &= ~CDNOTFOUND;
          cellDef->cd_flags |= CDAVAILABLE;

          if (dbCellReadDef(f, cellDef, filename, TRUE) == FALSE)
            return FALSE;

          if (dbFgets(line, sizeof(line), f) == NULL)
          {
            TxError("Error in backup file %s; partial restore only!\n",
                  name);
            return FALSE;
          }
      }
      else
      {
          TxError("Error in backup file %s; expected keyword"
                  " \"file\", got \"%s\"!\n", name, line);
          return FALSE;
      }
    }
    chrptr = strrchr(line, '\n');
    if (chrptr > line + 4)
    {
      /* Remove the trailing return character */
      *chrptr = '\0';
      DBWreload(line + 4);
    }
    return TRUE;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBCellRead --
 *
 * This is the wrapper for DBCellReadDef.  The routine has been divided into
 * parts so that a single backup file can be made and recovered, and in
 * preparation for allowing certain cell definitions to be in-lined into the
 * output file (such as polygonXXXXX cells generated by the gds read-in).
 *
 * Results:
 *    TRUE if the cell could be read successfully, FALSE
 *    otherwise.  If the cell is already read in, TRUE is
 *    also returned.
 *
 * Side Effects:
 *    If the cell is already marked as available (CDAVAILABLE), this
 *    routine     does nothing (has no side effects).
 *
 *    Otherwise, side effects are the side effects caused by
 *    dbCellReadDef() (see above).  In addition, the cell
 *    definition is marked as available.
 *
 * ----------------------------------------------------------------------------
 */

bool
DBCellRead(cellDef, name, ignoreTech)
    CellDef *cellDef;   /* Pointer to definition of cell to be read in */
    char *name;         /* Name of file from which to read definition.
                   * If NULL, then use cellDef->cd_file; if that
                   * is NULL try the name of the cell.
                   */
    bool ignoreTech;    /* If FALSE then the technology of the file MUST
                   * match the current technology, or else the
                   * subroutine will return an error condition
                   * without reading anything.  If TRUE, a
                   * warning will be printed if the technology
                   * names do not match, but an attempt will be
                   * made to read the file anyway.
                   */
{
    FILE *f;
    bool result;

    if (cellDef->cd_flags & CDAVAILABLE)
      result = TRUE;

    else if ((f = dbReadOpen(cellDef, name, TRUE)) == NULL)
      result = FALSE;

    else
    {
      result = (dbCellReadDef(f, cellDef, name, ignoreTech));
#ifdef FILE_LOCKS
      /* Close files that were locked by another user */
      if (cellDef->cd_fd == -1) fclose(f);
#else
      /* When using fcntl() to enforce file locks, we can't */
      /* close the file descriptor without losing the lock. */
      fclose(f);
#endif
    }
    return result;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbReadOpen --
 *
 * Open the file containing the cell we are going to read.
 * If a filename for the cell is specified ('name' is non-NULL),
 * we try to open it somewhere in the search path.  Otherwise,
 * we try the filename already associated with the cell, or the
 * name of the cell itself as the name of the file containing
 * the definition of the cell.
 *
 * If 'setFileName' is TRUE, then cellDef->cd_file will be updated
 * to point to the name of the file from which the cell was loaded.
 *
 * Results:
 *    Returns an open FILE * if successful, or NULL on error.
 *
 * Side effects:
 *    Opens a FILE.  Leaves cellDef->cd_flags marked as
 *    CDAVAILABLE, with the CDNOTFOUND bit clear, if we
 *    were successful.
 *
 * ----------------------------------------------------------------------------
 */

FILE *
dbReadOpen(cellDef, name, setFileName)
    CellDef *cellDef;   /* Def being read */
    char *name;         /* Name if specified, or NULL */
    bool setFileName;   /* If TRUE then cellDef->cd_file should be updated
                   * to point to the name of the file from which the
                   * cell was loaded.
                   */
{
    FILE *f;
    char *filename, *realname;
    bool is_locked;

#ifdef FILE_LOCKS
    if (cellDef->cd_fd != -1)
    {
      close(cellDef->cd_fd);
      cellDef->cd_fd = -1;
    }
#endif

    if (name != (char *) NULL)
    {
      f = PaLockOpen(name, "r", DBSuffix, Path,
                  CellLibPath, &filename, &is_locked);
    }
    else if (cellDef->cd_file != (char *) NULL)
    {
      f = PaLockOpen(cellDef->cd_file, "r", "", ".",
                  (char *) NULL, &filename, &is_locked);
    }
    else
    {
      f = PaLockOpen(cellDef->cd_name, "r", DBSuffix, Path,
                  CellLibPath, &filename, &is_locked);
    }

    if (f == NULL)
    {
      /* Don't print another message if we've already tried to read it */
      if (cellDef->cd_flags & CDNOTFOUND)
          return ((FILE *) NULL);

      if (name != (char *) NULL)
          TxError("File %s%s couldn't be found\n", name, DBSuffix);
      else if (cellDef->cd_file != (char *) NULL)
          TxError("File %s couldn't be found\n", cellDef->cd_file);
      else {
          TxError("Cell %s couldn't be found\n", cellDef->cd_name);
          realname = (char *) mallocMagic((unsigned) (strlen(cellDef->cd_name)
                  + strlen(DBSuffix) + 1));
          (void) sprintf(realname, "%s%s", cellDef->cd_name, DBSuffix);
          cellDef->cd_file = StrDup(&cellDef->cd_file, realname);
      }
      cellDef->cd_flags |= CDNOTFOUND;
      return ((FILE *) NULL);
    }

#ifdef FILE_LOCKS
    else
    {
      if (file_is_not_writeable(filename) || (is_locked == TRUE))
      {
          cellDef->cd_flags |= CDNOEDIT;
          if ((is_locked == FALSE) && DBVerbose)
            TxPrintf("Warning: cell <%s> from file %s is not writeable\n",
                  cellDef->cd_name, filename);
      }
      else
          cellDef->cd_flags &= ~CDNOEDIT;

      if (is_locked == FALSE)
          cellDef->cd_fd = fileno(f);
      cellDef->cd_flags &= ~CDNOTFOUND;
    }
#else
    if (file_is_not_writeable(filename) && DBVerbose)
      TxPrintf("Warning: cell <%s> from file %s is not writeable\n",
            cellDef->cd_name, filename);
    TxFlushOut();

    cellDef->cd_flags &= ~CDNOTFOUND;
#endif
    if (setFileName)
      (void) StrDup(&cellDef->cd_file, filename);
    cellDef->cd_flags |= CDAVAILABLE;
    return (f);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBTestOpen --
 *
 * Check whether or not a database file can be found on disk.
 *
 * Results:
 *    Returns TRUE if available, FALSE if not.
 *
 * Side effects:
 *    Full pathname is returned in fullPath (if non-NULL)
 *
 * ----------------------------------------------------------------------------
 */

bool
DBTestOpen(name, fullPath)
    char *name;
    char **fullPath;
{
    FILE *f;

    f = PaLockOpen(name, "r", DBSuffix, Path, CellLibPath,
            fullPath, (bool *)NULL);

    if (f != NULL)
    {
      fclose(f);
      return TRUE;
    }
    return FALSE;
}


/*
 * ----------------------------------------------------------------------------
 *
 * dbReadUse --
 *
 * Read a single cell use specification.  Create a new cell
 * use that is a child of cellDef.  Create the def for this
 * child use if it doesn't already exist.
 *
 * On input, 'line' contains the "use" line; on exit, 'line'
 * contains the next line in the input after the "use".
 *
 * Results:
 *    Returns TRUE normally, or FALSE on error or EOF.
 *
 * Side effects:
 *    See above.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbReadUse(cellDef, line, len, f, scalen, scaled)
    CellDef *cellDef;   /* Cell whose cells are being read */
    char *line;         /* Line containing "use ..." */
    int len;            /* Size of buffer pointed to by line */
    FILE *f;            /* Input file */
    int scalen;         /* Multiply values in file by this */
    int scaled;         /* Divide values in file by this */
{
    int xlo, xhi, ylo, yhi, xsep, ysep, childStamp;
    int absa, absb, absd, abse;
    char cellname[1024], useid[1024];
    CellUse *subCellUse;
    CellDef *subCellDef;
    Transform t;
    Rect r;
    bool locked;

    if (strncmp(line, "use", 3) != 0)
    {
      TxError("Expected \"use\" line but saw: %s", line);
      return (FALSE);
    }

    useid[0] = '\0';
    if (sscanf(line, "use %1023s %1023s", cellname, useid) < 1)
    {
      TxError("Malformed \"use\" line: %s", line);
      return (FALSE);
    }

    locked = (useid[0] == CULOCKCHAR) ? TRUE : FALSE;

    if (dbFgets(line, len, f) == NULL)
      return (FALSE);

    if (strncmp(line, "array", 5) == 0)
    {
      if (sscanf(line, "array %d %d %d %d %d %d",
            &xlo, &xhi, &xsep, &ylo, &yhi, &ysep) != 6)
      {
          TxError("Malformed \"array\" line: %s", line);
          return (FALSE);
      }
      if (scalen > 1)
      {
          xsep *= scalen;
          ysep *= scalen;
      }
      if (scaled > 1)
      {
          xsep /= scaled;
          ysep /= scaled;
      }
      if (dbFgets(line, len, f) == NULL)
          return (FALSE);
    }
    else
    {
      xlo = ylo = 0;
      xhi = yhi = 0;
      xsep = ysep = 0;
    }

    if (strncmp(line, "timestamp", 9) == 0)
    {
      if (sscanf(line, "timestamp %d", &childStamp) != 1)
      {
          TxError("Malformed \"timestamp\" line: %s", line);
          return (FALSE);
      }
      if (dbFgets(line, len, f) == NULL)
          return (FALSE);
    } else childStamp = 0;

    if (sscanf(line, "transform %d %d %d %d %d %d",
          &t.t_a, &t.t_b, &t.t_c, &t.t_d, &t.t_e, &t.t_f) != 6)
    {
badTransform:
      TxError("Malformed or illegal \"transform\" line: %s", line);
      return (FALSE);
    }

    /*
     * Sanity check for transform.
     * Either a == e == 0 and both abs(b) == abs(d) == 1,
     *     or b == d == 0 and both abs(a) == abs(e) == 1.
     */
    if (t.t_a == 0)
    {
      absb = t.t_b > 0 ? t.t_b : -t.t_b;
      absd = t.t_d > 0 ? t.t_d : -t.t_d;
      if (t.t_e != 0 || absb != 1 || absd != 1)
          goto badTransform;
    }
    else
    {
      absa = t.t_a > 0 ? t.t_a : -t.t_a;
      abse = t.t_e > 0 ? t.t_e : -t.t_e;
      if (t.t_b != 0 || t.t_d != 0 || absa != 1 || abse != 1)
          goto badTransform;
    }

    if (dbFgets(line, len, f) == NULL)
      return (FALSE);

    if (sscanf(line, "box %d %d %d %d",
          &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop) != 4)
    {
      TxError("Malformed \"box\" line: %s", line);
      return (FALSE);
    }

    if (scalen > 1)
    {
      t.t_c *= scalen;
      t.t_f *= scalen;
      r.r_xbot *= scalen;
      r.r_ybot *= scalen;
      r.r_xtop *= scalen;
      r.r_ytop *= scalen;
    }
    if (scaled > 1)
    {
      t.t_c /= scaled;
      t.t_f /= scaled;
      r.r_xbot /= scaled;
      r.r_ybot /= scaled;
      r.r_xtop /= scaled;
      r.r_ytop /= scaled;
    }

    /*
     * Set up cell use.
     * If the definition for this use has not been read in,
     * make a dummy one that's marked not available.  For now,
     * don't change the bounding box if the cell already exists
     * (we'll fix it below when handling timestamp problems).
     */
    subCellDef = DBCellLookDef(cellname);
    if (subCellDef == (CellDef *) NULL)
    {
      subCellDef = DBCellNewDef(cellname, (char *) NULL);
      subCellDef->cd_timestamp = childStamp;

      /* Make sure rectangle is non-degenerate */
      if (GEO_RECTNULL(&r))
      {
          TxPrintf("Subcell has degenerate bounding box: %d %d %d %d\n",
                r.r_xbot, r.r_ybot, r.r_xtop, r.r_ytop);
          TxPrintf("Adjusting bounding box of subcell %s of %s",
                cellname, cellDef->cd_name);
          if (r.r_xtop <= r.r_xbot) r.r_xtop = r.r_xbot + 1;
          if (r.r_ytop <= r.r_ybot) r.r_ytop = r.r_ybot + 1;
          TxPrintf(" to %d %d %d %d\n",
                r.r_xbot, r.r_ybot, r.r_xtop, r.r_ytop);
      }
      subCellDef->cd_bbox = r;
    }
    else if (DBIsAncestor(subCellDef, cellDef))
    {
      /*
       * Watchout for attempts to create circular structures.
       * If this happens, disregard the subcell.
       */
      TxPrintf("Subcells are used circularly!\n");
      TxPrintf("Ignoring subcell %s of %s.\n", cellname,
          cellDef->cd_name);
      goto nextLine;
    }

    
    subCellUse = DBCellNewUse(subCellDef, (useid[0]) ?
            ((locked) ? useid + 1 : useid) : (char *) NULL);

    if (locked) subCellUse->cu_flags |= CU_LOCKED;
    /*
     * Instead of calling DBLinkCell for each cell, DBGenerateUniqueIds()
     * gets called for the entire cell at the end.
     */

    DBMakeArray(subCellUse, &GeoIdentityTransform,
                      xlo, ylo, xhi, yhi, xsep, ysep);
    DBSetTrans(subCellUse, &t);

    /*
     * Link the subcell into the parent.
     * This should be the only place where a cell use
     * gets created as part of the database, and not recorded
     * on the undo list (because undo is disabled while the
     * cell is being read in).
     */
    DBPlaceCell(subCellUse, cellDef);

    /*
     * Things get real tricky if the our guess about the
     * timestamp doesn't match the existing timestamp in
     * subCellDef.  This can be because (1) subCellDef has
     * been read in, so we're just a confused parent, or
     * (2) the cell hasn't been read in yet, so two parents
     * disagree, and it's not clear which is correct.  In
     * either event, call the timestamp manager with our guess
     * area, since it seems to be wrong, and in case (2) also
     * call the timestamp manager with the existing area, since
     * that parent is probably confused too.  If the cell isn't
     * available, set the timestamp to zero to force mismatches
     * forever until the cell gets read from disk.
     */
    if ((childStamp != subCellDef->cd_timestamp) || (childStamp == 0))
    {
      DBStampMismatch(subCellDef, &r);
      if (!(subCellDef->cd_flags & CDAVAILABLE))
          subCellDef->cd_timestamp = 0;
      else DBStampMismatch(subCellDef, &subCellDef->cd_bbox);
    }

nextLine:
    return (dbFgets(line, len, f) != NULL);
}


/*
 * ----------------------------------------------------------------------------
 *
 * dbReadProperties --
 *
 * Starting with the line << properties >>, read properties for 'cellDef'
 * up to the end of the properties section.  On exit, 'line' contains
 * the line that terminated the properties section, which will be either
 * a line of the form "<< something >>" or one beginning with a
 * character other than 'r', 'l', or 't'.
 *
 * Results:
 *    Returns TRUE normally, or FALSE on error or EOF.
 *
 * Side effects:
 *    See above.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbReadProperties(cellDef, line, len, f)
    CellDef *cellDef;   /* Cell whose elements are being read */
    char *line;         /* Line containing << elements >> */
    int len;            /* Size of buffer pointed to by line */
    FILE *f;            /* Input file */
{
    char propertyname[128], propertyvalue[128], *storedvalue;
    int ntok;

    /* Get first element line */
    if (dbFgets(line, len, f) == NULL) return (FALSE);

    while (TRUE)
    {
      /* Skip blank lines */
      while (line[0] == '\0')
          if (dbFgets(line, len, f) == NULL)
            return (TRUE);

      /* Stop when at end of properties section (currently, only "string"
       * is defined)
       */
      if (line[0] != 's') break;

      /*
       * Properties may only be "string", for now.  This may be the only
       * property type ever needed.
       */
      if (line[0] == 's')
      {
          if ((ntok = sscanf(line, "string %127s %127[^\n]",
                propertyname, propertyvalue)) != 2)
          {
            TxError("Skipping bad property line: %s", line);
            goto nextproperty;
          }
          storedvalue = StrDup((char **)NULL, propertyvalue);
          (void) DBPropPut(cellDef, propertyname, storedvalue);

          /* Go ahead and process the vendor GDS property */
          if (!strcmp(propertyname, "GDS_FILE"))
            cellDef->cd_flags |= CDVENDORGDS;
      }

nextproperty:
      if (dbFgets(line, len, f) == NULL)
          break;
    }

    return (TRUE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbReadElements --
 *
 * Starting with the line << elements >>, read elements for 'cellDef'
 * up to the end of the elements section.  On exit, 'line' contains
 * the line that terminated the elements section, which will be either
 * a line of the form "<< something >>" or one beginning with a
 * character other than 'r', 'l', or 't'.
 *
 * Results:
 *    Returns TRUE normally, or FALSE on error or EOF.
 *
 * Side effects:
 *    See above.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbReadElements(cellDef, line, len, f, scalen, scaled)
    CellDef *cellDef;   /* Cell whose elements are being read */
    char *line;         /* Line containing << elements >> */
    int len;            /* Size of buffer pointed to by line */
    FILE *f;            /* Input file */
    int scalen;         /* Scale up by this factor */
    int scaled;         /* Scale down by this factor */
{
    char elementname[128], styles[1024], *text, flags[100];
    int istyle, ntok;
    Rect r;
    char *tstr, *nstr;

    /* Get first element line */
    if (dbFgets(line, len, f) == NULL) return (FALSE);

    while (TRUE)
    {
      /* Skip blank lines */
      while (line[0] == '\0')
          if (dbFgets(line, len, f) == NULL)
            return (TRUE);

      /* Stop when at end of elements section (either paint or cell use) */
      if (line[0] != 'r' && line[0] != 'l' && line[0] != 't') break;

      /*
       * Elements may be either rectangles, lines, or text.
       */
      if (line[0] == 'r')
      {
          if ((ntok = sscanf(line, "rectangle %127s %1023s %d %d %d %d %99[^\n]",
                elementname, styles, &r.r_xbot, &r.r_ybot,
                        &r.r_xtop, &r.r_ytop, flags)) < 6)
          {
            TxError("Skipping bad \"rectangle\" element line: %s", line);
            goto nextelement;
          }
          if (scalen > 1)
          {
            r.r_xbot *= scalen;
            r.r_ybot *= scalen;
            r.r_xtop *= scalen;
            r.r_ytop *= scalen;
          }
          if (scaled > 1)
          {
            r.r_xbot /= scaled;
            r.r_ybot /= scaled;
            r.r_xtop /= scaled;
            r.r_ytop /= scaled;
          }
          (void) DBWElementAddRect(NULL, elementname, &r, cellDef, 0);
          ntok -= 6;
      }
      else if (line[0] == 'l')
      {
          if ((ntok = sscanf(line, "line %127s %1023s %d %d %d %d %99[^\n]",
                elementname, styles, &r.r_xbot, &r.r_ybot,
                        &r.r_xtop, &r.r_ytop, flags)) < 6)
          {
            TxError("Skipping bad \"line\" element line: %s", line);
            goto nextelement;
          }
          if (scalen > 1)
          {
            r.r_xbot *= scalen;
            r.r_ybot *= scalen;
            r.r_xtop *= scalen;
            r.r_ytop *= scalen;
          }
          if (scaled > 1)
          {
            r.r_xbot /= scaled;
            r.r_ybot /= scaled;
            r.r_xtop /= scaled;
            r.r_ytop /= scaled;
          }
          (void) DBWElementAddLine(NULL, elementname, &r, cellDef, 0);
          ntok -= 6;
      }
      else
      {
          char *textend;

          if (((ntok = sscanf(line, "text %127s %1023s %d %d",
                  elementname, styles, &r.r_xbot, &r.r_ybot)) < 4)
                  || ((text = strchr(line, '"')) == NULL)
                  || ((textend = strrchr(line, '"')) == text))
          {
            TxError("Skipping bad \"text\" element line: %s", line);
            goto nextelement;
          }
          text++;
          *textend = '\0';

          if (scalen > 1)
          {
            r.r_xbot *= scalen;
            r.r_ybot *= scalen;
          }
          if (scaled > 1)
          {
            r.r_xbot /= scaled;
            r.r_ybot /= scaled;
          }
          (void) DBWElementAddText(NULL, elementname, r.r_xbot, r.r_ybot, text,
                        cellDef, 0);
          *textend = '"';
          ntok += sscanf(textend + 1, "%99[^\n]", flags);
          ntok -= 4;
      }
      DBWElementParseFlags(NULL, elementname, "persistent");

      /* Set the style(s) for this element */
      tstr = styles;
      while ((nstr = strchr(tstr, ',')) != NULL)
      {
          *nstr = '\0';
          istyle = GrGetStyleFromName(tstr);
          DBWElementStyle(NULL, elementname, istyle, TRUE);
          *nstr = ',';
          tstr = nstr + 1;
      }
      istyle = GrGetStyleFromName(tstr);
      DBWElementStyle(NULL, elementname, istyle, TRUE);

        /* Remove initial style 0, which was temporary */
      DBWElementStyle(NULL, elementname, 0, FALSE);

      /* Any remaining text should be a comma-separated list of flags */
      if (ntok > 0)
      {
          tstr = flags;
          while (isspace(*tstr)) tstr++;
          while ((nstr = strchr(tstr, ',')) != NULL)
          {
            *nstr = '\0';
            DBWElementParseFlags(NULL, elementname, tstr);
            *nstr = ',';
            tstr = nstr + 1;
          }
          DBWElementParseFlags(NULL, elementname, tstr);
      }

nextelement:
      if (dbFgets(line, len, f) == NULL)
          break;
    }

    return (TRUE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbReadLabels --
 *
 * Starting with the line << labels >>, read labels for 'cellDef'
 * up until the end of a label section.  On exit, 'line' contains
 * the line that terminated the label section, which will be either
 * a line of the form "<< something >>" or one beginning with a
 * character other than 'r', 'l', 'f', or 'p'.
 *
 * Results:
 *    Returns TRUE normally, or FALSE on error or EOF.
 *
 * Side effects:
 *    See above.
 *
 * ----------------------------------------------------------------------------
 */

bool
dbReadLabels(cellDef, line, len, f, scalen, scaled)
    CellDef *cellDef;   /* Cell whose labels are being read */
    char *line;         /* Line containing << labels >> */
    int len;            /* Size of buffer pointed to by line */
    FILE *f;            /* Input file */
    int scalen;         /* Scale up by this factor */
    int scaled;         /* Scale down by this factor */
{
    char layername[50], text[1024], port_use[50], port_class[50];
    TileType type;
    int ntok, orient;
    Rect r;

    /* Get first label line */
    if (dbFgets(line, len, f) == NULL) return (FALSE);

    while (TRUE)
    {
      /* Skip blank lines */
      while (line[0] == '\0')
          if (dbFgets(line, len, f) == NULL)
            return (TRUE);

      /* Stop when at end of labels section (either paint or cell use) */
      if (line[0] != 'r' && line[0] != 'l' && line[0] != 'p' &&
            line[0] != 'f') break;

      /*
       * Labels may be either point labels or rectangular ones.
       * Since each label is associated with a particular
       * tile, the type of tile is also stored.
       */
      if (line[0] == 'r')
      {
          if (sscanf(line, "rlabel %49s %d %d %d %d %d %99[^\n]",
                layername, &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop,
                         &orient, text) != 7)
          {
            TxError("Skipping bad \"rlabel\" line: %s", line);
            goto nextlabel;
          }
      }
      else if (line[0] == 'f')
      {
          /* Forward-compatibility with magic v. 8.0+ font labels.      */
          /* We ignore the extra information and process them like      */
          /* plain labels.                                  */

          if (sscanf(line,
                "flabel %49s %d %d %d %d %d %*255s %*d %*d %*d %*d %99[^\n]",
                layername, &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop,
                        &orient, text) != 7)
          {
            TxError("Skipping bad \"flabel\" line: %s", line);
            goto nextlabel;
          }
      }
      else if (line[0] == 'p')
      {
          char ppos[5], *pptr;
          int idx;
          Label *lab;

          if (((lab = cellDef->cd_lastLabel) == NULL) ||
                  (lab->lab_flags & PORT_DIR_MASK) ||
                  (((ntok = sscanf(line, "port %d %4s %49s %49s",
                        &idx, ppos, port_use, port_class)) != 2) &&
                  (ntok != 4)))
          {
            TxError("Skipping bad \"port\" line: %s", line);
            goto nextlabel;
          }
          lab->lab_flags = idx;
          for (pptr = &ppos[0]; *pptr != '\0'; pptr++)
          {
            switch(*pptr)
            {
                case 'n':
                  lab->lab_flags |= PORT_DIR_NORTH;
                  break;
                case 's':
                  lab->lab_flags |= PORT_DIR_SOUTH;
                  break;
                case 'e':
                  lab->lab_flags |= PORT_DIR_EAST;
                  break;
                case 'w':
                  lab->lab_flags |= PORT_DIR_WEST;
                  break;
            }
          }
          if (ntok == 4)
          {
            switch(port_use[0])
            {
                case 's':
                  lab->lab_flags |= PORT_USE_SIGNAL;
                  break;
                case 'a':
                  lab->lab_flags |= PORT_USE_ANALOG;
                  break;
                case 'p':
                  lab->lab_flags |= PORT_USE_POWER;
                  break;
                case 'g':
                  lab->lab_flags |= PORT_USE_GROUND;
                  break;
                case 'c':
                  lab->lab_flags |= PORT_USE_CLOCK;
                  break;
                case 'd':
                  lab->lab_flags |= PORT_USE_DEFAULT;
                  break;
                default:
                  TxError("Ignoring unknown \"port\" use: %s", port_use);
                  break;
            }

            switch(port_class[0])
            {
                case 'i':
                  lab->lab_flags |= PORT_CLASS_INPUT;
                  break;
                case 'o':
                  lab->lab_flags |= PORT_CLASS_OUTPUT;
                  break;
                case 't':
                  lab->lab_flags |= PORT_CLASS_TRISTATE;
                  break;
                case 'b':
                  lab->lab_flags |= PORT_CLASS_BIDIRECTIONAL;
                  break;
                case 'f':
                  lab->lab_flags |= PORT_CLASS_FEEDTHROUGH;
                  break;
                case 'd':
                  lab->lab_flags |= PORT_CLASS_DEFAULT;
                  break;
                default:
                  TxError("Ignoring unknown \"port\" use: %s", port_use);
                  break;
            }

          }
          goto nextlabel;
      }
      else  /* deprecated, retained for backward compatibility */
      {
          if (sscanf(line, "label %1023s %d %d %d %99[^\n]",
                layername, &r.r_xbot, &r.r_ybot, &orient, text) != 5)
          {
            TxError("Skipping bad \"label\" line: %s", line);
            goto nextlabel;
          }
          r.r_xtop = r.r_xbot;
          r.r_ytop = r.r_ybot;
      }

      if (scalen > 1)
      {
          r.r_xbot *= scalen;
          r.r_ybot *= scalen;
          r.r_xtop *= scalen;
          r.r_ytop *= scalen;
      }
      if (scaled > 1)
      {
          r.r_xbot /= scaled;
          r.r_ybot /= scaled;
          r.r_xtop /= scaled;
          r.r_ytop /= scaled;
      }
      type = DBTechNameType(layername);
      if (type < 0)
      {
          TileTypeBitMask rmask;

          /* Check against alias hash table.  Names that are      */
          /* aliases for multiple types return the first type     */
          /* encountered (lowest mask bit number).          */

          type = DBTechNameTypes(layername, &rmask);
      }
      if (type < 0)
      {
          TxError("Warning: label \"%s\" attached to unknown "
                  "type \"%s\"\n", text, layername);
          type = TT_SPACE;
      }
      else if (type >= DBNumUserLayers)
      {
          TileTypeBitMask *rmask;
          TileType rtype;

          /* Don't stick labels on stacked types; choose the      */
          /* topmost (last, usually) residue contact type.  */

          rmask = DBResidueMask(type);
          for (rtype = TT_SPACE + 1; rtype < DBNumUserLayers; rtype++)
            if (TTMaskHasType(rmask, rtype))
                type = rtype;
      }
      DBPutLabel(cellDef, &r, orient, text, type, 0);

nextlabel:
      if (dbFgets(line, len, f) == NULL)
          break;
    }

    return (TRUE);
}


/*
 * ----------------------------------------------------------------------------
 *
 * dbFgets --
 *
 * Like fgets(), but ignore lines beginning with a pound-sign.
 *
 * Results:
 *    Returns a pointer to 'line', or NULL on EOF.
 *
 * Side effects:
 *    Stores characters into 'line', terminating it with a
 *    NULL byte.
 *
 * ----------------------------------------------------------------------------
 */

char *
dbFgets(line, len, f)
    char *line;
    int len;
    FILE *f;
{
    char *cs;
    int l;
    int c;

    do
    {
      cs = line, l = len;
      while (--l > 0 && (c = getc(f)) != EOF)
      {
          *cs++ = c;
          if (c == '\n')
            break;
      }

      if (c == EOF && cs == line)
          return (NULL);

      *cs = '\0';
    } while (line[0] == '#');

    return (line);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBCellFindScale --
 *
 * Results:
 *    Returns the greatest common factor of all the geometry in the cellDef.
 *    This includes tiles, labels, and cell use positions (from the cell use
 *    transform), bounding boxes, and array spacing.
 *
 * Side effects:
 *    None.
 *
 * ----------------------------------------------------------------------------
 */

int
DBCellFindScale(cellDef)
    CellDef *cellDef;
{
    int dbFindGCFFunc(), dbFindCellGCFFunc();
    TileType type;
    TileTypeBitMask typeMask;
    int pNum;
    int ggcf;
    Label *lab;

    /* We only care about preventing magic from making the grid spacing finer */
    /* and finer.  If the current scale is lambda = 1 magic unit or larger,   */
    /* then we simply set the scale factor to 1 and return.  Otherwise, do the      */
    /* search to see if we can write this cell out at a coarser scale.        */

    if (DBLambda[1] <= DBLambda[0]) return 1;

    /* Find greatest common factor of all geometry.  If this becomes 1, stop. */

    ggcf = DBLambda[1];
    for (type = TT_PAINTBASE; type < DBNumUserLayers; type++)
    {
      if ((pNum = DBPlane(type)) < 0)
          continue;
      TTMaskSetOnlyType(&typeMask, type);
      if (DBSrPaintArea((Tile *) NULL, cellDef->cd_planes[pNum],
            &TiPlaneRect, &typeMask, dbFindGCFFunc, (ClientData) &ggcf))
          return 1;
    }

    /* Now labels */
    if (cellDef->cd_labels)
    {
      for (lab = cellDef->cd_labels; lab; lab = lab->lab_next)
      {
          if (lab->lab_rect.r_xtop % ggcf != 0)
            ggcf = FindGCF(lab->lab_rect.r_xtop, ggcf);
          if (lab->lab_rect.r_xbot % ggcf != 0)
            ggcf = FindGCF(lab->lab_rect.r_xbot, ggcf);
          if (lab->lab_rect.r_ytop % ggcf != 0)
            ggcf = FindGCF(lab->lab_rect.r_ytop, ggcf);
          if (lab->lab_rect.r_ybot % ggcf != 0)
            ggcf = FindGCF(lab->lab_rect.r_ybot, ggcf);
          if (ggcf == 1) return 1;
      }
    }

    /* Finally, cell uses */
      
    if (DBCellEnum(cellDef, dbFindCellGCFFunc, (ClientData) &ggcf))
      return 1;

    return ggcf;
}

int
dbFindGCFFunc(tile, ggcf)
    Tile *tile;
    int *ggcf;
{
    Rect r;

    TiToRect(tile, &r);

    if (r.r_xtop % (*ggcf) != 0)
      *ggcf = FindGCF(r.r_xtop, *ggcf);
    if (r.r_xbot % (*ggcf) != 0)
      *ggcf = FindGCF(r.r_xbot, *ggcf);
    if (r.r_ytop % (*ggcf) != 0)
      *ggcf = FindGCF(r.r_ytop, *ggcf);
    if (r.r_ybot % (*ggcf) != 0)
      *ggcf = FindGCF(r.r_ybot, *ggcf);
   
    return (*ggcf == 1) ? 1 : 0;
}

int
dbFindCellGCFFunc(cellUse, ggcf)
    CellUse *cellUse;   /* Cell use whose "call" is to be written to a file   */
    int *ggcf;          /* Greatest common denominator for all geometry       */
{
    Transform *t;
    Rect *b;

    t = &(cellUse->cu_transform);
    b = &(cellUse->cu_def->cd_bbox);

    /* Check transform translation values */
    if (t->t_c % (*ggcf) != 0)
      *ggcf = FindGCF(t->t_c, *ggcf);
    if (t->t_f % (*ggcf) != 0)
      *ggcf = FindGCF(t->t_f, *ggcf);

    /* Check bounding box */
    if (b->r_xtop % (*ggcf) != 0)
      *ggcf = FindGCF(b->r_xtop, *ggcf);
    if (b->r_xbot % (*ggcf) != 0)
      *ggcf = FindGCF(b->r_xbot, *ggcf);
    if (b->r_ytop % (*ggcf) != 0)
      *ggcf = FindGCF(b->r_ytop, *ggcf);
    if (b->r_ybot % (*ggcf) != 0)
      *ggcf = FindGCF(b->r_ybot, *ggcf);

    /* Check array separation, if arrayed */
    if ((cellUse->cu_xlo != cellUse->cu_xhi)
          || (cellUse->cu_ylo != cellUse->cu_yhi))
    {
      if (cellUse->cu_xsep % (*ggcf) != 0)
          *ggcf = FindGCF(cellUse->cu_xsep, *ggcf);
      if (cellUse->cu_ysep % (*ggcf) != 0)
          *ggcf = FindGCF(cellUse->cu_ysep, *ggcf);
    }

    return (*ggcf == 1) ? 1 : 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBCellWriteFile --
 *
 * NOTE: this routine is usually not want you want.  Use DBCellWrite().
 *
 * Write out the paint for a cell to the specified file.
 * Mark the cell as having been written out.  Before calling this
 * procedure, the caller should make sure that timestamps have been
 * updated where appropriate.
 *
 * Results:
 *    TRUE if the cell could be written successfully, FALSE otherwise.
 *
 * Side effects:
 *    Writes a file to disk.
 *    Does NOT close the file 'f', but does fflush(f) before
 *    returning.
 *
 *    If successful, clears the CDMODIFIED, CDBOXESCHANGED,
 *    and CDSTAMPSCHANGED bits in cellDef->cd_flags.
 *
 *    In the event of an error while writing out the cell,
 *    the external integer errno is set to the UNIX error
 *    encountered, and the above bits are not cleared in
 *    cellDef->cd_flags.
 *
 * ----------------------------------------------------------------------------
 */

bool
DBCellWriteFile(cellDef, f)
    CellDef *cellDef;   /* Pointer to definition of cell to be written out */
    FILE *f;            /* The FILE to write to */
{
    int dbWritePaintFunc(), dbWriteCellFunc(), dbWritePropFunc();
    Label *lab;
    struct writeArg arg;
    int pNum;
    TileType type, stype;
    TileTypeBitMask typeMask, *sMask;
    int reducer;
    char *estring;

#define FPRINTF(f,s)\
{\
     if (fprintf(f,s) == EOF) goto ioerror;\
     DBFileOffset += strlen(s);\
}
#define FPRINTR(f,s)\
{\
     if (fprintf(f,s) == EOF) return 1;\
     DBFileOffset += strlen(s);\
}

    if (f == NULL) return FALSE;

    /* If interrupts are left enabled, a partial file could get written.
     * This is not good.
     */

    reducer = DBCellFindScale(cellDef);

    SigDisableInterrupts();
    DBFileOffset = 0;

    if (cellDef->cd_flags & CDGETNEWSTAMP)
      TxPrintf("Magic error: writing out-of-date timestamp for %s.\n",
          cellDef->cd_name);

    {
       char headerstring[256];
       if (DBLambda[0] == (DBLambda[1] / reducer))    /* Default scale */
           sprintf(headerstring,"magic\ntech %s\ntimestamp %d\n",
                  DBTechName,cellDef->cd_timestamp);
       else
           sprintf(headerstring,"magic\ntech %s\nmagscale %d %d\ntimestamp %d\n",
                  DBTechName, DBLambda[0], DBLambda[1] / reducer,
                  cellDef->cd_timestamp);
       FPRINTF(f, headerstring);
    }

    /*
     * Output the paint of the cell.
     * Note that we only output up to the last layer appearing
     * in the technology file (DBNumUserLayers-1).  Automatically
     * generated stacked contact types are added to typeMask and
     * will be decomposed into the residue appropriate for the
     * plane being searched.
     */

    arg.wa_file = f;
    arg.wa_reducer = reducer;
    for (type = TT_PAINTBASE; type < DBNumUserLayers; type++)
    {
      if ((pNum = DBPlane(type)) < 0)
          continue;
      arg.wa_found = FALSE;
      arg.wa_type = type;
      arg.wa_plane = pNum;
      TTMaskSetOnlyType(&typeMask, type);

      /* Add to the mask all generated (stacking) types which     */
      /* have this type as a residue.                       */

      for (stype = DBNumUserLayers; stype < DBNumTypes; stype++)
      {
          sMask = DBResidueMask(stype);
          if (TTMaskHasType(sMask, type))
            TTMaskSetType(&typeMask, stype);
      }

      if (DBSrPaintArea((Tile *) NULL, cellDef->cd_planes[pNum],
            &TiPlaneRect, &typeMask, dbWritePaintFunc, (ClientData) &arg))
          goto ioerror;
    }

    /* Now the cell uses */
    if (DBCellEnum(cellDef, dbWriteCellFunc, (ClientData) &arg))
      goto ioerror;

    /* Now labels */
    if (cellDef->cd_labels)
    {
      char lstring[256];

      FPRINTF(f, "<< labels >>\n");
      for (lab = cellDef->cd_labels; lab; lab = lab->lab_next)
      {
          sprintf(lstring, "rlabel %s %d %d %d %d %d %s\n",
                  DBTypeLongName(lab->lab_type),
                  lab->lab_rect.r_xbot / reducer,
                  lab->lab_rect.r_ybot / reducer,
                  lab->lab_rect.r_xtop / reducer,
                  lab->lab_rect.r_ytop / reducer,
                  lab->lab_just, lab->lab_text);
          FPRINTF(f, lstring);
          if (lab->lab_flags & PORT_DIR_MASK)
          {
            char ppos[5];

            ppos[0] = '\0';
            if (lab->lab_flags & PORT_DIR_NORTH) strcat(ppos, "n");
            if (lab->lab_flags & PORT_DIR_SOUTH) strcat(ppos, "s");
            if (lab->lab_flags & PORT_DIR_EAST) strcat(ppos, "e");
            if (lab->lab_flags & PORT_DIR_WEST) strcat(ppos, "w");
            sprintf(lstring, "port %d %s", lab->lab_flags & PORT_NUM_MASK,
                  ppos);

            if (lab->lab_flags & (PORT_USE_MASK | PORT_CLASS_MASK))
            {
                switch (lab->lab_flags & PORT_USE_MASK)
                {
                  case PORT_USE_SIGNAL:
                      strcat(lstring, " signal");
                      break;
                  case PORT_USE_ANALOG:
                      strcat(lstring, " analog");
                      break;
                  case PORT_USE_POWER:
                      strcat(lstring, " power");
                      break;
                  case PORT_USE_GROUND:
                      strcat(lstring, " ground");
                      break;
                  case PORT_USE_CLOCK:
                      strcat(lstring, " clock");
                      break;
                  case PORT_USE_DEFAULT:
                      strcat(lstring, " default");
                      break;
                }

                switch (lab->lab_flags & PORT_CLASS_MASK)
                {
                  case PORT_CLASS_INPUT:
                      strcat(lstring, " input");
                      break;
                  case PORT_CLASS_OUTPUT:
                      strcat(lstring, " output");
                      break;
                  case PORT_CLASS_TRISTATE:
                      strcat(lstring, " tristate");
                      break;
                  case PORT_CLASS_BIDIRECTIONAL:
                      strcat(lstring, " bidirectional");
                      break;
                  case PORT_CLASS_FEEDTHROUGH:
                      strcat(lstring, " feedthrough");
                      break;
                  case PORT_CLASS_DEFAULT:
                      strcat(lstring, " default");
                      break;
                }
            }
            strcat(lstring, "\n");
            FPRINTF(f, lstring); 
          }
      }
    }

    /* Now any persistent elements */
    estring = DBWPrintElements(cellDef, DBW_ELEMENT_PERSISTENT);
    if (estring != NULL)
    {
      FPRINTF(f, "<< elements >>\n");
      FPRINTF(f, estring);
      freeMagic(estring);
    }

    /* And any properties */
    if (cellDef->cd_props != (ClientData)NULL)
    {
      FPRINTF(f, "<< properties >>\n");
      DBPropEnum(cellDef, dbWritePropFunc, (ClientData)f);
    }

    FPRINTF(f, "<< end >>\n");

    if (fflush(f) == EOF || ferror(f))
    {
ioerror:
      TxError("Warning: I/O error in writing file\n");
      SigEnableInterrupts();
      return (FALSE);
    }
    cellDef->cd_flags &= ~(CDMODIFIED|CDBOXESCHANGED|CDSTAMPSCHANGED);
    SigEnableInterrupts();
    return (TRUE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbWritePropFunc --
 *
 * Filter function used to write out a single cell property.
 *
 * Results:
 *    Normally returns 0; returns 1 on I/O error.
 *
 * Side effects:
 *    Writes to the disk file.
 *
 * Warnings:
 *    This function assumes that all property values are strings!
 *    This is currently true;  if it changes in the future, this
 *    function will have to check each property against a list of
 *    expected property strings and output the value based on the
 *    known format of what it points to.
 *
 * ----------------------------------------------------------------------------
 */

int
dbWritePropFunc(key, value, cdata)
    char *key;
    ClientData value;
    ClientData cdata;
{
    FILE *f = (FILE *)cdata;
    char lstring[256];

    sprintf(lstring, "string %s %s\n", key, (char *)value);
    FPRINTR(f, lstring);

    return 0;
}


/*
 * ----------------------------------------------------------------------------
 *
 * DBCellWrite --
 *
 * Write out the paint for a cell to its associated disk file.
 * Mark the cell as having been written out.  Before calling this
 * procedure, the caller should make sure that timestamps have been
 * updated where appropriate.
 * 
 * This code is fairly tricky to ensure that we never destroy the
 * original contents of a cell in the event of an I/O error.  We
 * try the following approaches in order.
 *
 *  1.      If we can create a temporary file in the same directory as the
 *    target cell, do so.  Then write to the temporary file and rename
 *    it to the target cell name.
 *
 *  2.      If we can't create the above temporary file, open the target
 *    cell for APPENDING, then write the new contents to the END of
 *    the file.  If successful, rewind the now-expanded file and
 *    overwrite the beginning of the file, then truncate it.
 *
 *
 * Results:
 *    TRUE if the cell could be written successfully, FALSE otherwise.
 *
 * Side effects:
 *    Writes a file to disk.
 *    If successful, clears the CDMODIFIED, CDBOXESCHANGED,
 *    and CDSTAMPSCHANGED bits in cellDef->cd_flags.
 *
 *    In the event of an error while writing out the cell,
 *    the external integer errno is set to the UNIX error
 *    encountered, and the above bits are not cleared in
 *    cellDef->cd_flags.
 *
 * ----------------------------------------------------------------------------
 */

bool
DBCellWrite(cellDef, fileName)
    CellDef *cellDef;   /* Pointer to definition of cell to be written out */
    char *fileName;     /* If not NULL, name of file to write.  If NULL,
                   * the name associated with the CellDef is used
                   */
{
#define NAME_SIZE 1000
    char *template = ".XXXXXXX";
    char *realname, *tmpname, *expandname;
    char *cp1, *cp2;
    char expandbuf[NAME_SIZE];
    FILE *realf, *tmpf;
    int tmpres;
    struct stat statb;
    bool result, exists;

    result = FALSE;

    /*
     * Figure out the name of the file we will eventually write.
     */
    if (fileName)
    {
      realname = (char *) mallocMagic((unsigned) (strlen(fileName)+strlen(DBSuffix)+1));
      (void) sprintf(realname, "%s%s", fileName, DBSuffix);
      /* Bug fix: 7/17/99, Michael D. Godfrey:  Forces            */
      /* cd_name and cd_file to ALWAYS be the same, otherwise ugly      */
      /* surprises can occur after saving a file as a different   */
      /* filename.                                          */
      cellDef->cd_file = StrDup(&cellDef->cd_file, realname);
    }
    else if (cellDef->cd_file)
    {
      realname = StrDup((char **) NULL, cellDef->cd_file);
    }
    else if (cellDef->cd_name)
    {
      realname = (char *) mallocMagic((unsigned) (strlen(cellDef->cd_name)
            + strlen(DBSuffix) + 1));
      (void) sprintf(realname, "%s%s", cellDef->cd_name, DBSuffix);
    }
    else return (FALSE);

    /*
     * Expand the filename, removing the leading ~, if any.
     */
    expandname = expandbuf;
    cp1 = realname;
    cp2 = expandname;
    if (PaExpand(&cp1, &cp2, NAME_SIZE) == -1)
      expandname = realname;

    /*
     * If the locking logic works, this should not happen.  Files which
     * are not editable should never be set to MODIFIED.  But it is
     * better to be extra safe.  If it does happen, it would be good to
     * figure out why.
     */

    if(cellDef->cd_flags & CDNOEDIT)
    {
#ifdef FILE_LOCKS
      TxPrintf("File %s is locked by another user or "
            "is read_only and cannot be written\n", realname);
#else
      TxPrintf("File %s is read_only and cannot be written\n", realname);
#endif
      return(FALSE);
    }

    /* Check if the .mag file exists.  If not, we don't need to deal    */
    /* with temporary file names.                           */
    exists = (access(expandname, F_OK) == 0) ? TRUE : FALSE;

    if (exists)
    {
      /*
       * Determine unique name for a temp file to write.
       */
      tmpname = (char *) mallocMagic((unsigned) (strlen(expandname)
            + strlen(template) + 1));
      (void) sprintf(tmpname, "%s%s", expandname, template);
      tmpres = mkstemp(tmpname);
      if (tmpres != -1)
      {
          /* Assert the file permissions of the original file */
          /* This will prevent the overwriting of a file that     */
          /* has write permissions blocked.                 */

          if (stat(expandname, &statb) == 0)
            fchmod(tmpres, statb.st_mode & 0777);
          close(tmpres);      /* We'll re-open it as a stream, below */
      }

      /* Critical: disable interrupts while we do our work */
      SigDisableInterrupts();

      /* Don't allow a write if the file isn't writeable, or if   */
      /* mkstemp() returned an error condition.             */

      if (file_is_not_writeable(expandname))
      {
          if (tmpres != -1) unlink(tmpname);
          perror(expandname);
          goto cleanup;
      }
    }
    else
    {
      tmpname = StrDup((char **)NULL, expandname);
    }

    /*
     * See if we can create a temporary file in this directory.
     * If so, write to the temp file and then rename it after
     * we're done.
     */
    if (tmpf = fopen(tmpname, "w"))
    {
      result = DBCellWriteFile(cellDef, tmpf);
      (void) fclose(tmpf);
      tmpf = NULL;
      if (!result)
      {
          /*
           * Total loss -- just can't write the file.
           * The error message is printed elsewhere.
           */
          (void) unlink(tmpname);
          goto cleanup;
      }

#ifdef FILE_LOCKS
      if (cellDef->cd_fd != -1)
      {
          close(cellDef->cd_fd);
          cellDef->cd_fd = -1;
      }
#endif  

      /*
       * The temp file is in good shape -- rename it to the real name,
       * thereby completing the write.  The error below should NEVER
       * normally happen.
       */
      if (exists && (rename(tmpname, expandname) < 0))
      {
          result = FALSE;
          perror("rename");
          TxError("ATTENTION: Magic was unable to rename file %s to %s.\n"
                "If the file %s exists, it is the old copy of the cell %s.\n"
                "The new copy is in the file %s.  Please copy this file\n"
                "to a safe place before executing any more Magic commands.\n",
                tmpname, expandname, expandname, cellDef->cd_name, tmpname);
          goto cleanup;
      }

#ifdef FILE_LOCKS
      else
          /* Re-aquire the lock on the new file by opening it. */
          DBCellRead(cellDef, NULL, TRUE);
#endif  

    }
    else if (exists)
    {
      /*
       * Couldn't create a temp file in this directory.  Instead, open
       * the original file for APPENDING, write this cell (just to make
       * sure the file is big enough), then rewind and write this cell
       * again, and finally truncate the file.  The idea here is that
       * by appending to realf, we don't trash the existing data, but
       * do guarantee that there's enough space left to rewrite the
       * file (in effect, we're pre-reserving space for it).
       */
      realf = fopen(expandname, "a");
      if (realf == (FILE *) NULL)
      {
          perror(expandname);
          result = FALSE;
          goto cleanup;
      }

      /* Remember the original length of the file for later truncation */
      (void) fstat(fileno(realf), &statb);

      /* Try to write by appending to the end of realf */
      if (!(result = DBCellWriteFile(cellDef, realf)))
      {
          /* Total loss -- just can't write the file */
          (void) fclose(realf);
          realf = NULL;
          (void) truncate(expandname, (long) statb.st_size);
          goto cleanup;
      }

      /*
       * Only try rewriting if the file wasn't zero-size to begin with.
       * (If the file were zero-size, we're already done).
       */
      if (statb.st_size > 0)
      {
          rewind(realf);
          result = DBCellWriteFile(cellDef, realf);
          if (!result)
          {
            /* Should NEVER happen */
            if (errno) perror(expandname);
            TxError("Something went wrong and the file %s was truncated\n",
                  expandname);
            TxError("Try saving it in another file that is on a \n");
            TxError("filesystem where there is enough space!\n");
            (void) fclose(realf);
            realf = NULL;
            goto cleanup;
          }

          /* Successful writing the second time around */
          statb.st_size = ftell(realf);
          (void) fclose(realf);
          realf = NULL;
          (void) truncate(expandname, (long) statb.st_size);
      }
    }

    /* Everything worked. */
    (void) StrDup(&cellDef->cd_file, expandname);
    result = TRUE;
    {
       struct stat thestat;
       realf = fopen(expandname,"r");
       fstat(fileno(realf),&thestat);
       if (thestat.st_size != DBFileOffset)
       {
              cellDef->cd_flags |= CDMODIFIED;
            TxError("Warning: I/O error in writing file\n");
       }
       fclose(realf);
       realf = NULL;
    }

cleanup:
    SigEnableInterrupts();
    freeMagic(realname);
    freeMagic(tmpname);
    return result;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbWritePaintFunc --
 *
 * Filter function used to write out a single paint tile.
 * Only writes out tiles of type arg->wa_type.
 * If the tile is the first encountered of its type, the header
 *    << typename >>
 * is output.
 *
 * Results:
 *    Normally returns 0; returns 1 on I/O error.
 *
 * Side effects:
 *    Writes to the disk file.
 *
 * ----------------------------------------------------------------------------
 */

int
dbWritePaintFunc(tile, cdarg)
    Tile *tile;
    ClientData cdarg;
{
    char pstring[256];
    struct writeArg *arg = (struct writeArg *) cdarg;
    TileType type = TiGetType(tile);

#ifdef NONMANHATTAN
    TileTypeBitMask *rMask, *lMask;
    int dir;

    if (IsSplit(tile))      
    { 
      lMask = DBResidueMask(SplitLeftType(tile));
      rMask = DBResidueMask(SplitRightType(tile));

      if ((SplitLeftType(tile) == arg->wa_type) ||
            ((SplitLeftType(tile) >= DBNumUserLayers) &&
            TTMaskHasType(lMask, arg->wa_type)))
      {
          type = arg->wa_type;
          dir = 0x0;
      }
      else if ((SplitRightType(tile) == arg->wa_type) ||
            ((SplitRightType(tile) >= DBNumUserLayers) &&
            TTMaskHasType(rMask, arg->wa_type)))
      {
          type = arg->wa_type;
          dir = 0x2;
      }
      else
          return 0;
    }
    else  
#endif   
    if (type != arg->wa_type)
    {
      rMask = DBResidueMask(type);
      if ((type < DBNumUserLayers) ||
            (!TTMaskHasType(rMask, arg->wa_type)))
          return 0;

      type = arg->wa_type;
    }

    if (!arg->wa_found)
    {
      sprintf(pstring, "<< %s >>\n", DBTypeLongName(type));
      FPRINTR(arg->wa_file,pstring);
      arg->wa_found = TRUE;
    }

#ifdef NONMANHATTAN
    if (IsSplit(tile))
    {
      static char *pos_diag[] = {"nw", "sw", "se", "ne"};
      dir |= SplitDirection(tile);
      sprintf(pstring, "tri %d %d %d %d %s\n",
            LEFT(tile) / arg->wa_reducer, BOTTOM(tile) / arg->wa_reducer,
            RIGHT(tile) / arg->wa_reducer, TOP(tile) / arg->wa_reducer,
            pos_diag[dir]);
    }
    else
#endif

    sprintf(pstring, "rect %d %d %d %d\n",
          LEFT(tile) / arg->wa_reducer, BOTTOM(tile) / arg->wa_reducer,
          RIGHT(tile) / arg->wa_reducer, TOP(tile) / arg->wa_reducer);
    FPRINTR(arg->wa_file,pstring);
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * dbWriteCellFunc --
 *
 * Filter function used to write out a single cell use in the
 * subcell tile plane for a cell.
 *
 * Results:
 *    Normally returns 0; return 1 on I/O error
 *
 * Side effects:
 *    Writes to the disk file.
 *
 * ----------------------------------------------------------------------------
 */

int
dbWriteCellFunc(cellUse, cdarg)
    CellUse *cellUse;   /* Cell use whose "call" is to be written to a file */
    ClientData cdarg;
{
    struct writeArg *arg = (struct writeArg *) cdarg;
    Transform *t;
    Rect *b;
    char     cstring[256];

    t = &(cellUse->cu_transform);
    b = &(cellUse->cu_def->cd_bbox);
    sprintf(cstring, "use %s %c%s\n", cellUse->cu_def->cd_name,
            (cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ',
            cellUse->cu_id);
    FPRINTR(arg->wa_file, cstring);

    if ((cellUse->cu_xlo != cellUse->cu_xhi)
          || (cellUse->cu_ylo != cellUse->cu_yhi))
    {
      sprintf(cstring, "array %d %d %d %d %d %d\n",
            cellUse->cu_xlo, cellUse->cu_xhi, cellUse->cu_xsep / arg->wa_reducer,
            cellUse->cu_ylo, cellUse->cu_yhi, cellUse->cu_ysep / arg->wa_reducer);
      FPRINTR(arg->wa_file,cstring);
    }

    sprintf(cstring, "timestamp %d\n", cellUse->cu_def->cd_timestamp);
    FPRINTR(arg->wa_file,cstring)
    sprintf(cstring, "transform %d %d %d %d %d %d\n",
          t->t_a, t->t_b, t->t_c / arg->wa_reducer,
          t->t_d, t->t_e, t->t_f / arg->wa_reducer);
    FPRINTR(arg->wa_file,cstring)
    sprintf(cstring, "box %d %d %d %d\n",
          b->r_xbot / arg->wa_reducer, b->r_ybot / arg->wa_reducer,
          b->r_xtop / arg->wa_reducer, b->r_ytop / arg->wa_reducer);
    FPRINTR(arg->wa_file,cstring)
    return 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBGetTech --
 *
 *    Reads the first few lines of a file to find out what technology
 *    it is.
 *
 * Results:
 *    The return value is a pointer to a string containing the name
 *    of the technology of the file containing cell cellName.  NULL
 *    is returned if the file couldn't be read or isn't in Magic
 *    format.  The string is stored locally to this procedure and
 *    will be overwritten on the next call to this procedure.
 *
 * Side effects:
 *    None.
 *
 * ----------------------------------------------------------------------------
 */

char *
DBGetTech(cellName)
    char *cellName;                 /* Name of cell whose technology
                               * is desired.
                               */
{
    FILE *f;
    static char line[512];
    char *p;

    f = PaOpen(cellName, "r", DBSuffix, Path, CellLibPath, (char **) NULL);
    if (f == NULL) return NULL;

    p = (char *) NULL;
    if (dbFgets(line, sizeof line - 1, f) == NULL) goto ret;
    if (strcmp(line, "magic\n") != 0) goto ret;
    if (dbFgets(line, sizeof line - 1, f) == NULL) goto ret;
    if (strncmp(line, "tech ", 5) != 0) goto ret;
    for (p = &line[5]; (*p != '\n') && (*p != 0); p++)
      /* Find the newline */;
    *p = 0;
    for (p = &line[5]; isspace(*p); p++)
      /* Find the tech name */;

ret:
    (void) fclose(f);
    f = NULL;
    return (p);
}

/*
 * ----------------------------------------------------------------------------
 *
 * DBWriteBackup --
 *
 * Save all modified cells to "filename", if specified, or the current
 * setting of DBbackupFile, which is the current name of the crash
 * backup file.  If "filename" is NULL and no name has been set for
 * DBbackupFile, then DBbackupFile is generated as a unique filename
 * in the temp directory.  If "filename" is non-null, then DBbackupFile
 * is set to this name, erasing any previous value.  If "filename" is
 * an empty string, then the DBbackupFile reverts to NULL.
 *
 * Results:
 *    TRUE if the backup file was created, FALSE if an error was
 *    encountered.
 *
 * Side effects:
 *    Writes cells to disk (in a single file).
 *    Allocates and sets global variable DBbackupFile.
 *    Does NOT clear the modified bits.
 *
 * ----------------------------------------------------------------------------
 */

bool
DBWriteBackup(filename)
    char *filename;
{
    FILE *f;
    int fd, pid;
    char *tempdir;
    MagWindow *mw;

    int dbWriteBackupFunc(), dbCheckModifiedCellsFunc();
    int flags = CDMODIFIED;
    int result;

    /* First check if there are any modified cells that need to be written */

    result = DBCellSrDefs(flags, dbCheckModifiedCellsFunc, (ClientData)NULL);
    if (result == 0) return TRUE;         /* Nothing to write */

    if (filename == NULL)
    {
      if (DBbackupFile == (char *)NULL)
      {
          char *doslash, *template;

          tempdir = getenv("TMPDIR");
          if (tempdir == NULL) tempdir = _PATH_TMP;
          template = (char *)mallocMagic(20 + strlen(tempdir));
          pid = (int)getpid();

          doslash = (tempdir[strlen(tempdir) - 1] == '/') ? "" : "/";
          sprintf(template, "%s/MAG%d.XXXXXX", tempdir, pid);

          fd = mkstemp(template);
          if (fd == -1)
          {
            TxError("Error generating backup file\n");
            freeMagic(template);
            return FALSE;
          }
          close(fd);
          DBbackupFile = StrDup(&DBbackupFile, template);
          freeMagic(template);
          TxPrintf("Created database crash recovery file %s\n", DBbackupFile);
      }
      filename = DBbackupFile;
    }
    else
    {
      if (strlen(filename) == 0)
      {
          DBbackupFile = StrDup(&DBbackupFile, (char *)NULL);
          return TRUE;
      }
      DBbackupFile = StrDup(&DBbackupFile, filename);
      TxPrintf("Created database crash recovery file %s\n", DBbackupFile);
    }

    f = fopen(filename, "w");
    if (f == NULL)
    {
      TxError("Backup file %s cannot be opened for writing.\n", filename);
      return FALSE;
    }

    result = DBCellSrDefs(flags, dbWriteBackupFunc, (ClientData)f);

    /* End by printing the keyword "end" followed by the cell to load   */
    /* into the first available window, so that we don't have a default */
    /* blank display after crash recovery.                        */

    mw = WindSearchWid(0);
    if (mw != NULL)
      fprintf(f, "end %s\n", ((CellUse *)mw->w_surfaceID)->cu_def->cd_name);
    else
      fprintf(f, "end\n");
    fclose(f);
    return TRUE;
}

/*
 * Filter function used by DBWriteBackup() above.
 * This function writes a single cell definition to the crash backup
 * file.  Only editable cells whose paint, labels, or subcells have
 * changed are considered.
 */

int
dbWriteBackupFunc(def, f)
    CellDef *def; /* Pointer to CellDef to be saved */
    FILE *f;            /* File to append to */
{
    char *name = def->cd_file;
    int result, save_flags;

    if (def->cd_flags & (CDINTERNAL | CDNOEDIT | CDNOTFOUND)) return 0;
    else if (!(def->cd_flags & CDAVAILABLE)) return 0;

    if (name == NULL) name = def->cd_name;

    fprintf(f, "file %s\n", name);
    
    /* Save/restore flags such that the crash recovery file write does  */
    /* *not* clear the CDMODIFIED, et al., bits                   */

    save_flags = def->cd_flags;
    def->cd_flags &= ~(CDGETNEWSTAMP);
    result = DBCellWriteFile(def, f);
    def->cd_flags = save_flags;
    return (result == TRUE) ? FALSE : TRUE;
}

/*
 * Filter function used by DBWriteBackup() above.
 * This function checks if at least one cell needs to be written to the
 * crash backup file.  Only editable cells whose paint, labels, or
 * subcells have changed are considered.
 */

int
dbCheckModifiedCellsFunc(def, cdata)
    CellDef *def; /* Pointer to CellDef to be saved */
    ClientData cdata;   /* Unused */
{
    if (def->cd_flags & (CDINTERNAL | CDNOEDIT | CDNOTFOUND)) return 0;
    else if (!(def->cd_flags & CDAVAILABLE)) return 0;
    return 1;
}

Generated by  Doxygen 1.6.0   Back to index