3400 lines
91 KiB
C
3400 lines
91 KiB
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-8.0/database/DBio.c,v 1.5 2010/06/24 12:37:15 tim Exp $";
|
||
#endif /* not lint */
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#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
|
||
* "tri <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> [<path>]
|
||
* 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. The <path> is a full path
|
||
* to the location of the cell <id>. <path> will be interpreted
|
||
* relative to the parent cell (the .mag file being read) if it
|
||
* does not begin with "/" or "~/". Only the first instance of a
|
||
* cell needs to declare <path>. If omitted completely, the cell
|
||
* is searched for in the search paths declared using the "addpath"
|
||
* command, which is the original, backwardly-compatible behavior.
|
||
* The new behavior using <path>, introduced in magic-8.2, implies
|
||
* that (apart from backwardly-compatible use) the search path only
|
||
* pertains to cells imported using "getcell", while cells named in
|
||
* database files are version controlled by specifically naming the
|
||
* path to the file. If no cell <id> exists at <path>, then the
|
||
* fall-back method is to use the search paths, which allows some
|
||
* portability of layouts from place to place without breaking.
|
||
*
|
||
* 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> [s] <xbot> <ybot> <xtop> <ytop> <position> <text>
|
||
* or
|
||
* flabel <layer> [s] <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, dereference)
|
||
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.
|
||
*/
|
||
bool dereference; /* If TRUE, ignore path references in the input */
|
||
{
|
||
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);
|
||
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\n", d);
|
||
TxError("Warning: Geometry may be lost because internal grid"
|
||
" cannot be reduced.\n");
|
||
}
|
||
else
|
||
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, dereference))
|
||
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, n, d)) 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:
|
||
while (((c = getc(f)) == 'r') || (c == 't'))
|
||
{
|
||
TileType dinfo;
|
||
int dir;
|
||
/*
|
||
* GetRect actually reads the rest of the line up to
|
||
* a trailing newline or EOF.
|
||
*/
|
||
if (c == 't')
|
||
{
|
||
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
|
||
{
|
||
dinfo = 0;
|
||
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;
|
||
if (dinfo & TT_SIDE) loctype <<= 14;
|
||
loctype |= dinfo;
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
/* Update timestamp flags */
|
||
DBFlagMismatches(cellDef);
|
||
|
||
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) == FALSE)
|
||
return FALSE;
|
||
|
||
if (dbFgets(line, sizeof(line), f) == NULL)
|
||
{
|
||
TxError("Error in backup file %s; partial restore only!\n",
|
||
name);
|
||
return FALSE;
|
||
}
|
||
/* Update timestamp flags from dbCellReadDef() */
|
||
DBFlagMismatches(cellDef);
|
||
}
|
||
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, dereference, errptr)
|
||
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.
|
||
*/
|
||
bool dereference; /* If TRUE then ignore path argument to uses */
|
||
int *errptr; /* Copy of errno set by file reading routine
|
||
* is placed here, unless NULL.
|
||
*/
|
||
{
|
||
FILE *f;
|
||
bool result;
|
||
|
||
if (errptr != NULL) *errptr = 0;
|
||
|
||
if (cellDef->cd_flags & CDAVAILABLE)
|
||
result = TRUE;
|
||
|
||
else if ((f = dbReadOpen(cellDef, name, TRUE, errptr)) == NULL)
|
||
result = FALSE;
|
||
|
||
else
|
||
{
|
||
result = (dbCellReadDef(f, cellDef, name, ignoreTech, dereference));
|
||
|
||
#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, errptr)
|
||
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.
|
||
*/
|
||
int *errptr; /* Pointer to int to hold error value */
|
||
{
|
||
FILE *f = NULL;
|
||
char *filename, *realname;
|
||
bool is_locked;
|
||
|
||
#ifdef FILE_LOCKS
|
||
if (cellDef->cd_fd != -1)
|
||
{
|
||
close(cellDef->cd_fd);
|
||
cellDef->cd_fd = -1;
|
||
}
|
||
#endif
|
||
|
||
if (errptr != NULL) *errptr = 0; // No error, by default
|
||
|
||
if (name != (char *) NULL)
|
||
{
|
||
f = PaLockOpen(name, "r", DBSuffix, Path,
|
||
CellLibPath, &filename, &is_locked);
|
||
if (errptr != NULL) *errptr = errno;
|
||
}
|
||
else if (cellDef->cd_file != (char *) NULL)
|
||
{
|
||
/* Do not send a name with a file extension to PaLockOpen(),
|
||
* otherwise that routine must handle it and then cannot
|
||
* distinguish between, say, cell.mag and cell.mag.mag.
|
||
*/
|
||
char *pptr, *sptr;
|
||
|
||
sptr = strrchr(cellDef->cd_file, '/');
|
||
if (sptr == NULL)
|
||
sptr = cellDef->cd_file;
|
||
else
|
||
sptr++;
|
||
|
||
pptr = strrchr(sptr, '.');
|
||
if (pptr != NULL)
|
||
if (strcmp(pptr, DBSuffix)) pptr = NULL;
|
||
else
|
||
*pptr = '\0';
|
||
|
||
f = PaLockOpen(cellDef->cd_file, "r", DBSuffix, ".",
|
||
(char *) NULL, &filename, &is_locked);
|
||
|
||
/* Fall back on the original method of using search paths. */
|
||
|
||
if (f == NULL)
|
||
{
|
||
f = PaLockOpen(cellDef->cd_name, "r", DBSuffix, Path,
|
||
CellLibPath, &filename, &is_locked);
|
||
|
||
if (f != NULL)
|
||
{
|
||
/* NOTE: May not want to present this as an error, as */
|
||
/* it is a common technique to read files from, say, a */
|
||
/* LEF file but not save them locally, and then expect */
|
||
/* that the layout views will be picked up from */
|
||
/* somewhere else in the search paths. */
|
||
|
||
if (pptr != NULL) *pptr = '.';
|
||
TxError("Warning: Parent cell lists instance of \"%s\" at bad file "
|
||
"path %s.\n", cellDef->cd_name, cellDef->cd_file);
|
||
|
||
/* Write the new path to cd_file or else magic will */
|
||
/* generate another error later. */
|
||
cellDef->cd_file = StrDup(&cellDef->cd_file, filename);
|
||
|
||
TxError("The cell exists in the search paths at %s.\n", filename);
|
||
TxError("The discovered version will be used.\n");
|
||
}
|
||
}
|
||
|
||
if (errptr != NULL) *errptr = errno;
|
||
if (pptr != NULL) *pptr = '.'; // Put it back where you found it!
|
||
}
|
||
else
|
||
{
|
||
f = PaLockOpen(cellDef->cd_name, "r", DBSuffix, Path,
|
||
CellLibPath, &filename, &is_locked);
|
||
if (errptr != NULL) *errptr = errno;
|
||
}
|
||
|
||
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 read\n", name, DBSuffix);
|
||
else if (cellDef->cd_file != (char *) NULL)
|
||
TxError("File %s couldn't be read\n", cellDef->cd_file);
|
||
else {
|
||
TxError("Cell %s couldn't be read\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);
|
||
}
|
||
if (errptr) TxError("%s\n", strerror(*errptr));
|
||
|
||
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)
|
||
{
|
||
/* Remove any ".mag" file extension */
|
||
char *pptr = strrchr(filename, '.');
|
||
if (pptr != NULL)
|
||
if (!strcmp(pptr, DBSuffix)) *pptr = '\0';
|
||
|
||
(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, dereference)
|
||
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 */
|
||
bool dereference; /* If TRUE, ignore path references */
|
||
{
|
||
int xlo, xhi, ylo, yhi, xsep, ysep, childStamp;
|
||
int absa, absb, absd, abse, nconv;
|
||
char cellname[1024], useid[1024], path[1024];
|
||
CellUse *subCellUse;
|
||
CellDef *subCellDef;
|
||
Transform t;
|
||
Rect r;
|
||
bool locked;
|
||
char *slashptr, *pathptr;
|
||
|
||
if (strncmp(line, "use", 3) != 0)
|
||
{
|
||
TxError("Expected \"use\" line but saw: %s", line);
|
||
return (FALSE);
|
||
}
|
||
|
||
useid[0] = '\0';
|
||
nconv = sscanf(line, "use %1023s %1023s %1023s", cellname, useid, path);
|
||
if (nconv < 1)
|
||
{
|
||
TxError("Malformed \"use\" line: %s", line);
|
||
return (FALSE);
|
||
}
|
||
/* Make sure useid[0] is an empty string if no useid was provided */
|
||
if (nconv == 1) useid[0] = '\0';
|
||
if (nconv <= 2) path[0] = '\0';
|
||
|
||
pathptr = &path[0];
|
||
while (*pathptr == ' ' || *pathptr == '\t') pathptr++;
|
||
if ((dereference == TRUE) || (*pathptr == '\n')) *pathptr = '\0';
|
||
|
||
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;
|
||
subCellDef->cd_extended = 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;
|
||
}
|
||
|
||
/* Relative path handling: If path does not have a leading "/" */
|
||
/* or "~" and cellDef->cd_file has path components, then the path */
|
||
/* should be interpreted relative to the path of the parent cell. */
|
||
|
||
if ((*pathptr == '\0') || ((*pathptr != '/') && (*pathptr != '~')))
|
||
if ((cellDef->cd_file != NULL) &&
|
||
(slashptr = strrchr(cellDef->cd_file, '/')) != NULL)
|
||
{
|
||
*slashptr = '\0';
|
||
if (*pathptr == '\0')
|
||
strcpy(path, cellDef->cd_file);
|
||
else
|
||
{
|
||
char savepath[1024];
|
||
strcpy(savepath, pathptr);
|
||
sprintf(path, "%s/%s", cellDef->cd_file, savepath);
|
||
}
|
||
pathptr = &path[0];
|
||
*slashptr = '/';
|
||
}
|
||
|
||
/* If path has a leading '~/' and cellDef->cd_file has an absolute */
|
||
/* path that does not match the user's home directory, but appears */
|
||
/* to be a different home directory, then replace the "~" with the */
|
||
/* home directory used by the parent cell. */
|
||
|
||
if (*pathptr == '~' && *(pathptr + 1) == '/')
|
||
if ((cellDef->cd_file != NULL) && (cellDef->cd_file[0] == '/'))
|
||
{
|
||
char *homedir = getenv("HOME");
|
||
if (strncmp(cellDef->cd_file, homedir, strlen(homedir)) ||
|
||
*(cellDef->cd_file + strlen(homedir)) != '/')
|
||
{
|
||
char *homeroot = strrchr(homedir, '/');
|
||
int rootlen = (int)(homeroot - homedir) + 1;
|
||
if (!strncmp(cellDef->cd_file, homedir, rootlen))
|
||
{
|
||
char savepath[1024];
|
||
char *userbrk = strchr(cellDef->cd_file + rootlen, '/');
|
||
if (userbrk != NULL)
|
||
{
|
||
int userlen = (int)(userbrk - cellDef->cd_file);
|
||
strcpy(savepath, pathptr + 1);
|
||
strcpy(path, cellDef->cd_file);
|
||
strcpy(path + userlen, savepath);
|
||
pathptr = &path[0];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* If "use" line contains a path name, then set cd_file to this and */
|
||
/* it will be the preferred path. If cd_file is already set and */
|
||
/* points to a different target, then flag an error, as there are */
|
||
/* now two versions of the same cell name coming from different */
|
||
/* sources, and this must be corrected. */
|
||
|
||
if (*pathptr != '\0')
|
||
{
|
||
if (subCellDef->cd_file != NULL)
|
||
{
|
||
slashptr = strrchr(subCellDef->cd_file, '/');
|
||
if (slashptr != NULL)
|
||
{
|
||
bool pathOK = FALSE;
|
||
*slashptr = '\0';
|
||
|
||
/* Avoid generating error message if pathptr starts with '~' */
|
||
/* and the tilde-expanded name matches the subCellDef name */
|
||
|
||
if (*pathptr == '~')
|
||
{
|
||
char *homedir = getenv("HOME");
|
||
if (!strncmp(subCellDef->cd_file, homedir, strlen(homedir))
|
||
&& (!strcmp(subCellDef->cd_file + strlen(homedir),
|
||
pathptr + 1)))
|
||
pathOK = TRUE;
|
||
}
|
||
|
||
if ((pathOK == FALSE) && strcmp(subCellDef->cd_file, pathptr)
|
||
&& (dereference == FALSE))
|
||
{
|
||
TxError("Duplicate cell in %s: Instance of cell %s is from "
|
||
"path %s but cell was previously read from %s.\n",
|
||
cellDef->cd_name, slashptr + 1, pathptr,
|
||
subCellDef->cd_file);
|
||
|
||
/* To do: Check if new path does not exist (ignore), */
|
||
/* or if new path has same symbolic link or is the same */
|
||
/* filesize and checksum (ignore). If file appears to */
|
||
/* be truly different, then create a new cell with a */
|
||
/* modified cell name. */
|
||
|
||
TxError("New path will be ignored. Please check.\n");
|
||
}
|
||
*slashptr = '/';
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Reconstruct file from path and cellname */
|
||
|
||
strcat(path, "/");
|
||
strcat(path, subCellDef->cd_name);
|
||
strcat(path, DBSuffix);
|
||
StrDup(&subCellDef->cd_file, path);
|
||
}
|
||
}
|
||
|
||
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, 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 propertyname[128], propertyvalue[2048], *storedvalue;
|
||
int ntok;
|
||
unsigned int noeditflag;
|
||
|
||
/* Save CDNOEDIT flag if set, and clear it */
|
||
noeditflag = cellDef->cd_flags & CDNOEDIT;
|
||
cellDef->cd_flags &= ~CDNOEDIT;
|
||
|
||
/* 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)
|
||
{
|
||
cellDef->cd_flags |= noeditflag;
|
||
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 %2047[^\n]",
|
||
propertyname, propertyvalue)) != 2)
|
||
{
|
||
TxError("Skipping bad property line: %s", line);
|
||
goto nextproperty;
|
||
}
|
||
|
||
/* Go ahead and process the vendor GDS property */
|
||
if (!strcmp(propertyname, "GDS_FILE"))
|
||
cellDef->cd_flags |= CDVENDORGDS;
|
||
|
||
/* Also process FIXED_BBOX property, as units must match */
|
||
|
||
if (!strcmp(propertyname, "FIXED_BBOX"))
|
||
{
|
||
Rect locbbox;
|
||
|
||
if (sscanf(propertyvalue, "%d %d %d %d",
|
||
&(locbbox.r_xbot),
|
||
&(locbbox.r_ybot),
|
||
&(locbbox.r_xtop),
|
||
&(locbbox.r_ytop)) != 4)
|
||
{
|
||
TxError("Cannot read bounding box values in %s property",
|
||
propertyname);
|
||
storedvalue = StrDup((char **)NULL, propertyvalue);
|
||
(void) DBPropPut(cellDef, propertyname, storedvalue);
|
||
}
|
||
else
|
||
{
|
||
if (scalen > 1)
|
||
{
|
||
locbbox.r_xbot *= scalen;
|
||
locbbox.r_ybot *= scalen;
|
||
locbbox.r_xtop *= scalen;
|
||
locbbox.r_ytop *= scalen;
|
||
}
|
||
if (scaled > 1)
|
||
{
|
||
locbbox.r_xbot /= scaled;
|
||
locbbox.r_ybot /= scaled;
|
||
locbbox.r_xtop /= scaled;
|
||
locbbox.r_ytop /= scaled;
|
||
}
|
||
cellDef->cd_flags |= CDFIXEDBBOX;
|
||
storedvalue = (char *)mallocMagic(40);
|
||
sprintf(storedvalue, "%d %d %d %d",
|
||
locbbox.r_xbot, locbbox.r_ybot,
|
||
locbbox.r_xtop, locbbox.r_ytop);
|
||
(void) DBPropPut(cellDef, propertyname, storedvalue);
|
||
|
||
}
|
||
}
|
||
else
|
||
{
|
||
storedvalue = StrDup((char **)NULL, propertyvalue);
|
||
(void) DBPropPut(cellDef, propertyname, storedvalue);
|
||
}
|
||
}
|
||
|
||
nextproperty:
|
||
if (dbFgets(line, len, f) == NULL)
|
||
break;
|
||
}
|
||
|
||
cellDef->cd_flags |= noeditflag;
|
||
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, size, rotate, font, flags;
|
||
Point offset;
|
||
Rect r;
|
||
char stickyflag[2];
|
||
|
||
/* 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 %1s", stickyflag) == 1)
|
||
{
|
||
font = -1;
|
||
if (*stickyflag == 's')
|
||
{
|
||
flags = LABEL_STICKY;
|
||
if (sscanf(line, "rlabel %49s %c %d %d %d %d %d %99[^\n]",
|
||
layername, &stickyflag[0], &r.r_xbot, &r.r_ybot,
|
||
&r.r_xtop, &r.r_ytop, &orient, text) != 8)
|
||
{
|
||
TxError("Skipping bad \"rlabel\" line: %s", line);
|
||
goto nextlabel;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
flags = 0;
|
||
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
|
||
{
|
||
TxError("Skipping bad \"flabel\" line: %s", line);
|
||
goto nextlabel;
|
||
}
|
||
}
|
||
else if (line[0] == 'f')
|
||
{
|
||
char fontname[256];
|
||
if (sscanf(line, "flabel %*49s %1s", stickyflag) == 1)
|
||
{
|
||
if (*stickyflag == 's')
|
||
{
|
||
flags = LABEL_STICKY;
|
||
if (sscanf(line,
|
||
"flabel %49s %c %d %d %d %d %d %255s %d %d %d %d %99[^\n]",
|
||
layername, &stickyflag[0], &r.r_xbot, &r.r_ybot, &r.r_xtop,
|
||
&r.r_ytop, &orient, fontname, &size, &rotate, &offset.p_x,
|
||
&offset.p_y, text) != 13)
|
||
{
|
||
TxError("Skipping bad \"flabel\" line: %s", line);
|
||
goto nextlabel;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
flags = 0;
|
||
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, fontname, &size, &rotate, &offset.p_x,
|
||
&offset.p_y, text) != 12)
|
||
{
|
||
TxError("Skipping bad \"flabel\" line: %s", line);
|
||
goto nextlabel;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
TxError("Skipping bad \"flabel\" line: %s", line);
|
||
goto nextlabel;
|
||
}
|
||
|
||
font = DBNameToFont(fontname);
|
||
if (font < -1) font = -1; /* Force default font if font is unknown */
|
||
}
|
||
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 &= ~LABEL_STICKY; */
|
||
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 %49s %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;
|
||
font = -1;
|
||
}
|
||
|
||
if (scalen > 1)
|
||
{
|
||
r.r_xbot *= scalen;
|
||
r.r_ybot *= scalen;
|
||
r.r_xtop *= scalen;
|
||
r.r_ytop *= scalen;
|
||
if (font >= 0)
|
||
{
|
||
size *= scalen;
|
||
offset.p_x *= scalen;
|
||
offset.p_y *= scalen;
|
||
}
|
||
}
|
||
if (scaled > 1)
|
||
{
|
||
r.r_xbot /= scaled;
|
||
r.r_ybot /= scaled;
|
||
r.r_xtop /= scaled;
|
||
r.r_ytop /= scaled;
|
||
if (font >= 0)
|
||
{
|
||
size /= scaled;
|
||
offset.p_x /= scaled;
|
||
offset.p_y /= 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;
|
||
}
|
||
if (font < 0)
|
||
DBPutLabel(cellDef, &r, orient, text, type, flags);
|
||
else
|
||
DBPutFontLabel(cellDef, &r, font, size, rotate, &offset,
|
||
orient, text, type, flags);
|
||
|
||
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)
|
||
{
|
||
if (c != '\r') *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();
|
||
int dbClearCellFunc();
|
||
Label *lab;
|
||
struct writeArg arg;
|
||
int pNum;
|
||
TileType type, stype;
|
||
TileTypeBitMask typeMask, *sMask;
|
||
int reducer;
|
||
char *estring;
|
||
char lstring[256];
|
||
char *propvalue;
|
||
bool propfound;
|
||
|
||
#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.
|
||
*/
|
||
|
||
if (cellDef->cd_file)
|
||
arg.wa_name = cellDef->cd_file;
|
||
else
|
||
arg.wa_name = cellDef->cd_name;
|
||
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;
|
||
|
||
/* Clear flags set in dbWriteCellFunc */
|
||
DBCellEnum(cellDef, dbClearCellFunc, (ClientData)NULL);
|
||
|
||
/* Now labels */
|
||
if (cellDef->cd_labels)
|
||
{
|
||
FPRINTF(f, "<< labels >>\n");
|
||
for (lab = cellDef->cd_labels; lab; lab = lab->lab_next)
|
||
{
|
||
if (strlen(lab->lab_text) == 0) continue; // Shouldn't happen
|
||
if (lab->lab_font < 0)
|
||
{
|
||
sprintf(lstring, "rlabel %s %s%d %d %d %d %d %s\n",
|
||
DBTypeLongName(lab->lab_type),
|
||
((lab->lab_flags & LABEL_STICKY) ? "s " : ""),
|
||
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);
|
||
}
|
||
else
|
||
{
|
||
sprintf(lstring, "flabel %s %s%d %d %d %d %d %s %d %d %d %d %s\n",
|
||
DBTypeLongName(lab->lab_type),
|
||
((lab->lab_flags & LABEL_STICKY) ? "s " : ""),
|
||
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, DBFontList[lab->lab_font]->mf_name,
|
||
lab->lab_size / reducer, lab->lab_rotate,
|
||
lab->lab_offset.p_x / reducer,
|
||
lab->lab_offset.p_y / reducer, 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 */
|
||
|
||
/* NOTE: FIXED_BBOX is treated specially; values are database */
|
||
/* values and should be divided by reducer. Easiest to do it */
|
||
/* here and revert values after. */
|
||
|
||
propvalue = (char *)DBPropGet(cellDef, "FIXED_BBOX", &propfound);
|
||
if (propfound)
|
||
{
|
||
char *proporig, *propscaled;
|
||
Rect scalebox, bbox;
|
||
|
||
proporig = StrDup((char **)NULL, propvalue);
|
||
propscaled = mallocMagic(strlen(propvalue) + 5);
|
||
if (sscanf(propvalue, "%d %d %d %d", &bbox.r_xbot, &bbox.r_ybot,
|
||
&bbox.r_xtop, &bbox.r_ytop) == 4)
|
||
{
|
||
scalebox.r_xbot = bbox.r_xbot / reducer;
|
||
scalebox.r_xtop = bbox.r_xtop / reducer;
|
||
scalebox.r_ybot = bbox.r_ybot / reducer;
|
||
scalebox.r_ytop = bbox.r_ytop / reducer;
|
||
sprintf(propscaled, "%d %d %d %d",
|
||
bbox.r_xbot / reducer, bbox.r_ybot / reducer,
|
||
bbox.r_xtop / reducer, bbox.r_ytop / reducer);
|
||
|
||
DBPropPut(cellDef, "FIXED_BBOX", propscaled);
|
||
propvalue = proporig;
|
||
}
|
||
}
|
||
|
||
if (cellDef->cd_props != (ClientData)NULL)
|
||
{
|
||
FPRINTF(f, "<< properties >>\n");
|
||
DBPropEnum(cellDef, dbWritePropFunc, (ClientData)f);
|
||
}
|
||
|
||
if (propfound) DBPropPut(cellDef, "FIXED_BBOX", propvalue);
|
||
|
||
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;
|
||
|
||
lstring = (char *)mallocMagic(10 + strlen((char *)value) + strlen(key));
|
||
sprintf(lstring, "string %s %s\n", key, (char *)value);
|
||
FPRINTR(f, lstring);
|
||
freeMagic(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(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, fileName);
|
||
}
|
||
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
|
||
freeMagic(realname);
|
||
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
|
||
{
|
||
bool dereference = (cellDef->cd_flags & CDDEREFERENCE) ? TRUE : FALSE;
|
||
/* Re-aquire the lock on the new file by opening it. */
|
||
DBCellRead(cellDef, NULL, TRUE, dereference, NULL);
|
||
}
|
||
#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 so far. */
|
||
|
||
(void) StrDup(&cellDef->cd_file, expandname);
|
||
result = TRUE;
|
||
{
|
||
struct stat thestat;
|
||
realf = fopen(expandname,"r");
|
||
if (realf == NULL)
|
||
{
|
||
cellDef->cd_flags |= CDMODIFIED;
|
||
TxError("Warning: Cannot open file for writing!\n");
|
||
}
|
||
else
|
||
{
|
||
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);
|
||
TileTypeBitMask *lMask, *rMask;
|
||
|
||
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 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;
|
||
}
|
||
|
||
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
|
||
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;
|
||
}
|
||
|
||
/*
|
||
* ----------------------------------------------------------------------------
|
||
*
|
||
* dbClearCellFunc --
|
||
*
|
||
* Filter function that clears flags set by dbWriteCellFunc.
|
||
*
|
||
* Results:
|
||
* Always returns 0
|
||
*
|
||
* Side effects:
|
||
* Cell use flags changed.
|
||
*
|
||
* ----------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
dbClearCellFunc(cellUse, cdarg)
|
||
CellUse *cellUse; /* Cell use */
|
||
ClientData cdarg; /* Not used */
|
||
{
|
||
cellUse->cu_def->cd_flags &= ~CDVISITED;
|
||
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], *pathend, *pathstart, *parent;
|
||
|
||
t = &(cellUse->cu_transform);
|
||
b = &(cellUse->cu_def->cd_bbox);
|
||
pathstart = cellUse->cu_def->cd_file;
|
||
parent = arg->wa_name;
|
||
|
||
if (pathstart == NULL)
|
||
pathend = NULL;
|
||
else
|
||
{
|
||
char *slashptr, *pathorigin;
|
||
|
||
/* Get child path relative to the parent path */
|
||
|
||
pathorigin = pathstart;
|
||
pathend = strrchr(pathstart, '/');
|
||
slashptr = strchr(pathstart, '/');
|
||
while (slashptr)
|
||
{
|
||
if (!strncmp(pathorigin, parent, (int)(slashptr - pathorigin + 1)))
|
||
{
|
||
pathstart = slashptr + 1;
|
||
slashptr = strchr(pathstart, '/');
|
||
}
|
||
else
|
||
break;
|
||
}
|
||
if (pathend != NULL)
|
||
{
|
||
*pathend = '\0';
|
||
if (pathstart >= pathend)
|
||
pathstart = NULL;
|
||
}
|
||
}
|
||
|
||
if ((cellUse->cu_def->cd_flags & CDVISITED) || (pathend == NULL) ||
|
||
(pathstart == NULL) || (*pathstart == '\0'))
|
||
{
|
||
sprintf(cstring, "use %s %c%s\n", cellUse->cu_def->cd_name,
|
||
(cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ',
|
||
cellUse->cu_id);
|
||
}
|
||
else
|
||
{
|
||
/* If path starts with home path, then replace with "~" */
|
||
/* to make IP semi-portable between home directories */
|
||
/* with the same file structure. */
|
||
|
||
char *homedir = getenv("HOME");
|
||
|
||
if (!strncmp(cellUse->cu_def->cd_file, homedir, strlen(homedir))
|
||
&& (*(cellUse->cu_def->cd_file + strlen(homedir)) == '/'))
|
||
{
|
||
sprintf(cstring, "use %s %c%s ~%s\n", cellUse->cu_def->cd_name,
|
||
(cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ',
|
||
cellUse->cu_id, cellUse->cu_def->cd_file +
|
||
strlen(homedir));
|
||
}
|
||
else
|
||
{
|
||
sprintf(cstring, "use %s %c%s %s\n", cellUse->cu_def->cd_name,
|
||
(cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ',
|
||
cellUse->cu_id, pathstart);
|
||
}
|
||
}
|
||
FPRINTR(arg->wa_file, cstring);
|
||
|
||
cellUse->cu_def->cd_flags |= CDVISITED;
|
||
if (pathend != NULL) *pathend = '/';
|
||
|
||
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;
|
||
}
|