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;
|
|
|
|
|
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
#ifdef FILE_LOCKS
|
|
|
|
|
extern bool FileLocking;
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* 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 */
|
2022-01-03 22:00:31 +01:00
|
|
|
if (cellDef->cd_fd == -2) fclose(f);
|
2017-04-25 14:41:48 +02:00
|
|
|
#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
|
2022-01-03 22:00:31 +01:00
|
|
|
if (cellDef->cd_fd >= 0)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
close(cellDef->cd_fd);
|
2022-01-03 22:00:31 +01:00
|
|
|
cellDef->cd_fd = -1; /* Set to initial state */
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
#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;
|
|
|
|
|
|
2022-01-03 22:00:31 +01:00
|
|
|
if (is_locked == TRUE)
|
|
|
|
|
cellDef->cd_fd = -2; /* Indicates locked file */
|
|
|
|
|
else
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
|
|
|
2022-01-12 21:36:33 +01:00
|
|
|
#define FPUTSF(f,s)\
|
2017-04-25 14:41:48 +02:00
|
|
|
{\
|
2022-01-12 17:23:08 +01:00
|
|
|
if (fputs(s,f) == EOF) goto ioerror;\
|
2017-04-25 14:41:48 +02:00
|
|
|
DBFileOffset += strlen(s);\
|
|
|
|
|
}
|
2022-01-12 21:36:33 +01:00
|
|
|
#define FPUTSR(f,s)\
|
2017-04-25 14:41:48 +02:00
|
|
|
{\
|
2022-01-12 17:23:08 +01:00
|
|
|
if (fputs(s,f) == EOF) return 1;\
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSF(f, headerstring);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSF(f, "<< labels >>\n");
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
|
|
|
|
}
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSF(f, lstring);
|
2017-04-25 14:41:48 +02:00
|
|
|
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");
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSF(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)
|
|
|
|
|
{
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSF(f, "<< elements >>\n");
|
|
|
|
|
FPUTSF(f, estring);
|
2017-04-25 14:41:48 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSF(f, "<< properties >>\n");
|
2017-04-25 14:41:48 +02:00
|
|
|
DBPropEnum(cellDef, dbWritePropFunc, (ClientData)f);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-16 02:40:59 +02:00
|
|
|
if (propfound) DBPropPut(cellDef, "FIXED_BBOX", propvalue);
|
|
|
|
|
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSF(f, "<< end >>\n");
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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);
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(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.
|
|
|
|
|
*/
|
|
|
|
|
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
if (cellDef->cd_flags & CDNOEDIT)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
TxPrintf("File %s is read_only and cannot be written\n", realname);
|
|
|
|
|
freeMagic(realname);
|
|
|
|
|
return(FALSE);
|
|
|
|
|
}
|
|
|
|
|
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
#ifdef FILE_LOCKS
|
2022-01-03 22:00:31 +01:00
|
|
|
if (cellDef->cd_fd == -2)
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
{
|
|
|
|
|
TxPrintf("File %s is locked by another user and "
|
|
|
|
|
"cannot be written\n", realname);
|
|
|
|
|
freeMagic(realname);
|
|
|
|
|
return(FALSE);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-04-25 14:41:48 +02:00
|
|
|
/* 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
|
2022-01-03 22:00:31 +01:00
|
|
|
if (cellDef->cd_fd >= 0)
|
2017-04-25 14:41:48 +02:00
|
|
|
{
|
|
|
|
|
close(cellDef->cd_fd);
|
2022-01-03 22:00:31 +01:00
|
|
|
cellDef->cd_fd = -1; /* Set to initial state */
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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;
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
bool is_locked;
|
|
|
|
|
#ifdef FILE_LOCKS
|
|
|
|
|
if (FileLocking)
|
|
|
|
|
realf = flock_open(expandname, "r", &is_locked);
|
|
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
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
|
|
|
|
|
{
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
int fd = fileno(realf);
|
|
|
|
|
fstat(fd, &thestat);
|
2017-04-25 14:41:48 +02:00
|
|
|
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
|
|
|
}
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
|
|
|
|
|
#ifdef FILE_LOCKS
|
2022-01-03 22:00:31 +01:00
|
|
|
cellDef->cd_fd = -1;
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
if (FileLocking && (is_locked == FALSE))
|
|
|
|
|
cellDef->cd_fd = fd;
|
2022-01-03 22:00:31 +01:00
|
|
|
else if (FileLocking && (is_locked == TRUE))
|
|
|
|
|
cellDef->cd_fd = -2;
|
Played around with the file locking and discovered to my chagrin that
whenever a process writes a cell to disk, it immediately releases the
file lock it had on that cell, which is clearly not the intent of file
locking. Fixed this issue. On a related topic, revised the "cellname
writeable" command so that it can make a cell editable even if the cell
has an advisory lock and cannot be made writeable. Perhaps there should
be a clearer distinction here between "writeable" and "editable". Also:
Reconsidered the previous commit, which removed the "--disable-locking"
from the configuration options. Because some operating systems may not
implement fnctl()-based file locking (Cygwin, for one, apparently doesn't),
it is still useful to be able to completely remove the function, in case
the operating system will fail to recognize the fnctl() values in the
code. Now, file locking behavior can be permanently removed through the
configuration option, or temporarily disabled from the command line.
2022-01-01 22:53:46 +01:00
|
|
|
else
|
|
|
|
|
#endif
|
|
|
|
|
fclose(realf);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
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));
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(arg->wa_file,pstring);
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(arg->wa_file,pstring);
|
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
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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
|
|
|
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(arg->wa_file, cstring);
|
2017-04-25 14:41:48 +02:00
|
|
|
|
|
|
|
|
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);
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(arg->wa_file,cstring);
|
2017-04-25 14:41:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sprintf(cstring, "timestamp %d\n", cellUse->cu_def->cd_timestamp);
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(arg->wa_file,cstring)
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(arg->wa_file,cstring)
|
2017-04-25 14:41:48 +02:00
|
|
|
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);
|
2022-01-12 21:36:33 +01:00
|
|
|
FPUTSR(arg->wa_file,cstring)
|
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
|
|
|
/*
|
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
|
*
|
|
|
|
|
* 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;
|
|
|
|
|
}
|