2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* DBio.c --
|
|
|
|
|
*
|
|
|
|
|
* Reading and writing of cells
|
|
|
|
|
*
|
2020-05-23 23:13:14 +02:00
|
|
|
* *********************************************************************
|
|
|
|
|
* * 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. *
|
2017-04-25 14:41:48 +02:00
|
|
|
* *********************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
|
2020-12-04 22:56:51 +01:00
|
|
|
#include "tcltk/tclmagic.h"
|
2017-04-25 14:41:48 +02:00
|
|
|
#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();
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
2020-05-23 23:13:14 +02:00
|
|
|
* file_is_not_writeable --
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-25 19:37:31 +01:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* DBSearchForTech --
|
|
|
|
|
*
|
|
|
|
|
* Helper function for automatically discovering a technology used in a
|
|
|
|
|
* .mag file when reading. This function will recursively search all
|
|
|
|
|
* directories rooted at "pathroot" looking for a file "techname" which
|
|
|
|
|
* must include the ".tech" extension. If found, the path name is returned.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* Pointer to a string containing the path name. This is allocated so as
|
|
|
|
|
* not to be lost, and must be freed by the caller.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
DBSearchForTech(techname, pathroot, level)
|
|
|
|
|
char *techname;
|
|
|
|
|
char *pathroot;
|
|
|
|
|
int level;
|
|
|
|
|
{
|
|
|
|
|
char *newpath, *found;
|
|
|
|
|
struct dirent *tdent;
|
|
|
|
|
DIR *tdir;
|
|
|
|
|
|
|
|
|
|
/* Avoid potential infinite looping. Any tech file should not be very */
|
|
|
|
|
/* far down the path. 10 levels is already excessive. */
|
|
|
|
|
if (level > 10) return NULL;
|
|
|
|
|
|
|
|
|
|
tdir = opendir(pathroot);
|
|
|
|
|
if (tdir) {
|
|
|
|
|
|
|
|
|
|
/* Read the directory contents of tdir */
|
|
|
|
|
while ((tdent = readdir(tdir)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (tdent->d_type != DT_DIR)
|
|
|
|
|
{
|
|
|
|
|
if (!strcmp(tdent->d_name, techname))
|
|
|
|
|
return pathroot;
|
|
|
|
|
}
|
|
|
|
|
else if (strcmp(tdent->d_name, ".") && strcmp(tdent->d_name, ".."))
|
|
|
|
|
{
|
|
|
|
|
newpath = mallocMagic(strlen(pathroot) + strlen(tdent->d_name) + 3);
|
|
|
|
|
sprintf(newpath, "%s/%s", pathroot, tdent->d_name);
|
|
|
|
|
found = DBSearchForTech(techname, newpath, level + 1);
|
|
|
|
|
if (found != newpath) freeMagic(newpath);
|
|
|
|
|
if (found) return found;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
closedir(tdir);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* DBAddStandardCellPaths --
|
|
|
|
|
*
|
|
|
|
|
* Search for .mag files in any directory below "pathptr", for any
|
|
|
|
|
* directory found containing .mag files, add that path to the search
|
|
|
|
|
* path for cells.
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* Number of paths added to CellLibPath.
|
|
|
|
|
*
|
|
|
|
|
* Side effects:
|
|
|
|
|
* May add paths to the CellLibPath.
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
DBAddStandardCellPaths(pathptr, level)
|
|
|
|
|
char *pathptr;
|
|
|
|
|
int level;
|
|
|
|
|
{
|
|
|
|
|
int paths = 0;
|
|
|
|
|
struct dirent *tdent;
|
|
|
|
|
char *newpath;
|
|
|
|
|
DIR *tdir;
|
|
|
|
|
bool magfound = FALSE;
|
|
|
|
|
|
|
|
|
|
/* Avoid potential infinite looping. Any tech file should not be very */
|
|
|
|
|
/* far down the path. 10 levels is already excessive. */
|
|
|
|
|
if (level > 10) return 0;
|
|
|
|
|
|
|
|
|
|
tdir = opendir(pathptr);
|
|
|
|
|
if (tdir) {
|
|
|
|
|
|
|
|
|
|
while ((tdent = readdir(tdir)) != NULL)
|
|
|
|
|
{
|
|
|
|
|
if ((tdent->d_type == DT_DIR) &&
|
|
|
|
|
(strcmp(tdent->d_name, ".") && strcmp(tdent->d_name, "..")))
|
|
|
|
|
{
|
|
|
|
|
/* Scan the directory contents of tdir for more subdirectories */
|
|
|
|
|
newpath = mallocMagic(strlen(pathptr) + strlen(tdent->d_name) + 3);
|
|
|
|
|
sprintf(newpath, "%s/%s", pathptr, tdent->d_name);
|
|
|
|
|
paths += DBAddStandardCellPaths(newpath, level + 1);
|
|
|
|
|
freeMagic(newpath);
|
|
|
|
|
}
|
|
|
|
|
else if (tdent->d_type != DT_DIR)
|
|
|
|
|
{
|
|
|
|
|
/* Scan the directory contents of tdir for .mag files */
|
|
|
|
|
if (!strcmp(tdent->d_name + strlen(tdent->d_name) - 4, ".mag"))
|
|
|
|
|
{
|
|
|
|
|
if (magfound == FALSE)
|
|
|
|
|
{
|
|
|
|
|
PaAppend(&CellLibPath, pathptr);
|
|
|
|
|
paths++;
|
|
|
|
|
magfound = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
closedir(tdir);
|
|
|
|
|
}
|
|
|
|
|
return paths;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2018-06-17 21:12:00 +02:00
|
|
|
* "tri <xbot> <ybot> <xtop> <ytop> <dir>" with <dir> indicating
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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.
|
2020-05-23 23:13:14 +02:00
|
|
|
*
|
2017-04-25 14:41:48 +02:00
|
|
|
* 6. Zero or more groups of lines describing cell uses. Each group
|
|
|
|
|
* is of the form
|
2018-06-17 21:12:00 +02:00
|
|
|
* use <filename> <id> [<path>]
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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
|
2018-06-17 21:12:00 +02:00
|
|
|
* 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.
|
2017-04-25 14:41:48 +02:00
|
|
|
*
|
|
|
|
|
* 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
|
2019-11-14 21:18:26 +01:00
|
|
|
dbCellReadDef(f, cellDef, name, ignoreTech, dereference)
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
|
|
|
|
*/
|
2019-11-14 21:18:26 +01:00
|
|
|
bool dereference; /* If TRUE, ignore path references in the input */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
int cellStamp = 0, rectCount = 0, rectReport = 10000;
|
|
|
|
|
char line[2048], tech[50], layername[50];
|
|
|
|
|
PaintResultType *ptable;
|
2021-03-17 03:46:46 +01:00
|
|
|
bool result = TRUE, scaleLimit = FALSE, has_mismatch;
|
2017-04-25 14:41:48 +02:00
|
|
|
Rect *rp;
|
|
|
|
|
int c;
|
|
|
|
|
TileType type, rtype, loctype;
|
|
|
|
|
TileTypeBitMask *rmask, typemask;
|
|
|
|
|
Plane *plane;
|
|
|
|
|
Rect r;
|
|
|
|
|
int n = 1, d = 1;
|
2021-11-21 02:54:51 +01:00
|
|
|
HashTable dbUseTable;
|
2021-11-25 19:20:28 +01:00
|
|
|
bool needcleanup = FALSE;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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
|
|
|
|
|
{
|
2021-02-25 19:37:31 +01:00
|
|
|
/* If no cells are currently in memory, then make an
|
|
|
|
|
* attempt to find the technology associated with the
|
|
|
|
|
* layout and load it.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (!CmdCheckForPaintFunc())
|
|
|
|
|
{
|
|
|
|
|
/* Places to check for a technology: In the PDK_ROOT
|
|
|
|
|
* (PDKROOT) directory, PDK_PATH (PDKPATH) from environment
|
|
|
|
|
* variables, and CAD_ROOT from Tcl variables; the open_pdks
|
|
|
|
|
* default install path /usr/share/pdk/, and magic's install
|
|
|
|
|
* path. For CAD_ROOT the variable is expected to point to
|
|
|
|
|
* a path containing the techfile. For PDK_PATH and PDK_ROOT,
|
|
|
|
|
* search the directory tree for any subdirectory called
|
|
|
|
|
* magic/ and look for a compatible techfile there.
|
|
|
|
|
*/
|
|
|
|
|
char *found = NULL;
|
|
|
|
|
char *string, *techfullname;
|
|
|
|
|
|
|
|
|
|
techfullname = mallocMagic(strlen(tech) + 6);
|
|
|
|
|
sprintf(techfullname, "%s.tech", tech);
|
|
|
|
|
|
|
|
|
|
string = getenv("PDK_PATH");
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
string = getenv("PDKPATH");
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
}
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
string = getenv("PDK_ROOT");
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
}
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
string = getenv("PDKROOT");
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
}
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
found = DBSearchForTech(techfullname, "/usr/share/pdk", 0);
|
|
|
|
|
}
|
|
|
|
|
#ifdef MAGIC_WRAPPER
|
|
|
|
|
/* Additional checks for PDK_PATH, etc., as Tcl variables. */
|
|
|
|
|
/* This is unlikely, as they would have to be set in a */
|
|
|
|
|
/* startup file, and a startup file is likely to just load */
|
|
|
|
|
/* the technology itself. */
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
string = (char *)Tcl_GetVar(magicinterp, "PDK_ROOT",
|
|
|
|
|
TCL_GLOBAL_ONLY);
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
}
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
string = (char *)Tcl_GetVar(magicinterp, "PDKROOT",
|
|
|
|
|
TCL_GLOBAL_ONLY);
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
}
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
string = (char *)Tcl_GetVar(magicinterp, "PDK_PATH",
|
|
|
|
|
TCL_GLOBAL_ONLY);
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
}
|
|
|
|
|
if (!found)
|
|
|
|
|
{
|
|
|
|
|
string = (char *)Tcl_GetVar(magicinterp, "PDKPATH",
|
|
|
|
|
TCL_GLOBAL_ONLY);
|
|
|
|
|
if (string)
|
|
|
|
|
found = DBSearchForTech(techfullname, string, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
freeMagic(techfullname);
|
|
|
|
|
if (found)
|
|
|
|
|
{
|
|
|
|
|
char *sptr;
|
|
|
|
|
PaAppend(&SysLibPath, found);
|
|
|
|
|
|
|
|
|
|
TxError("Loading technology %s\n", tech);
|
|
|
|
|
if (!TechLoad(tech, 0))
|
|
|
|
|
TxError("Error in loading technology file\n");
|
|
|
|
|
else if ((sptr = strstr(found, "libs.tech")) != NULL)
|
|
|
|
|
{
|
|
|
|
|
int paths = 0;
|
|
|
|
|
/* Additional automatic handling of open_pdks- */
|
|
|
|
|
/* style PDKs. Append the libs.ref libraries */
|
|
|
|
|
/* to the cell search path. */
|
|
|
|
|
|
|
|
|
|
strcpy(sptr + 5, "ref");
|
|
|
|
|
paths = DBAddStandardCellPaths(found, 0);
|
|
|
|
|
if (paths > 0)
|
|
|
|
|
TxPrintf("Cell path is now \"%s\"\n", CellLibPath);
|
|
|
|
|
}
|
|
|
|
|
freeMagic(found);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (strcmp(DBTechName, tech))
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (dbFgets(line, sizeof line, f) == NULL)
|
|
|
|
|
goto badfile;
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* 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");
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2020-05-23 23:13:14 +02:00
|
|
|
TxPrintf("Scaled magic input cell %s geometry by factor of %d",
|
2017-04-25 14:41:48 +02:00
|
|
|
cellDef->cd_name, n);
|
2020-05-23 23:13:14 +02:00
|
|
|
if (d > 1)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
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();
|
2021-11-21 02:54:51 +01:00
|
|
|
HashInit(&dbUseTable, 32, HT_STRINGKEYS);
|
2021-11-25 19:20:28 +01:00
|
|
|
needcleanup = TRUE;
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2021-11-21 02:54:51 +01:00
|
|
|
if (!dbReadUse(cellDef, line, sizeof line, f, n, d,
|
|
|
|
|
dereference, &dbUseTable))
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
|
|
|
|
*/
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
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.
|
|
|
|
|
*/
|
2021-03-17 03:46:46 +01:00
|
|
|
has_mismatch = FALSE;
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
2021-03-17 03:46:46 +01:00
|
|
|
has_mismatch = TRUE;
|
2017-04-25 14:41:48 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-08-02 20:50:32 +02:00
|
|
|
/* Update timestamp flags */
|
2021-03-17 03:46:46 +01:00
|
|
|
if (has_mismatch) DBFlagMismatches(cellDef);
|
2019-08-02 20:50:32 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-25 19:20:28 +01:00
|
|
|
if (needcleanup) HashKill(&dbUseTable);
|
2017-04-25 14:41:48 +02:00
|
|
|
UndoEnable();
|
2021-03-17 03:46:46 +01:00
|
|
|
/* Disabled 3/16/2021. Let <<checkpaint>> in file force a DRC check */
|
|
|
|
|
/* DRCCheckThis(cellDef, TT_CHECKPAINT, (Rect *) NULL); */
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
2020-05-23 23:13:14 +02:00
|
|
|
*
|
2017-04-25 14:41:48 +02:00
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
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;
|
2020-05-28 23:09:03 +02:00
|
|
|
StrDup(&DBbackupFile, tempname);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
closedir(cwd);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-05-28 23:09:03 +02:00
|
|
|
StrDup(&DBbackupFile, filename);
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
2020-03-21 17:40:35 +01:00
|
|
|
cellDef = DBCellNewDef(rootname);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
cellDef->cd_flags &= ~CDNOTFOUND;
|
|
|
|
|
cellDef->cd_flags |= CDAVAILABLE;
|
|
|
|
|
|
2019-11-14 21:18:26 +01:00
|
|
|
if (dbCellReadDef(f, cellDef, filename, TRUE, FALSE) == FALSE)
|
2017-04-25 14:41:48 +02:00
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
if (dbFgets(line, sizeof(line), f) == NULL)
|
|
|
|
|
{
|
|
|
|
|
TxError("Error in backup file %s; partial restore only!\n",
|
|
|
|
|
name);
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2019-08-02 20:50:32 +02:00
|
|
|
/* Update timestamp flags from dbCellReadDef() */
|
2020-01-18 21:55:05 +01:00
|
|
|
DBFlagMismatches(cellDef);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
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 --
|
|
|
|
|
*
|
2021-01-14 21:21:39 +01:00
|
|
|
* This is the wrapper for dbCellReadDef. The routine has been divided into
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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
|
2019-11-14 21:18:26 +01:00
|
|
|
DBCellRead(cellDef, name, ignoreTech, dereference, errptr)
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
|
|
|
|
*/
|
2020-01-02 16:13:04 +01:00
|
|
|
bool dereference; /* If TRUE then ignore path argument to uses */
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
|
|
|
|
{
|
2019-11-14 21:18:26 +01:00
|
|
|
result = (dbCellReadDef(f, cellDef, name, ignoreTech, dereference));
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
#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.
|
|
|
|
|
*
|
2020-12-30 15:43:24 +01:00
|
|
|
* Notes:
|
|
|
|
|
* Global variable DBVerbose determines whether or not error
|
|
|
|
|
* messages are generated by this routine. This can be controlled
|
|
|
|
|
* by "load -quiet".
|
|
|
|
|
*
|
2017-04-25 14:41:48 +02:00
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2018-04-17 03:43:35 +02:00
|
|
|
/* 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)
|
|
|
|
|
{
|
2018-06-17 21:12:00 +02:00
|
|
|
/* 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. */
|
|
|
|
|
|
2020-03-21 15:16:33 +01:00
|
|
|
if (pptr != NULL) *pptr = '.';
|
2020-12-30 15:43:24 +01:00
|
|
|
if (DBVerbose)
|
|
|
|
|
TxError("Warning: Parent cell lists instance of \"%s\" at "
|
|
|
|
|
"bad file path %s.\n",
|
|
|
|
|
cellDef->cd_name, cellDef->cd_file);
|
2018-06-17 21:12:00 +02:00
|
|
|
|
|
|
|
|
/* Write the new path to cd_file or else magic will */
|
|
|
|
|
/* generate another error later. */
|
2020-05-28 23:09:03 +02:00
|
|
|
StrDup(&cellDef->cd_file, filename);
|
2020-03-21 15:16:33 +01:00
|
|
|
|
2020-12-30 15:43:24 +01:00
|
|
|
if (DBVerbose)
|
|
|
|
|
{
|
|
|
|
|
TxError("The cell exists in the search paths at %s.\n", filename);
|
|
|
|
|
TxError("The discovered version will be used.\n");
|
|
|
|
|
}
|
2018-04-17 16:08:00 +02:00
|
|
|
}
|
2018-04-17 03:43:35 +02:00
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
2020-12-30 15:43:24 +01:00
|
|
|
{
|
|
|
|
|
if (DBVerbose)
|
|
|
|
|
TxError("File %s%s couldn't be read\n", name, DBSuffix);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else if (cellDef->cd_file != (char *) NULL)
|
2020-12-30 15:43:24 +01:00
|
|
|
{
|
|
|
|
|
if (DBVerbose)
|
|
|
|
|
TxError("File %s couldn't be read\n", cellDef->cd_file);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
else {
|
2020-12-30 15:43:24 +01:00
|
|
|
if (DBVerbose)
|
|
|
|
|
TxError("Cell %s couldn't be read\n", cellDef->cd_name);
|
2017-04-25 14:41:48 +02:00
|
|
|
realname = (char *) mallocMagic((unsigned) (strlen(cellDef->cd_name)
|
|
|
|
|
+ strlen(DBSuffix) + 1));
|
|
|
|
|
(void) sprintf(realname, "%s%s", cellDef->cd_name, DBSuffix);
|
2020-05-28 23:09:03 +02:00
|
|
|
StrDup(&cellDef->cd_file, realname);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2020-12-30 15:43:24 +01:00
|
|
|
if (errptr && DBVerbose) TxError("%s\n", strerror(*errptr));
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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)
|
2020-03-20 18:40:16 +01:00
|
|
|
{
|
|
|
|
|
/* Remove any ".mag" file extension */
|
|
|
|
|
char *pptr = strrchr(filename, '.');
|
|
|
|
|
if (pptr != NULL)
|
|
|
|
|
if (!strcmp(pptr, DBSuffix)) *pptr = '\0';
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
(void) StrDup(&cellDef->cd_file, filename);
|
2020-03-20 18:40:16 +01:00
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
2021-11-21 02:54:51 +01:00
|
|
|
dbReadUse(cellDef, line, len, f, scalen, scaled, dereference, dbUseTable)
|
2017-04-25 14:41:48 +02:00
|
|
|
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 */
|
2019-11-14 21:18:26 +01:00
|
|
|
bool dereference; /* If TRUE, ignore path references */
|
2021-11-21 02:54:51 +01:00
|
|
|
HashTable *dbUseTable; /* Hash table of instances seen in this file */
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
int xlo, xhi, ylo, yhi, xsep, ysep, childStamp;
|
2018-03-23 15:52:57 +01:00
|
|
|
int absa, absb, absd, abse, nconv;
|
|
|
|
|
char cellname[1024], useid[1024], path[1024];
|
2017-04-25 14:41:48 +02:00
|
|
|
CellUse *subCellUse;
|
|
|
|
|
CellDef *subCellDef;
|
|
|
|
|
Transform t;
|
|
|
|
|
Rect r;
|
2021-11-21 02:54:51 +01:00
|
|
|
bool locked, firstUse;
|
2018-04-17 16:08:00 +02:00
|
|
|
char *slashptr, *pathptr;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (strncmp(line, "use", 3) != 0)
|
|
|
|
|
{
|
|
|
|
|
TxError("Expected \"use\" line but saw: %s", line);
|
|
|
|
|
return (FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useid[0] = '\0';
|
2018-03-23 15:52:57 +01:00
|
|
|
nconv = sscanf(line, "use %1023s %1023s %1023s", cellname, useid, path);
|
|
|
|
|
if (nconv < 1)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
TxError("Malformed \"use\" line: %s", line);
|
|
|
|
|
return (FALSE);
|
|
|
|
|
}
|
2018-03-23 15:52:57 +01:00
|
|
|
/* Make sure useid[0] is an empty string if no useid was provided */
|
|
|
|
|
if (nconv == 1) useid[0] = '\0';
|
2018-04-17 16:08:00 +02:00
|
|
|
if (nconv <= 2) path[0] = '\0';
|
|
|
|
|
|
|
|
|
|
pathptr = &path[0];
|
|
|
|
|
while (*pathptr == ' ' || *pathptr == '\t') pathptr++;
|
2019-11-14 21:18:26 +01:00
|
|
|
if ((dereference == TRUE) || (*pathptr == '\n')) *pathptr = '\0';
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-21 02:54:51 +01:00
|
|
|
/* Flag if this is the first time the cell is used in the file,
|
|
|
|
|
* so that we can expect that additional instances will not have
|
|
|
|
|
* path information given.
|
|
|
|
|
*/
|
|
|
|
|
firstUse = (HashLookOnly(dbUseTable, cellname) == NULL) ? TRUE : FALSE;
|
|
|
|
|
if (firstUse) HashFind(dbUseTable, cellname);
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
2020-03-21 17:40:35 +01:00
|
|
|
subCellDef = DBCellNewDef(cellname);
|
2017-04-25 14:41:48 +02:00
|
|
|
subCellDef->cd_timestamp = childStamp;
|
|
|
|
|
|
|
|
|
|
/* Make sure rectangle is non-degenerate */
|
|
|
|
|
if (GEO_RECTNULL(&r))
|
|
|
|
|
{
|
2021-11-21 02:54:51 +01:00
|
|
|
if (firstUse == TRUE)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2021-11-21 02:54:51 +01:00
|
|
|
if (firstUse == TRUE)
|
|
|
|
|
{
|
|
|
|
|
TxPrintf(" to %d %d %d %d\n",
|
|
|
|
|
r.r_xbot, r.r_ybot, r.r_xtop, r.r_ytop);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
subCellDef->cd_bbox = r;
|
|
|
|
|
subCellDef->cd_extended = r;
|
|
|
|
|
}
|
|
|
|
|
else if (DBIsAncestor(subCellDef, cellDef))
|
|
|
|
|
{
|
|
|
|
|
/*
|
2020-12-04 22:56:51 +01:00
|
|
|
* Watch out for attempts to create circular structures.
|
2017-04-25 14:41:48 +02:00
|
|
|
* If this happens, disregard the subcell.
|
|
|
|
|
*/
|
2021-11-21 02:54:51 +01:00
|
|
|
if (firstUse == TRUE)
|
|
|
|
|
{
|
|
|
|
|
TxPrintf("Subcells are used circularly!\n");
|
|
|
|
|
TxPrintf("Ignoring subcell %s of %s.\n", cellname,
|
|
|
|
|
cellDef->cd_name);
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
goto nextLine;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-04 22:56:51 +01:00
|
|
|
#ifdef MAGIC_WRAPPER
|
|
|
|
|
/* If path starts with '$' then check for a possible Tcl variable */
|
|
|
|
|
/* replacement. */
|
|
|
|
|
|
|
|
|
|
if (*pathptr == '$')
|
|
|
|
|
{
|
|
|
|
|
char *varstart, *varend, savechar, *tvar;
|
|
|
|
|
|
|
|
|
|
varstart = pathptr + 1;
|
|
|
|
|
if (*varstart == '{') varstart++;
|
|
|
|
|
varend = varstart + 1;
|
|
|
|
|
while (*varend != '\0' && *varend != '}' && *varend != '/'
|
|
|
|
|
&& *varend != '\n' && *varend != ' ') varend++;
|
|
|
|
|
savechar = *varend;
|
|
|
|
|
*varend = '\0';
|
|
|
|
|
|
|
|
|
|
tvar = (char *)Tcl_GetVar(magicinterp, varstart, TCL_GLOBAL_ONLY);
|
|
|
|
|
*varend = savechar;
|
|
|
|
|
if (savechar == '}') varend++;
|
|
|
|
|
if (tvar)
|
|
|
|
|
{
|
|
|
|
|
memmove(pathptr + strlen(tvar), varend, strlen(varend) + 1);
|
|
|
|
|
memmove(pathptr, tvar, strlen(tvar));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-04-06 21:09:43 +02:00
|
|
|
/* 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. */
|
2018-03-23 18:06:58 +01:00
|
|
|
|
2021-11-21 02:54:51 +01:00
|
|
|
/* If there is no pathptr, then the situation is one of these two: */
|
|
|
|
|
/* (1) The instance is not the first time the cell was encountered */
|
|
|
|
|
/* in the file, or (2) The cell is in the same path as the parent. */
|
|
|
|
|
/* Only case (2) needs to be handled. */
|
|
|
|
|
|
|
|
|
|
if ((firstUse == TRUE) && ((*pathptr == '\0') ||
|
|
|
|
|
((*pathptr != '/') && (*pathptr != '~'))))
|
2018-04-06 21:09:43 +02:00
|
|
|
if ((cellDef->cd_file != NULL) &&
|
2018-03-23 18:06:58 +01:00
|
|
|
(slashptr = strrchr(cellDef->cd_file, '/')) != NULL)
|
2018-04-06 21:09:43 +02:00
|
|
|
{
|
|
|
|
|
*slashptr = '\0';
|
2018-04-17 16:08:00 +02:00
|
|
|
if (*pathptr == '\0')
|
2018-04-06 21:09:43 +02:00
|
|
|
strcpy(path, cellDef->cd_file);
|
|
|
|
|
else
|
2018-03-23 18:06:58 +01:00
|
|
|
{
|
|
|
|
|
char savepath[1024];
|
2018-04-17 16:08:00 +02:00
|
|
|
strcpy(savepath, pathptr);
|
2018-03-23 18:06:58 +01:00
|
|
|
sprintf(path, "%s/%s", cellDef->cd_file, savepath);
|
|
|
|
|
}
|
2018-04-17 16:08:00 +02:00
|
|
|
pathptr = &path[0];
|
2018-04-06 21:09:43 +02:00
|
|
|
*slashptr = '/';
|
|
|
|
|
}
|
2018-03-23 18:06:58 +01:00
|
|
|
|
2018-04-06 21:09:43 +02:00
|
|
|
/* 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. */
|
2018-03-23 18:06:58 +01:00
|
|
|
|
2018-04-17 16:08:00 +02:00
|
|
|
if (*pathptr == '~' && *(pathptr + 1) == '/')
|
2018-04-06 21:09:43 +02:00
|
|
|
if ((cellDef->cd_file != NULL) && (cellDef->cd_file[0] == '/'))
|
|
|
|
|
{
|
|
|
|
|
char *homedir = getenv("HOME");
|
|
|
|
|
if (strncmp(cellDef->cd_file, homedir, strlen(homedir)) ||
|
2018-03-23 18:06:58 +01:00
|
|
|
*(cellDef->cd_file + strlen(homedir)) != '/')
|
2018-04-06 21:09:43 +02:00
|
|
|
{
|
|
|
|
|
char *homeroot = strrchr(homedir, '/');
|
|
|
|
|
int rootlen = (int)(homeroot - homedir) + 1;
|
|
|
|
|
if (!strncmp(cellDef->cd_file, homedir, rootlen))
|
2018-03-23 18:06:58 +01:00
|
|
|
{
|
2018-04-06 21:09:43 +02:00
|
|
|
char savepath[1024];
|
|
|
|
|
char *userbrk = strchr(cellDef->cd_file + rootlen, '/');
|
|
|
|
|
if (userbrk != NULL)
|
2018-03-23 18:06:58 +01:00
|
|
|
{
|
2018-04-06 21:09:43 +02:00
|
|
|
int userlen = (int)(userbrk - cellDef->cd_file);
|
2018-04-17 16:08:00 +02:00
|
|
|
strcpy(savepath, pathptr + 1);
|
2018-04-06 21:09:43 +02:00
|
|
|
strcpy(path, cellDef->cd_file);
|
|
|
|
|
strcpy(path + userlen, savepath);
|
2018-04-17 16:08:00 +02:00
|
|
|
pathptr = &path[0];
|
2018-03-23 18:06:58 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-06 21:09:43 +02:00
|
|
|
}
|
2018-03-23 18:06:58 +01:00
|
|
|
|
2018-04-06 21:09:43 +02:00
|
|
|
/* 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. */
|
2018-03-23 15:52:57 +01:00
|
|
|
|
2018-04-24 15:29:05 +02:00
|
|
|
if (*pathptr != '\0')
|
2018-04-06 21:09:43 +02:00
|
|
|
{
|
2018-04-24 15:29:05 +02:00
|
|
|
if (subCellDef->cd_file != NULL)
|
2018-03-23 15:52:57 +01:00
|
|
|
{
|
2018-04-24 15:29:05 +02:00
|
|
|
slashptr = strrchr(subCellDef->cd_file, '/');
|
|
|
|
|
if (slashptr != NULL)
|
2018-04-06 21:09:43 +02:00
|
|
|
{
|
2019-12-15 00:30:48 +01:00
|
|
|
bool pathOK = FALSE;
|
2021-11-30 18:17:21 +01:00
|
|
|
char *cwddir = getenv("PWD");
|
2018-04-24 15:29:05 +02:00
|
|
|
*slashptr = '\0';
|
|
|
|
|
|
2019-12-15 00:30:48 +01:00
|
|
|
/* 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;
|
|
|
|
|
}
|
2021-11-30 18:17:21 +01:00
|
|
|
else if (!strcmp(cwddir, pathptr)) pathOK = TRUE;
|
2019-12-15 00:30:48 +01:00
|
|
|
|
2020-03-13 15:33:44 +01:00
|
|
|
if ((pathOK == FALSE) && strcmp(subCellDef->cd_file, pathptr)
|
2021-11-21 02:54:51 +01:00
|
|
|
&& (dereference == FALSE) && (firstUse == TRUE))
|
2018-04-24 15:29:05 +02:00
|
|
|
{
|
2021-11-30 18:17:21 +01:00
|
|
|
FILE *ftest;
|
|
|
|
|
|
2018-04-24 15:29:05 +02:00
|
|
|
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);
|
|
|
|
|
|
2021-11-30 18:17:21 +01:00
|
|
|
/* Test file at path. If path is invalid then ignore it */
|
|
|
|
|
/* (automatic dereferencing due to unavailability). */
|
|
|
|
|
|
|
|
|
|
ftest = PaOpen(cellname, "r", DBSuffix, pathptr, (char *)NULL,
|
|
|
|
|
(char **) NULL);
|
|
|
|
|
if (ftest == NULL)
|
|
|
|
|
{
|
|
|
|
|
TxError("New path does not exist and will be ignored.\n");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
char *newname = (char *)mallocMagic(strlen(cellname) + 6);
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
/* To do: Run checksum on file (not yet implemented) */
|
|
|
|
|
fclose(ftest);
|
2018-04-24 15:29:05 +02:00
|
|
|
|
2021-11-30 18:17:21 +01:00
|
|
|
while (TRUE)
|
|
|
|
|
{
|
|
|
|
|
sprintf(newname, "%s#%d", cellname, i);
|
|
|
|
|
if (DBCellLookDef(newname) == NULL) break;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
TxError("Cell name conflict: Renaming original cell to %s.\n",
|
|
|
|
|
newname);
|
|
|
|
|
|
|
|
|
|
DBCellRename(cellname, newname, TRUE);
|
|
|
|
|
subCellDef = DBCellNewDef(cellname);
|
|
|
|
|
subCellDef->cd_timestamp = childStamp;
|
|
|
|
|
subCellDef->cd_bbox = r;
|
|
|
|
|
subCellDef->cd_extended = r;
|
|
|
|
|
freeMagic(newname);
|
|
|
|
|
|
|
|
|
|
/* Reconstruct file from path and cellname */
|
|
|
|
|
|
|
|
|
|
strcat(path, "/");
|
|
|
|
|
strcat(path, subCellDef->cd_name);
|
|
|
|
|
strcat(path, DBSuffix);
|
|
|
|
|
StrDup(&subCellDef->cd_file, path);
|
|
|
|
|
}
|
2018-04-24 15:29:05 +02:00
|
|
|
}
|
|
|
|
|
*slashptr = '/';
|
2018-03-23 15:52:57 +01:00
|
|
|
}
|
2021-11-30 18:17:21 +01:00
|
|
|
|
|
|
|
|
/* The same reasoning applies if the existing file has a */
|
|
|
|
|
/* default path but the new cell has a (different) path. */
|
|
|
|
|
/* The paths only match if pathptr is the CWD. */
|
|
|
|
|
|
|
|
|
|
else if ((pathptr != NULL) && (*pathptr != '\0'))
|
|
|
|
|
{
|
|
|
|
|
bool pathOK = FALSE;
|
|
|
|
|
char *cwddir = getenv("PWD");
|
|
|
|
|
|
|
|
|
|
if (cwddir != NULL)
|
|
|
|
|
{
|
|
|
|
|
if (*pathptr == '~')
|
|
|
|
|
{
|
|
|
|
|
/* Check if the path is the same as the current directory */
|
|
|
|
|
|
|
|
|
|
char *homedir = getenv("HOME");
|
|
|
|
|
if (!strncmp(cwddir, homedir, strlen(homedir))
|
|
|
|
|
&& (!strcmp(cwddir + strlen(homedir),
|
|
|
|
|
pathptr + 1)))
|
|
|
|
|
pathOK = TRUE;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp(cwddir, pathptr)) pathOK = TRUE;
|
|
|
|
|
|
|
|
|
|
if ((pathOK == FALSE) && strcmp(cwddir, pathptr)
|
|
|
|
|
&& (dereference == FALSE) && (firstUse == TRUE))
|
|
|
|
|
{
|
|
|
|
|
FILE *ftest;
|
|
|
|
|
|
|
|
|
|
TxError("Duplicate cell in %s: Instance of cell %s is from "
|
|
|
|
|
"path %s but cell was previously read from "
|
|
|
|
|
"the current directory.\n",
|
|
|
|
|
cellDef->cd_name, cellname, pathptr);
|
|
|
|
|
|
|
|
|
|
/* Test file at path. If path is invalid then ignore */
|
|
|
|
|
/* it (automatic dereferencing due to unavailability). */
|
|
|
|
|
|
|
|
|
|
ftest = PaOpen(cellname, "r", DBSuffix, pathptr, (char *)NULL,
|
|
|
|
|
(char **) NULL);
|
|
|
|
|
if (ftest == NULL)
|
|
|
|
|
{
|
|
|
|
|
TxError("New path does not exist and will be ignored.\n");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
char *newname = (char *)mallocMagic(strlen(cellname) + 6);
|
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
|
|
/* To do: Run checksum on file (not yet implemented) */
|
|
|
|
|
fclose(ftest);
|
|
|
|
|
|
|
|
|
|
while (TRUE)
|
|
|
|
|
{
|
|
|
|
|
sprintf(newname, "%s#%d", cellname, i);
|
|
|
|
|
if (DBCellLookDef(newname) == NULL) break;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
TxError("Cell name conflict: Renaming original "
|
|
|
|
|
"cell to %s.\n", newname);
|
|
|
|
|
|
|
|
|
|
DBCellRename(cellname, newname, TRUE);
|
|
|
|
|
subCellDef = DBCellNewDef(cellname);
|
|
|
|
|
subCellDef->cd_timestamp = childStamp;
|
|
|
|
|
subCellDef->cd_bbox = r;
|
|
|
|
|
subCellDef->cd_extended = r;
|
|
|
|
|
freeMagic(newname);
|
|
|
|
|
|
|
|
|
|
/* Reconstruct file from path and cellname */
|
|
|
|
|
|
|
|
|
|
strcat(path, "/");
|
|
|
|
|
strcat(path, subCellDef->cd_name);
|
|
|
|
|
strcat(path, DBSuffix);
|
|
|
|
|
StrDup(&subCellDef->cd_file, path);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-23 15:52:57 +01:00
|
|
|
}
|
2018-04-24 15:29:05 +02:00
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Reconstruct file from path and cellname */
|
2018-03-23 18:06:58 +01:00
|
|
|
|
2018-04-24 15:29:05 +02:00
|
|
|
strcat(path, "/");
|
|
|
|
|
strcat(path, subCellDef->cd_name);
|
|
|
|
|
strcat(path, DBSuffix);
|
|
|
|
|
StrDup(&subCellDef->cd_file, path);
|
|
|
|
|
}
|
2018-03-23 15:52:57 +01:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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;
|
|
|
|
|
|
2019-03-23 00:58:47 +01:00
|
|
|
/* Also process FIXED_BBOX property, as units must match */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
if (!strcmp(propertyname, "FIXED_BBOX"))
|
|
|
|
|
{
|
2019-03-23 00:58:47 +01:00
|
|
|
Rect locbbox;
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (sscanf(propertyvalue, "%d %d %d %d",
|
2019-03-23 00:58:47 +01:00
|
|
|
&(locbbox.r_xbot),
|
|
|
|
|
&(locbbox.r_ybot),
|
|
|
|
|
&(locbbox.r_xtop),
|
|
|
|
|
&(locbbox.r_ytop)) != 4)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
TxError("Cannot read bounding box values in %s property",
|
|
|
|
|
propertyname);
|
|
|
|
|
storedvalue = StrDup((char **)NULL, propertyvalue);
|
|
|
|
|
(void) DBPropPut(cellDef, propertyname, storedvalue);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (scalen > 1)
|
|
|
|
|
{
|
2019-03-23 00:58:47 +01:00
|
|
|
locbbox.r_xbot *= scalen;
|
|
|
|
|
locbbox.r_ybot *= scalen;
|
|
|
|
|
locbbox.r_xtop *= scalen;
|
|
|
|
|
locbbox.r_ytop *= scalen;
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
if (scaled > 1)
|
|
|
|
|
{
|
2019-03-23 00:58:47 +01:00
|
|
|
locbbox.r_xbot /= scaled;
|
|
|
|
|
locbbox.r_ybot /= scaled;
|
|
|
|
|
locbbox.r_xtop /= scaled;
|
|
|
|
|
locbbox.r_ytop /= scaled;
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
cellDef->cd_flags |= CDFIXEDBBOX;
|
2019-03-23 00:58:47 +01:00
|
|
|
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);
|
2019-07-25 02:36:55 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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 */
|
|
|
|
|
{
|
2020-05-29 04:06:22 +02:00
|
|
|
char layername[50], text[1024], port_use[50], port_class[50], port_shape[50];
|
2017-04-25 14:41:48 +02:00
|
|
|
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) ||
|
2020-05-29 04:06:22 +02:00
|
|
|
(((ntok = sscanf(line, "port %d %4s %49s %49s %49s",
|
|
|
|
|
&idx, ppos, port_use, port_class, port_shape)) != 2) &&
|
|
|
|
|
(ntok != 4) && (ntok != 5)))
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
TxError("Skipping bad \"port\" line: %s", line);
|
|
|
|
|
goto nextlabel;
|
|
|
|
|
}
|
2017-08-02 04:14:42 +02:00
|
|
|
/* lab->lab_flags &= ~LABEL_STICKY; */
|
2021-12-13 04:09:31 +01:00
|
|
|
lab->lab_port = idx;
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-29 04:06:22 +02:00
|
|
|
if (ntok >= 4)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
2020-05-29 04:06:22 +02:00
|
|
|
if (ntok == 5) {
|
|
|
|
|
switch(port_shape[0])
|
|
|
|
|
{
|
|
|
|
|
case 'a':
|
|
|
|
|
lab->lab_flags |= PORT_SHAPE_ABUT;
|
|
|
|
|
break;
|
|
|
|
|
case 'r':
|
|
|
|
|
lab->lab_flags |= PORT_SHAPE_RING;
|
|
|
|
|
break;
|
|
|
|
|
case 'f':
|
|
|
|
|
lab->lab_flags |= PORT_SHAPE_THRU;
|
|
|
|
|
break;
|
|
|
|
|
case 'd':
|
|
|
|
|
lab->lab_flags |= PORT_SHAPE_DEFAULT;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
TxError("Ignoring unknown \"port\" shape: %s", port_shape);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
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)
|
2021-12-13 04:09:31 +01:00
|
|
|
DBPutLabel(cellDef, &r, orient, text, type, flags, 0);
|
2017-04-25 14:41:48 +02:00
|
|
|
else
|
|
|
|
|
DBPutFontLabel(cellDef, &r, font, size, rotate, &offset,
|
2021-12-13 04:09:31 +01:00
|
|
|
orient, text, type, flags, 0);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
nextlabel:
|
|
|
|
|
if (dbFgets(line, len, f) == NULL)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (TRUE);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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 */
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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];
|
2019-10-16 02:40:59 +02:00
|
|
|
char *propvalue;
|
|
|
|
|
bool propfound;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
#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.
|
|
|
|
|
*/
|
|
|
|
|
|
2018-05-31 04:51:13 +02:00
|
|
|
if (cellDef->cd_file)
|
|
|
|
|
arg.wa_name = cellDef->cd_file;
|
|
|
|
|
else
|
|
|
|
|
arg.wa_name = cellDef->cd_name;
|
2017-04-25 14:41:48 +02:00
|
|
|
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");
|
2021-12-13 04:09:31 +01:00
|
|
|
sprintf(lstring, "port %d %s", lab->lab_port, ppos);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2020-05-29 04:06:22 +02:00
|
|
|
if (lab->lab_flags & (PORT_USE_MASK | PORT_CLASS_MASK | PORT_SHAPE_MASK))
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
2020-05-29 04:06:22 +02:00
|
|
|
|
|
|
|
|
switch (lab->lab_flags & PORT_SHAPE_MASK)
|
|
|
|
|
{
|
|
|
|
|
case PORT_SHAPE_ABUT:
|
|
|
|
|
strcat(lstring, " abutment");
|
|
|
|
|
break;
|
|
|
|
|
case PORT_SHAPE_RING:
|
|
|
|
|
strcat(lstring, " ring");
|
|
|
|
|
break;
|
|
|
|
|
case PORT_SHAPE_THRU:
|
|
|
|
|
strcat(lstring, " feedthrough");
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
strcat(lstring, "\n");
|
2020-05-23 23:13:14 +02:00
|
|
|
FPRINTF(f, lstring);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Now any persistent elements */
|
2020-05-12 18:03:38 +02:00
|
|
|
estring = DBWPrintElements(cellDef, DBW_ELEMENT_PERSISTENT, reducer);
|
2017-04-25 14:41:48 +02:00
|
|
|
if (estring != NULL)
|
|
|
|
|
{
|
|
|
|
|
FPRINTF(f, "<< elements >>\n");
|
|
|
|
|
FPRINTF(f, estring);
|
|
|
|
|
freeMagic(estring);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* And any properties */
|
2019-10-16 02:40:59 +02:00
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
if (cellDef->cd_props != (ClientData)NULL)
|
|
|
|
|
{
|
|
|
|
|
FPRINTF(f, "<< properties >>\n");
|
|
|
|
|
DBPropEnum(cellDef, dbWritePropFunc, (ClientData)f);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-16 02:40:59 +02:00
|
|
|
if (propfound) DBPropPut(cellDef, "FIXED_BBOX", propvalue);
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
2018-01-09 03:45:19 +01:00
|
|
|
char *lstring;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2018-01-09 03:45:19 +01:00
|
|
|
lstring = (char *)mallocMagic(10 + strlen((char *)value) + strlen(key));
|
2017-04-25 14:41:48 +02:00
|
|
|
sprintf(lstring, "string %s %s\n", key, (char *)value);
|
|
|
|
|
FPRINTR(f, lstring);
|
2018-01-09 03:45:19 +01:00
|
|
|
freeMagic(lstring);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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.
|
2020-05-23 23:13:14 +02:00
|
|
|
*
|
2017-04-25 14:41:48 +02:00
|
|
|
* 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;
|
2020-03-23 15:19:34 +01:00
|
|
|
char *cp1, *cp2, *dotptr;
|
2017-04-25 14:41:48 +02:00
|
|
|
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.
|
|
|
|
|
*/
|
2020-03-21 20:34:41 +01:00
|
|
|
if (!fileName)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
2020-03-21 20:34:41 +01:00
|
|
|
if (cellDef->cd_file)
|
|
|
|
|
fileName = cellDef->cd_file;
|
|
|
|
|
else if (cellDef->cd_name)
|
|
|
|
|
fileName = cellDef->cd_name;
|
|
|
|
|
else
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2020-03-21 20:34:41 +01:00
|
|
|
/* 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. */
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2020-05-29 03:10:42 +02:00
|
|
|
if (fileName != cellDef->cd_file)
|
|
|
|
|
StrDup(&cellDef->cd_file, fileName);
|
2020-03-21 20:34:41 +01:00
|
|
|
|
2020-03-23 15:19:34 +01:00
|
|
|
/* The cd_file should not have the .mag suffix, but make sure */
|
|
|
|
|
/* it doesn't before adding one. */
|
|
|
|
|
|
2020-11-03 18:17:16 +01:00
|
|
|
if ((strlen(fileName) < 4) || (strcmp(fileName + strlen(fileName) - 4, DBSuffix)))
|
2020-03-23 15:19:34 +01:00
|
|
|
{
|
|
|
|
|
realname = (char *) mallocMagic(strlen(fileName) + strlen(DBSuffix) + 1);
|
|
|
|
|
(void) sprintf(realname, "%s%s", fileName, DBSuffix);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
realname = StrDup((char **)NULL, fileName);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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
|
2020-01-02 16:13:04 +01:00
|
|
|
{
|
|
|
|
|
bool dereference = (cellDef->cd_flags & CDDEREFERENCE) ? TRUE : FALSE;
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Re-aquire the lock on the new file by opening it. */
|
2021-01-14 21:21:39 +01:00
|
|
|
if (DBCellRead(cellDef, NULL, TRUE, dereference, NULL) == FALSE)
|
|
|
|
|
return FALSE;
|
2020-01-02 16:13:04 +01:00
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
#endif
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-23 15:19:34 +01:00
|
|
|
/* Copy expandname back to cellDef->cd_file, if the name was changed. */
|
|
|
|
|
/* The file extension does not get copied into cd_file. */
|
|
|
|
|
|
|
|
|
|
dotptr = strrchr(expandname, '.');
|
|
|
|
|
if (dotptr) *dotptr = '\0';
|
|
|
|
|
if (strcmp(expandname, cellDef->cd_file))
|
|
|
|
|
StrDup(&cellDef->cd_file, expandname);
|
|
|
|
|
if (dotptr) *dotptr = '.';
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* Everything worked so far. */
|
|
|
|
|
|
|
|
|
|
result = TRUE;
|
|
|
|
|
{
|
|
|
|
|
struct stat thestat;
|
2020-03-23 15:19:34 +01:00
|
|
|
realf = fopen(expandname, "r");
|
2017-04-25 14:41:48 +02:00
|
|
|
if (realf == NULL)
|
|
|
|
|
{
|
|
|
|
|
cellDef->cd_flags |= CDMODIFIED;
|
2020-07-26 16:58:36 +02:00
|
|
|
TxError("Warning: Cannot open file \"%s\" for writing!\n", expandname);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
fstat(fileno(realf),&thestat);
|
|
|
|
|
if (thestat.st_size != DBFileOffset)
|
|
|
|
|
{
|
|
|
|
|
cellDef->cd_flags |= CDMODIFIED;
|
2020-07-26 16:58:36 +02:00
|
|
|
TxError("Warning: I/O error in writing file \"%s\"\n", expandname);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
fclose(realf);
|
|
|
|
|
}
|
|
|
|
|
realf = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanup:
|
|
|
|
|
SigEnableInterrupts();
|
|
|
|
|
freeMagic(realname);
|
|
|
|
|
freeMagic(tmpname);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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;
|
|
|
|
|
|
2020-05-23 23:13:14 +02:00
|
|
|
if (IsSplit(tile))
|
|
|
|
|
{
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-22 20:39:34 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* DBPathSubstitute --
|
|
|
|
|
*
|
|
|
|
|
* Replace the leading part of a file path string according to the following
|
|
|
|
|
* criteria:
|
|
|
|
|
*
|
|
|
|
|
* 1) If the filename starts with a string equal to the contents of
|
|
|
|
|
* Tcl variables PDK_PATH, PDKPATH, PDK_ROOT, or PDKROOT, then
|
|
|
|
|
* replace the string with the variable name. The "PATH" names are
|
|
|
|
|
* more specific than "ROOT" and so are checked first.
|
|
|
|
|
* 2) If the filename starts with a string equal to the contents of
|
|
|
|
|
* environment variable HOME, then replace the string with "~".
|
|
|
|
|
*
|
|
|
|
|
* Results:
|
|
|
|
|
* None.
|
|
|
|
|
*
|
|
|
|
|
* Side Effects:
|
|
|
|
|
* Writes into the string "cstring".
|
|
|
|
|
*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
DBPathSubstitute(pathstart, cstring, cellDef)
|
|
|
|
|
char *pathstart;
|
|
|
|
|
char *cstring;
|
|
|
|
|
CellDef *cellDef;
|
|
|
|
|
{
|
|
|
|
|
bool subbed = FALSE;
|
|
|
|
|
#ifdef MAGIC_WRAPPER
|
|
|
|
|
char *tvar;
|
|
|
|
|
|
|
|
|
|
/* Check for the leading component of the file path being equal to */
|
|
|
|
|
/* one of several common variable names for the PDK location, and */
|
|
|
|
|
/* if there is a match, then substitute the variable name for the */
|
|
|
|
|
/* matching leading path component. */
|
|
|
|
|
|
|
|
|
|
if (subbed == FALSE)
|
|
|
|
|
{
|
|
|
|
|
tvar = (char *)Tcl_GetVar(magicinterp, "PDK_PATH", TCL_GLOBAL_ONLY);
|
|
|
|
|
if (tvar)
|
|
|
|
|
if (!strncmp(pathstart, tvar, strlen(tvar)))
|
|
|
|
|
{
|
|
|
|
|
sprintf(cstring, "$PDK_PATH%s", pathstart + strlen(tvar));
|
|
|
|
|
subbed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (subbed == FALSE)
|
|
|
|
|
{
|
|
|
|
|
tvar = (char *)Tcl_GetVar(magicinterp, "PDKPATH", TCL_GLOBAL_ONLY);
|
|
|
|
|
if (tvar)
|
|
|
|
|
if (!strncmp(pathstart, tvar, strlen(tvar)))
|
|
|
|
|
{
|
|
|
|
|
sprintf(cstring, "$PDKPATH%s", pathstart + strlen(tvar));
|
|
|
|
|
subbed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (subbed == FALSE)
|
|
|
|
|
{
|
|
|
|
|
tvar = (char *)Tcl_GetVar(magicinterp, "PDK_ROOT", TCL_GLOBAL_ONLY);
|
|
|
|
|
if (tvar)
|
|
|
|
|
if (!strncmp(pathstart, tvar, strlen(tvar)))
|
|
|
|
|
{
|
|
|
|
|
sprintf(cstring, "$PDK_ROOT%s", pathstart + strlen(tvar));
|
|
|
|
|
subbed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (subbed == FALSE)
|
|
|
|
|
{
|
|
|
|
|
tvar = (char *)Tcl_GetVar(magicinterp, "PDKROOT", TCL_GLOBAL_ONLY);
|
|
|
|
|
if (tvar)
|
|
|
|
|
if (!strncmp(pathstart, tvar, strlen(tvar)))
|
|
|
|
|
{
|
|
|
|
|
sprintf(cstring, "$PDKROOT%s", pathstart + strlen(tvar));
|
|
|
|
|
subbed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (subbed == FALSE)
|
|
|
|
|
{
|
|
|
|
|
/* 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");
|
|
|
|
|
|
2021-04-25 04:13:30 +02:00
|
|
|
if (cellDef->cd_file == NULL)
|
|
|
|
|
sprintf(cstring, "%s", pathstart);
|
|
|
|
|
else if (!strncmp(cellDef->cd_file, homedir, strlen(homedir))
|
2021-04-22 20:39:34 +02:00
|
|
|
&& (*(cellDef->cd_file + strlen(homedir)) == '/'))
|
|
|
|
|
sprintf(cstring, "~%s", cellDef->cd_file + strlen(homedir));
|
|
|
|
|
else
|
|
|
|
|
sprintf(cstring, "%s", pathstart);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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;
|
2021-04-22 20:39:34 +02:00
|
|
|
char cstring[1024], *pathend, *pathstart, *parent;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
t = &(cellUse->cu_transform);
|
|
|
|
|
b = &(cellUse->cu_def->cd_bbox);
|
2018-04-06 18:50:37 +02:00
|
|
|
pathstart = cellUse->cu_def->cd_file;
|
|
|
|
|
parent = arg->wa_name;
|
2017-04-25 14:41:48 +02:00
|
|
|
|
2018-04-06 18:50:37 +02:00
|
|
|
if (pathstart == NULL)
|
2017-04-25 14:41:48 +02:00
|
|
|
pathend = NULL;
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-04-06 21:09:43 +02:00
|
|
|
char *slashptr, *pathorigin;
|
|
|
|
|
|
2018-04-06 18:50:37 +02:00
|
|
|
/* Get child path relative to the parent path */
|
2018-04-06 21:09:43 +02:00
|
|
|
|
|
|
|
|
pathorigin = pathstart;
|
2018-04-06 18:50:37 +02:00
|
|
|
pathend = strrchr(pathstart, '/');
|
2018-04-06 21:09:43 +02:00
|
|
|
slashptr = strchr(pathstart, '/');
|
|
|
|
|
while (slashptr)
|
|
|
|
|
{
|
|
|
|
|
if (!strncmp(pathorigin, parent, (int)(slashptr - pathorigin + 1)))
|
|
|
|
|
{
|
|
|
|
|
pathstart = slashptr + 1;
|
|
|
|
|
slashptr = strchr(pathstart, '/');
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
break;
|
2020-09-04 15:57:16 +02:00
|
|
|
|
2018-04-06 21:09:43 +02:00
|
|
|
}
|
2020-09-04 17:10:44 +02:00
|
|
|
|
|
|
|
|
/* If there are no common components, then restore the leading '/' */
|
|
|
|
|
if ((*pathorigin == '/') && (pathstart == pathorigin + 1))
|
|
|
|
|
pathstart = pathorigin;
|
|
|
|
|
|
2018-04-06 21:09:43 +02:00
|
|
|
if (pathend != NULL)
|
|
|
|
|
{
|
|
|
|
|
*pathend = '\0';
|
|
|
|
|
if (pathstart >= pathend)
|
|
|
|
|
pathstart = NULL;
|
|
|
|
|
}
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
2018-04-06 18:50:37 +02:00
|
|
|
if ((cellUse->cu_def->cd_flags & CDVISITED) || (pathend == NULL) ||
|
|
|
|
|
(pathstart == NULL) || (*pathstart == '\0'))
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
sprintf(cstring, "use %s %c%s\n", cellUse->cu_def->cd_name,
|
|
|
|
|
(cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ',
|
|
|
|
|
cellUse->cu_id);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-04-22 20:39:34 +02:00
|
|
|
sprintf(cstring, "use %s %c%s ", cellUse->cu_def->cd_name,
|
|
|
|
|
(cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ',
|
|
|
|
|
cellUse->cu_id);
|
|
|
|
|
DBPathSubstitute(pathstart, cstring + strlen(cstring), cellUse->cu_def);
|
|
|
|
|
strcat(cstring, "\n");
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
2021-04-22 20:39:34 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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);
|
2020-05-28 23:09:03 +02:00
|
|
|
StrDup(&DBbackupFile, template);
|
2017-04-25 14:41:48 +02:00
|
|
|
freeMagic(template);
|
|
|
|
|
TxPrintf("Created database crash recovery file %s\n", DBbackupFile);
|
|
|
|
|
}
|
|
|
|
|
filename = DBbackupFile;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (strlen(filename) == 0)
|
|
|
|
|
{
|
2020-05-28 23:09:03 +02:00
|
|
|
StrDup(&DBbackupFile, (char *)NULL);
|
2017-04-25 14:41:48 +02:00
|
|
|
return TRUE;
|
|
|
|
|
}
|
2020-05-28 23:09:03 +02:00
|
|
|
StrDup(&DBbackupFile, filename);
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
2020-05-23 23:13:14 +02:00
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* 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;
|
|
|
|
|
}
|