/* * DBio.c -- * * Reading and writing of cells * * ********************************************************************* * * Copyright (C) 1985, 1990 Regents of the University of California. * * * Permission to use, copy, modify, and distribute this * * * software and its documentation for any purpose and without * * * fee is hereby granted, provided that the above copyright * * * notice appear in all copies. The University of California * * * makes no representations about the suitability of this * * * software for any purpose. It is provided "as is" without * * * express or implied warranty. Export of this software outside * * * of the United States of America may require an export license. * * ********************************************************************* */ #ifndef lint static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/database/DBio.c,v 1.5 2010/06/24 12:37:15 tim Exp $"; #endif /* not lint */ #include #include #include #include #include #include #include #include #ifdef HAVE_DIRENT_H #include #include #define direct dirent #else #include #endif #include #include #include #ifdef HAVE_PATHS_H #include #endif #include "utils/magic.h" #include "utils/geometry.h" #include "tiles/tile.h" #include "utils/utils.h" #include "utils/hash.h" #include "database/database.h" #include "database/databaseInt.h" #include "database/fonts.h" #include "windows/windows.h" #include "dbwind/dbwind.h" #include "utils/tech.h" #include "textio/textio.h" #include "drc/drc.h" #include "utils/undo.h" #include "utils/malloc.h" #include "utils/signals.h" #ifndef _PATH_TMP #define _PATH_TMP "/tmp" #endif extern char *Path; /* Suffix for all Magic files */ char *DBSuffix = ".mag"; /* Magic units per lambda (2 integers, representing (n / d) */ int DBLambda[2] = {1, 1}; /* If set to FALSE, don't print warning messages. */ bool DBVerbose = TRUE; /* Global name of backup file for this session */ static char *DBbackupFile = (char *)NULL; /* Forward declarations */ char *dbFgets(); FILE *dbReadOpen(); int DBFileOffset; bool dbReadLabels(); bool dbReadElements(); bool dbReadProperties(); bool dbReadUse(); /* * ---------------------------------------------------------------------------- * * file_is_not_writeable -- * * Check to see if file is not writeable. (wen-king@cs.caltech.edu) * Modified to deal with changed semantics of access() (rajit@cs.caltech.edu) * * --------------------------------------------------------------------------- */ static int file_is_not_writeable(name) char *name; { struct stat sbuf; /* attempt to read stat buffer for file */ if (lstat(name,&sbuf) < 0) return(-1); /* regular file? if not, error */ if (!S_ISREG(sbuf.st_mode)) { errno = EACCES; return(-1); } /* can we write to it? */ if (access(name, W_OK) < 0) return(-1); /* the OS thinks we can write to the file; but does the file think so? */ if (geteuid() == sbuf.st_uid) { if (sbuf.st_mode & S_IWUSR) return (0); /* I own the file, but I don't have write permission */ errno = EACCES; return (-1); } if (!(sbuf.st_mode & (S_IWOTH|S_IWGRP))) { errno = EACCES; return (-1); } return(0); } /* * ---------------------------------------------------------------------------- * * dbCellReadDef -- * * * Read in the paint for a cell from its associated disk file. * If a filename for the cell is specified, we try to open it * somewhere in the search path. Otherwise, we try the filename * already associated with the cell, or the name of the cell itself * as the name of the file containing the definition of the cell. * * Mark the cell definition as "read in" (CDAVAILABLE), and * recompute the bounding box. * * WARNING: * * It is the responsibility of the caller to call DBReComputeBbox(cellDef) * at a convenient place, as we do not set the bounding box of the cell def. * If we were to update the bounding box here, this CellDef would first have * to be ripped out of each parent subcell tile plane in which it appears, and * then relinked in after its bounding box had changed. Since DBCellRead() may * be called from in the middle of a database search, however, the database * modification resulting from this could ruin the search context and crash * the system. * * Results: * TRUE if the cell could be read successfully, FALSE * otherwise. * * Side effects: Clears the cell's MODIFIED bit. * Updates the tile planes for the cell definition. * In the event of an error while reading in the cell, * the external integer errno is set to the UNIX error * encountered. * * If newTechOk is TRUE and the cell's technology is different * from the current one, the current technology is changed. * * Errors: * If incomplete specs are given either for a rectangle or for * a cell use, then we immediately stop reading the file. * * File Format: * * 1. The first line of the file contains the string "magic". * * 2. Next comes an optional technology line, with the format * "tech ". is the technology of the cell. * * 3. Next comes an optional scale line, with the format * "magscale ". and give the number of magic * internal units per lambda as the ratio /. 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 ", where * 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 ". * Nonmanhattan geometry is covered by the entry * "tri " with indicating * the direction of the corner made by the right triangle. * If the split tile contains more than one type, separate entries * are output for each. * * 6. Zero or more groups of lines describing cell uses. Each group * is of the form * use [] * array * timestamp * transform * box * Each group may be preceded by one or more separator lines. Note * that is optional and is omitted if there is to be no * instance id for the cell use. If it is omitted, an instance * identifier is generated internally. The "array" line may be * omitted if the cell use is not an array. The "timestamp" line is * optional; if present, it gives the last time the parent * was aware that the child changed. The is a full path * to the location of the cell . 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 . 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 , 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 exists at , then the * fall-back method is to use the search paths, which allows some * portability of layouts from place to place without breaking. * * 7. If the cell contains labels, then the labels are preceded * by the line "<< labels >>". Each label is one line of the form * rlabel [s] * or * flabel [s] * * or * label * (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 * where is a string with one to four characters "nsew", * denoting the position of allowed connections to the port. The * index must be unique to each port in a cell. * * 9. Elements are saved in a section "<< elements >>". * * 10. The file is terminated by the line "<< end >>". * * Note: be careful about any changes to this format: there are * several previous file formats still in use in old files, and * the current one is backwardly compatible with all of them. * * * ---------------------------------------------------------------------------- */ bool dbCellReadDef(f, cellDef, name, ignoreTech, dereference) FILE *f; /* The file, already opened by the caller */ CellDef *cellDef; /* Pointer to definition of cell to be read in */ char *name; /* Name of file from which to read definition. * If NULL, then use cellDef->cd_file; if that * is NULL try the name of the cell. */ bool ignoreTech; /* If FALSE then the technology of the file MUST * match the current technology, or else the * subroutine will return an error condition * without reading anything. If TRUE, a * warning will be printed if the technology * names do not match, but an attempt will be * made to read the file anyway. */ bool dereference; /* If TRUE, ignore path references in the input */ { int cellStamp = 0, rectCount = 0, rectReport = 10000; char line[2048], tech[50], layername[50]; PaintResultType *ptable; bool result = TRUE, scaleLimit = FALSE; Rect *rp; int c; TileType type, rtype, loctype; TileTypeBitMask *rmask, typemask; Plane *plane; Rect r; int n = 1, d = 1; /* * It's very important to disable interrupts during the body of * this routine. Otherwise, if the user types the interrupt key * only part of the file will be read in, and if he then writes * the cell out, the disk copy will get trashed. */ SigDisableInterrupts(); /* * Process the header of the Magic file: * make sure that this is a Magic-format file and that it * has the right technology. Magic files have a first line * of "magic". */ if (dbFgets(line, sizeof line, f) == NULL) goto badfile; if (strncmp(line, "magic", 5) != 0) { TxError("First line in file must be \"magic\"; instead saw: %s", line); goto badfile; } if (dbFgets(line, sizeof line, f) == NULL) goto badfile; if ((line[0] != '<') && (line[0] != '\0')) { if (sscanf(line, "tech %49s", tech) != 1) { TxError("Malformed \"tech\" line: %s", line); goto badfile; } if (strcmp(DBTechName, tech) != 0) { TxError("Cell %s has technology \"%s\", but current " "technology is \"%s\"\n", cellDef->cd_name, tech, DBTechName); if (ignoreTech) TxPrintf("Will attempt to read cell anyway.\n"); else { TxError("Use command \"tech load\" if you want to switch" " technologies, or use\n"); TxError("\"cellname delete %s\" and \"load %s -force\" to" " force the cell to load as technology %s\n", cellDef->cd_name, cellDef->cd_name, DBTechName); SigEnableInterrupts(); return (FALSE); } } if (dbFgets(line, sizeof line, f) == NULL) goto badfile; if (line[0] == 'm') { if (!strncmp(line, "magscale", 8)) { if (sscanf(line, "magscale %d %d", &n, &d) != 2) { TxError("Expected two arguments to magscale; ignoring\n"); n = d = 1; } } /* For backward compatibility, accept (and throw away) lines * whose first word is "maxlabscale". */ else if (!strncmp(line, "maxlabscale", 11)) TxError("Deprecated keyword \"maxlabscale\" in input file.\n"); else TxError("Expected magscale but got: %s", line); if (dbFgets(line, sizeof line, f) == NULL) goto badfile; } if (line[0] == 't') { if (sscanf(line, "timestamp %d", &cellStamp) != 1) TxError("Expected timestamp but got: %s", line); if (dbFgets(line, sizeof line, f) == NULL) goto badfile; } } /* * Determine scalefactor between file and database. Adjust scale of the * file and/or the database accordingly. */ n *= DBLambda[1]; d *= DBLambda[0]; ReduceFraction(&n, &d); scaleLimit = CIFTechLimitScale(n, d); if (!scaleLimit && (d > 1)) { CIFTechInputScale(1, d, TRUE); CIFTechOutputScale(1, d); DRCTechScale(1, d); ExtTechScale(1, d); WireTechScale(1, d); #ifdef LEF_MODULE LefTechScale(1, d); #endif #ifdef ROUTE_MODULE RtrTechScale(1, d); MZAfterTech(); IRAfterTech(); #endif DBScaleEverything(d, 1); DBLambda[1] *= d; TxPrintf("Input cell %s scales magic internal geometry by factor of %d\n", cellDef->cd_name, d); d = 1; } if (n > 1) { TxPrintf("Scaled magic input cell %s geometry by factor of %d", cellDef->cd_name, n); if (d > 1) { TxPrintf("/ %d\n", d); TxError("Warning: Geometry may be lost because internal grid" " cannot be reduced.\n"); } else TxPrintf("\n"); } /* * Next, get the paint, subcells, and labels for this cell. * While we are generating paints to the database, we want * to disable the undo package. */ rp = &r; UndoDisable(); while (TRUE) { /* * Read the header line to get the layer name, then read as * many rectangles as are specified on consecutive lines. * If not a layer header line, then it should be a cell * use header line. */ if (sscanf(line, "<< %s >>", layername) != 1) { if (!dbReadUse(cellDef, line, sizeof line, f, n, d, dereference)) goto badfile; continue; } TTMaskZero(&typemask); rmask = &typemask; type = DBTechNameType(layername); if (type < 0) { /* * Look for special layer names: * labels -- begins a list of labels and ports * elements -- begins a list of elements * properties -- begins a list of properties * end -- marks the end of this file */ if (!strcmp(layername, "labels")) { if (!dbReadLabels(cellDef, line, sizeof line, f, n, d)) goto badfile; continue; } else if (!strcmp(layername, "elements")) { if (!dbReadElements(cellDef, line, sizeof line, f, n, d)) goto badfile; continue; } else if (!strcmp(layername, "properties")) { if (!dbReadProperties(cellDef, line, sizeof line, f, n, d)) goto badfile; continue; } else if (!strcmp(layername, "end")) goto done; else DBTechNoisyNameMask(layername, rmask); // TxError("Unknown layer %s ignored in %s\n", layername, // cellDef->cd_name); } /* * Record presence of material in cell. */ if (DBPlane(type) > 0) { if (type < DBNumUserLayers) { TTMaskSetType(&cellDef->cd_types, type); TTMaskSetType(rmask, type); } else { /* Separate stacked contact types into their components */ rmask = DBResidueMask(type); for (rtype = TT_SPACE + 1; rtype < DBNumUserLayers; rtype++) if (TTMaskHasType(rmask, rtype)) TTMaskSetType(&cellDef->cd_types, type); } } /* * The following loop is executed once for each line * in the file beginning with 'r'. */ nextrect: while (((c = getc(f)) == 'r') || (c == 't')) { TileType dinfo; int dir; /* * GetRect actually reads the rest of the line up to * a trailing newline or EOF. */ if (c == 't') { if ((dir = GetRect(f, 3, rp, n, d)) == 0) goto badfile; dir >>= 1; dinfo = TT_DIAGONAL | ((dir & 0x2) ? TT_SIDE : 0) | ((((dir & 0x2) >> 1) ^ (dir & 0x1)) ? TT_DIRECTION : 0); } else { dinfo = 0; if (!GetRect(f, 4, rp, n, d)) goto badfile; } if ((++rectCount % rectReport == 0) && DBVerbose) { TxPrintf("%s: %d rects\n", cellDef->cd_name, rectCount); fflush(stdout); } /* * Only add a new rectangle if it is non-null, and if the * layer is reasonable. */ if (!GEO_RECTNULL(rp)) { /*------------------------------------------------------*/ /* The complicated use of DBPaintPlane() has been */ /* replaced with a simpler call to DBPaint(). HOWEVER */ /* there are instances where this can cause unexpected */ /* behavior. Namely, magic-7.2 allows, e.g., m456c */ /* painted over m3c; this apparently works although */ /* image on the metal4 plane cannot represent both */ /* contact types. magic-7.3 disallows this, so for the */ /* same technology file (no stackable types), m3c gets */ /* eliminated by the paint rules! This condition is */ /* left AS-IS. Caveat end-user. */ /* Tim 7/8/04 */ /*------------------------------------------------------*/ for (rtype = TT_SPACE + 1; rtype < DBNumUserLayers; rtype++) { if (TTMaskHasType(rmask, rtype)) { loctype = rtype; if (dinfo & TT_SIDE) loctype <<= 14; loctype |= dinfo; DBPaint(cellDef, rp, loctype); } } } } /* * Ignore comments. * Note we use fgets() since we only want to discard this line. */ if (c == '#') { (void) fgets(line, sizeof line, f); goto nextrect; } /* * We reach here if the first character on a line is not * 'r', meaning that we have reached the end of this * section of rectangles. */ if (c == EOF) goto badfile; line[0] = c; if (dbFgets(&line[1], sizeof line - 1, f) == NULL) goto badfile; } done: cellDef->cd_flags &= ~(CDMODIFIED|CDBOXESCHANGED|CDGETNEWSTAMP); /* * Assign instance-ids to cell uses that didn't contain * explicit use identifiers. Warn about duplicate instance * ids as well, changing these to unique ones. We do this * here instead of on-the-fly during cell read-in, to avoid * an N**2 algorithm that blows up for large #s of subcells. */ DBGenerateUniqueIds(cellDef, TRUE); /* * If the timestamp in the cell didn't match expectations, * notify the timestamp manager. Note: it's possible that * this cell is used only as the root of windows. If that * is the case, then don't trigger a timestamp mismatch (we * can tell this by whether or not there are any parent uses * with non-null parent defs. If the cell on disk had a zero * timestamp, then force the cell to be written out with a * correct timestamp. */ if ((cellDef->cd_timestamp != cellStamp) || (cellStamp == 0)) { CellUse *cu; for (cu = cellDef->cd_parents; cu != NULL; cu = cu->cu_nextuse) { if (cu->cu_parent != NULL) { DBStampMismatch(cellDef, &cellDef->cd_bbox); break; } } } /* Update timestamp flags */ DBFlagMismatches(cellDef); cellDef->cd_timestamp = cellStamp; if (cellStamp == 0) { TxError("\"%s\" has a zero timestamp; it should be written out\n", cellDef->cd_name); TxError(" to establish a correct timestamp.\n"); cellDef->cd_flags |= CDSTAMPSCHANGED|CDGETNEWSTAMP; } UndoEnable(); DRCCheckThis(cellDef, TT_CHECKPAINT, (Rect *) NULL); SigEnableInterrupts(); return (result); badfile: TxError("File %s contained format error\n", cellDef->cd_name); DRCCheckThis(cellDef, TT_CHECKPAINT, (Rect *) NULL); result = FALSE; goto done; } /* * ---------------------------------------------------------------------------- * * DBRemoveBackup() -- * * Remove any crash backup file. This routine is normally called either on * normal program exit, or after the successful read-in of a crash backup. * * Results: * None. * * Side effects: * If DBbackupFile is non-NULL, then memory is freed, and the backup * file is removed from the filesystem temp directory. Otherwise, * nothing happens. * * ---------------------------------------------------------------------------- */ void DBRemoveBackup() { if (DBbackupFile != (char *)NULL) { unlink(DBbackupFile); freeMagic(DBbackupFile); DBbackupFile = (char *)NULL; } } /* * ---------------------------------------------------------------------------- * * DBFileRecovery() -- * * Get the name of the first backup file found in the /tmp directory, * prompt for action, and if action is "read", then load it. * * Results: * None. * * Side Effects: * Prompts for action. * Loads the contents of the backup file into the database. * * ---------------------------------------------------------------------------- */ void DBFileRecovery(filename) char *filename; { DIR *cwd; struct direct *dp; struct stat sbuf; uid_t userid = getuid(); time_t recent = 0; char *snptr, *tempdir, tempname[256]; int pid; static char *actionNames[] = {"read", "cancel", 0 }; char *prompt; int action; if (DBbackupFile != NULL) { TxError("Error: Backup file in use for current session.\n"); return; } if (filename == NULL) { tempdir = getenv("TMPDIR"); if (tempdir == NULL) tempdir = _PATH_TMP; cwd = opendir(tempdir); if (cwd == NULL) return; /* Find the most recent crash file in the temp directory */ while ((dp = readdir(cwd)) != NULL) { char *doslash = (tempdir[strlen(tempdir) - 1] == '/') ? "" : "/"; sprintf(tempname, "%s%s%s", tempdir, doslash, dp->d_name); snptr = tempname + strlen(tempdir); if (!strncmp(snptr, "MAG", 3)) { char *dotptr = strchr(snptr, '.'); pid = -1; if (dotptr && dotptr > snptr + 3) { *dotptr = '\0'; if (sscanf(snptr + 3, "%d", &pid) != 1) pid = -1; *dotptr = '.'; } if ((!stat(tempname, &sbuf)) && (sbuf.st_uid == userid)) { if ((recent == 0) || (sbuf.st_ctime > recent)) { /* If the PID encoded in the name belongs to an */ /* active process, then we should not try to */ /* open it. */ if (pid != -1) if (SigCheckProcess(pid) == TRUE) continue; recent = sbuf.st_ctime; DBbackupFile = StrDup(&DBbackupFile, tempname); } } } } closedir(cwd); } else { DBbackupFile = StrDup(&DBbackupFile, filename); recent = 1; } if (recent > 0) { /* There exists at least one temporary file */ /* belonging to this user. Ask to recover */ /* the most recent one. */ prompt = TxPrintString("Recover from backup file %s?", DBbackupFile); action = TxDialog(prompt, actionNames, 0); switch(action) { case 0: /* Read */ if (DBReadBackup(DBbackupFile) == TRUE) DBRemoveBackup(); break; case 1: /* Cancel */ break; } } /* Make sure we've cleared out the backup filename */ if (DBbackupFile != NULL) { freeMagic(DBbackupFile); DBbackupFile = (char *)NULL; } } /* * ---------------------------------------------------------------------------- * * DBReadBackup -- * * This file reads a backup file containing multiple cell definitions. * * Results: * TRUE if the backup file was read successfully, FALSE otherwise. * * Side Effects: * Side effects are the side effects caused by dbCellReadDef() * (see above). As many cells as are listed in the backup file * are created and added to the database. * * ---------------------------------------------------------------------------- */ bool DBReadBackup(name) char *name; /* Name of the backup file */ { FILE *f; char *filename, *rootname, *chrptr; char line[256]; CellDef *cellDef; bool result = TRUE; if ((f = PaOpen(name, "r", NULL, "", NULL, NULL)) == NULL) { TxError("Cannot open backup file \"%s\"\n", name); return FALSE; } if (dbFgets(line, sizeof(line), f) == NULL) { TxError("Bad backup file %s; can't restore!\n", name); return FALSE; } while (strncmp(line, "end", 3) != 0) { if (strncmp(line, "file", 4) == 0) { filename = line + 4; /* Remove any trailing return character */ chrptr = strrchr(filename, '\n'); if (chrptr != NULL) *chrptr = '\0'; /* Remove any trailing file extension */ chrptr = strstr(filename, ".mag"); if (chrptr != NULL) *chrptr = '\0'; rootname = strrchr(filename, '/'); if (rootname == NULL) rootname = filename; else rootname++; /* Remove any leading whitespace */ while (isspace(*rootname) && *rootname != '\0') rootname++; if (strlen(rootname) == 0) return FALSE; cellDef = DBCellLookDef(rootname); if (cellDef == (CellDef *)NULL) cellDef = DBCellNewDef(rootname, (char *)NULL); cellDef->cd_flags &= ~CDNOTFOUND; cellDef->cd_flags |= CDAVAILABLE; if (dbCellReadDef(f, cellDef, filename, TRUE, FALSE) == FALSE) return FALSE; if (dbFgets(line, sizeof(line), f) == NULL) { TxError("Error in backup file %s; partial restore only!\n", name); return FALSE; } /* Update timestamp flags from dbCellReadDef() */ DBFlagMismatches(cellDef); } else { TxError("Error in backup file %s; expected keyword" " \"file\", got \"%s\"!\n", name, line); return FALSE; } } chrptr = strrchr(line, '\n'); if (chrptr > line + 4) { /* Remove the trailing return character */ *chrptr = '\0'; DBWreload(line + 4); } return TRUE; } /* * ---------------------------------------------------------------------------- * * DBCellRead -- * * This is the wrapper for DBCellReadDef. The routine has been divided into * parts so that a single backup file can be made and recovered, and in * preparation for allowing certain cell definitions to be in-lined into the * output file (such as polygonXXXXX cells generated by the gds read-in). * * Results: * TRUE if the cell could be read successfully, FALSE * otherwise. If the cell is already read in, TRUE is * also returned. * * Side Effects: * If the cell is already marked as available (CDAVAILABLE), this * routine does nothing (has no side effects). * * Otherwise, side effects are the side effects caused by * dbCellReadDef() (see above). In addition, the cell * definition is marked as available. * * ---------------------------------------------------------------------------- */ bool DBCellRead(cellDef, name, ignoreTech, dereference, errptr) CellDef *cellDef; /* Pointer to definition of cell to be read in */ char *name; /* Name of file from which to read definition. * If NULL, then use cellDef->cd_file; if that * is NULL try the name of the cell. */ bool ignoreTech; /* If FALSE then the technology of the file MUST * match the current technology, or else the * subroutine will return an error condition * without reading anything. If TRUE, a * warning will be printed if the technology * names do not match, but an attempt will be * made to read the file anyway. */ bool dereference; /* If TRUE then ignore path argument to uses */ int *errptr; /* Copy of errno set by file reading routine * is placed here, unless NULL. */ { FILE *f; bool result; if (errptr != NULL) *errptr = 0; if (cellDef->cd_flags & CDAVAILABLE) result = TRUE; else if ((f = dbReadOpen(cellDef, name, TRUE, errptr)) == NULL) result = FALSE; else { result = (dbCellReadDef(f, cellDef, name, ignoreTech, dereference)); #ifdef FILE_LOCKS /* Close files that were locked by another user */ if (cellDef->cd_fd == -1) fclose(f); #else /* When using fcntl() to enforce file locks, we can't */ /* close the file descriptor without losing the lock. */ fclose(f); #endif } return result; } /* * ---------------------------------------------------------------------------- * * dbReadOpen -- * * Open the file containing the cell we are going to read. * If a filename for the cell is specified ('name' is non-NULL), * we try to open it somewhere in the search path. Otherwise, * we try the filename already associated with the cell, or the * name of the cell itself as the name of the file containing * the definition of the cell. * * If 'setFileName' is TRUE, then cellDef->cd_file will be updated * to point to the name of the file from which the cell was loaded. * * Results: * Returns an open FILE * if successful, or NULL on error. * * Side effects: * Opens a FILE. Leaves cellDef->cd_flags marked as * CDAVAILABLE, with the CDNOTFOUND bit clear, if we * were successful. * * ---------------------------------------------------------------------------- */ FILE * dbReadOpen(cellDef, name, setFileName, errptr) CellDef *cellDef; /* Def being read */ char *name; /* Name if specified, or NULL */ bool setFileName; /* If TRUE then cellDef->cd_file should be updated * to point to the name of the file from which the * cell was loaded. */ int *errptr; /* Pointer to int to hold error value */ { FILE *f = NULL; char *filename, *realname; bool is_locked; #ifdef FILE_LOCKS if (cellDef->cd_fd != -1) { close(cellDef->cd_fd); cellDef->cd_fd = -1; } #endif if (errptr != NULL) *errptr = 0; // No error, by default if (name != (char *) NULL) { f = PaLockOpen(name, "r", DBSuffix, Path, CellLibPath, &filename, &is_locked); if (errptr != NULL) *errptr = errno; } else if (cellDef->cd_file != (char *) NULL) { /* Do not send a name with a file extension to PaLockOpen(), * otherwise that routine must handle it and then cannot * distinguish between, say, cell.mag and cell.mag.mag. */ char *pptr, *sptr; sptr = strrchr(cellDef->cd_file, '/'); if (sptr == NULL) sptr = cellDef->cd_file; else sptr++; pptr = strrchr(sptr, '.'); if (pptr != NULL) if (strcmp(pptr, DBSuffix)) pptr = NULL; else *pptr = '\0'; f = PaLockOpen(cellDef->cd_file, "r", DBSuffix, ".", (char *) NULL, &filename, &is_locked); /* Fall back on the original method of using search paths. */ if (f == NULL) { f = PaLockOpen(cellDef->cd_name, "r", DBSuffix, Path, CellLibPath, &filename, &is_locked); if (f != NULL) { if (pptr != NULL) *pptr = '.'; /* 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. */ TxError("Warning: Parent cell lists instance of \"%s\" at bad file " "path %s.\n", cellDef->cd_name, cellDef->cd_file); TxError("The cell exists in the search paths at %s.\n", filename); TxError("The discovered version will be used.\n"); /* Write the new path to cd_file or else magic will */ /* generate another error later. */ cellDef->cd_file = StrDup(&cellDef->cd_file, filename); } } if (errptr != NULL) *errptr = errno; if (pptr != NULL) *pptr = '.'; // Put it back where you found it! } else { f = PaLockOpen(cellDef->cd_name, "r", DBSuffix, Path, CellLibPath, &filename, &is_locked); if (errptr != NULL) *errptr = errno; } if (f == NULL) { /* Don't print another message if we've already tried to read it */ if (cellDef->cd_flags & CDNOTFOUND) return ((FILE *) NULL); if (name != (char *) NULL) TxError("File %s%s couldn't be read\n", name, DBSuffix); else if (cellDef->cd_file != (char *) NULL) TxError("File %s couldn't be read\n", cellDef->cd_file); else { TxError("Cell %s couldn't be read\n", cellDef->cd_name); realname = (char *) mallocMagic((unsigned) (strlen(cellDef->cd_name) + strlen(DBSuffix) + 1)); (void) sprintf(realname, "%s%s", cellDef->cd_name, DBSuffix); cellDef->cd_file = StrDup(&cellDef->cd_file, realname); } if (errptr) TxError("%s\n", strerror(*errptr)); cellDef->cd_flags |= CDNOTFOUND; return ((FILE *) NULL); } #ifdef FILE_LOCKS else { if (file_is_not_writeable(filename) || (is_locked == TRUE)) { cellDef->cd_flags |= CDNOEDIT; if ((is_locked == FALSE) && DBVerbose) TxPrintf("Warning: cell <%s> from file %s is not writeable\n", cellDef->cd_name, filename); } else cellDef->cd_flags &= ~CDNOEDIT; if (is_locked == FALSE) cellDef->cd_fd = fileno(f); cellDef->cd_flags &= ~CDNOTFOUND; } #else if (file_is_not_writeable(filename) && DBVerbose) TxPrintf("Warning: cell <%s> from file %s is not writeable\n", cellDef->cd_name, filename); TxFlushOut(); cellDef->cd_flags &= ~CDNOTFOUND; #endif if (setFileName) (void) StrDup(&cellDef->cd_file, filename); cellDef->cd_flags |= CDAVAILABLE; return (f); } /* * ---------------------------------------------------------------------------- * * DBTestOpen -- * * Check whether or not a database file can be found on disk. * * Results: * Returns TRUE if available, FALSE if not. * * Side effects: * Full pathname is returned in fullPath (if non-NULL) * * ---------------------------------------------------------------------------- */ bool DBTestOpen(name, fullPath) char *name; char **fullPath; { FILE *f; f = PaLockOpen(name, "r", DBSuffix, Path, CellLibPath, fullPath, (bool *)NULL); if (f != NULL) { fclose(f); return TRUE; } return FALSE; } /* * ---------------------------------------------------------------------------- * * dbReadUse -- * * Read a single cell use specification. Create a new cell * use that is a child of cellDef. Create the def for this * child use if it doesn't already exist. * * On input, 'line' contains the "use" line; on exit, 'line' * contains the next line in the input after the "use". * * Results: * Returns TRUE normally, or FALSE on error or EOF. * * Side effects: * See above. * * ---------------------------------------------------------------------------- */ bool dbReadUse(cellDef, line, len, f, scalen, scaled, dereference) CellDef *cellDef; /* Cell whose cells are being read */ char *line; /* Line containing "use ..." */ int len; /* Size of buffer pointed to by line */ FILE *f; /* Input file */ int scalen; /* Multiply values in file by this */ int scaled; /* Divide values in file by this */ bool dereference; /* If TRUE, ignore path references */ { int xlo, xhi, ylo, yhi, xsep, ysep, childStamp; int absa, absb, absd, abse, nconv; char cellname[1024], useid[1024], path[1024]; CellUse *subCellUse; CellDef *subCellDef; Transform t; Rect r; bool locked; char *slashptr, *pathptr; if (strncmp(line, "use", 3) != 0) { TxError("Expected \"use\" line but saw: %s", line); return (FALSE); } useid[0] = '\0'; nconv = sscanf(line, "use %1023s %1023s %1023s", cellname, useid, path); if (nconv < 1) { TxError("Malformed \"use\" line: %s", line); return (FALSE); } /* Make sure useid[0] is an empty string if no useid was provided */ if (nconv == 1) useid[0] = '\0'; if (nconv <= 2) path[0] = '\0'; pathptr = &path[0]; while (*pathptr == ' ' || *pathptr == '\t') pathptr++; if ((dereference == TRUE) || (*pathptr == '\n')) *pathptr = '\0'; locked = (useid[0] == CULOCKCHAR) ? TRUE : FALSE; if (dbFgets(line, len, f) == NULL) return (FALSE); if (strncmp(line, "array", 5) == 0) { if (sscanf(line, "array %d %d %d %d %d %d", &xlo, &xhi, &xsep, &ylo, &yhi, &ysep) != 6) { TxError("Malformed \"array\" line: %s", line); return (FALSE); } if (scalen > 1) { xsep *= scalen; ysep *= scalen; } if (scaled > 1) { xsep /= scaled; ysep /= scaled; } if (dbFgets(line, len, f) == NULL) return (FALSE); } else { xlo = ylo = 0; xhi = yhi = 0; xsep = ysep = 0; } if (strncmp(line, "timestamp", 9) == 0) { if (sscanf(line, "timestamp %d", &childStamp) != 1) { TxError("Malformed \"timestamp\" line: %s", line); return (FALSE); } if (dbFgets(line, len, f) == NULL) return (FALSE); } else childStamp = 0; if (sscanf(line, "transform %d %d %d %d %d %d", &t.t_a, &t.t_b, &t.t_c, &t.t_d, &t.t_e, &t.t_f) != 6) { badTransform: TxError("Malformed or illegal \"transform\" line: %s", line); return (FALSE); } /* * Sanity check for transform. * Either a == e == 0 and both abs(b) == abs(d) == 1, * or b == d == 0 and both abs(a) == abs(e) == 1. */ if (t.t_a == 0) { absb = t.t_b > 0 ? t.t_b : -t.t_b; absd = t.t_d > 0 ? t.t_d : -t.t_d; if (t.t_e != 0 || absb != 1 || absd != 1) goto badTransform; } else { absa = t.t_a > 0 ? t.t_a : -t.t_a; abse = t.t_e > 0 ? t.t_e : -t.t_e; if (t.t_b != 0 || t.t_d != 0 || absa != 1 || abse != 1) goto badTransform; } if (dbFgets(line, len, f) == NULL) return (FALSE); if (sscanf(line, "box %d %d %d %d", &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop) != 4) { TxError("Malformed \"box\" line: %s", line); return (FALSE); } if (scalen > 1) { t.t_c *= scalen; t.t_f *= scalen; r.r_xbot *= scalen; r.r_ybot *= scalen; r.r_xtop *= scalen; r.r_ytop *= scalen; } if (scaled > 1) { t.t_c /= scaled; t.t_f /= scaled; r.r_xbot /= scaled; r.r_ybot /= scaled; r.r_xtop /= scaled; r.r_ytop /= scaled; } /* * Set up cell use. * If the definition for this use has not been read in, * make a dummy one that's marked not available. For now, * don't change the bounding box if the cell already exists * (we'll fix it below when handling timestamp problems). */ subCellDef = DBCellLookDef(cellname); if (subCellDef == (CellDef *) NULL) { subCellDef = DBCellNewDef(cellname, (char *)NULL); subCellDef->cd_timestamp = childStamp; /* Make sure rectangle is non-degenerate */ if (GEO_RECTNULL(&r)) { TxPrintf("Subcell has degenerate bounding box: %d %d %d %d\n", r.r_xbot, r.r_ybot, r.r_xtop, r.r_ytop); TxPrintf("Adjusting bounding box of subcell %s of %s", cellname, cellDef->cd_name); if (r.r_xtop <= r.r_xbot) r.r_xtop = r.r_xbot + 1; if (r.r_ytop <= r.r_ybot) r.r_ytop = r.r_ybot + 1; TxPrintf(" to %d %d %d %d\n", r.r_xbot, r.r_ybot, r.r_xtop, r.r_ytop); } subCellDef->cd_bbox = r; subCellDef->cd_extended = r; } else if (DBIsAncestor(subCellDef, cellDef)) { /* * Watchout for attempts to create circular structures. * If this happens, disregard the subcell. */ TxPrintf("Subcells are used circularly!\n"); TxPrintf("Ignoring subcell %s of %s.\n", cellname, cellDef->cd_name); goto nextLine; } /* Relative path handling: If path does not have a leading "/" */ /* or "~" and cellDef->cd_file has path components, then the path */ /* should be interpreted relative to the path of the parent cell. */ if ((*pathptr == '\0') || ((*pathptr != '/') && (*pathptr != '~'))) if ((cellDef->cd_file != NULL) && (slashptr = strrchr(cellDef->cd_file, '/')) != NULL) { *slashptr = '\0'; if (*pathptr == '\0') strcpy(path, cellDef->cd_file); else { char savepath[1024]; strcpy(savepath, pathptr); sprintf(path, "%s/%s", cellDef->cd_file, savepath); } pathptr = &path[0]; *slashptr = '/'; } /* If path has a leading '~/' and cellDef->cd_file has an absolute */ /* path that does not match the user's home directory, but appears */ /* to be a different home directory, then replace the "~" with the */ /* home directory used by the parent cell. */ if (*pathptr == '~' && *(pathptr + 1) == '/') if ((cellDef->cd_file != NULL) && (cellDef->cd_file[0] == '/')) { char *homedir = getenv("HOME"); if (strncmp(cellDef->cd_file, homedir, strlen(homedir)) || *(cellDef->cd_file + strlen(homedir)) != '/') { char *homeroot = strrchr(homedir, '/'); int rootlen = (int)(homeroot - homedir) + 1; if (!strncmp(cellDef->cd_file, homedir, rootlen)) { char savepath[1024]; char *userbrk = strchr(cellDef->cd_file + rootlen, '/'); if (userbrk != NULL) { int userlen = (int)(userbrk - cellDef->cd_file); strcpy(savepath, pathptr + 1); strcpy(path, cellDef->cd_file); strcpy(path + userlen, savepath); pathptr = &path[0]; } } } } /* If "use" line contains a path name, then set cd_file to this and */ /* it will be the preferred path. If cd_file is already set and */ /* points to a different target, then flag an error, as there are */ /* now two versions of the same cell name coming from different */ /* sources, and this must be corrected. */ if (*pathptr != '\0') { if (subCellDef->cd_file != NULL) { slashptr = strrchr(subCellDef->cd_file, '/'); if (slashptr != NULL) { bool pathOK = FALSE; *slashptr = '\0'; /* Avoid generating error message if pathptr starts with '~' */ /* and the tilde-expanded name matches the subCellDef name */ if (*pathptr == '~') { char *homedir = getenv("HOME"); if (!strncmp(subCellDef->cd_file, homedir, strlen(homedir)) && (!strcmp(subCellDef->cd_file + strlen(homedir), pathptr + 1))) pathOK = TRUE; } if ((pathOK == FALSE) && strcmp(subCellDef->cd_file, pathptr)) { TxError("Duplicate cell in %s: Instance of cell %s is from " "path %s but cell was previously read from %s.\n", cellDef->cd_name, slashptr + 1, pathptr, subCellDef->cd_file); /* To do: Check if new path does not exist (ignore), */ /* or if new path has same symbolic link or is the same */ /* filesize and checksum (ignore). If file appears to */ /* be truly different, then create a new cell with a */ /* modified cell name. */ TxError("New path will be ignored. Please check.\n"); } *slashptr = '/'; } } else { /* Reconstruct file from path and cellname */ strcat(path, "/"); strcat(path, subCellDef->cd_name); strcat(path, DBSuffix); StrDup(&subCellDef->cd_file, path); } } subCellUse = DBCellNewUse(subCellDef, (useid[0]) ? ((locked) ? useid + 1 : useid) : (char *) NULL); if (locked) subCellUse->cu_flags |= CU_LOCKED; /* * Instead of calling DBLinkCell for each cell, DBGenerateUniqueIds() * gets called for the entire cell at the end. */ DBMakeArray(subCellUse, &GeoIdentityTransform, xlo, ylo, xhi, yhi, xsep, ysep); DBSetTrans(subCellUse, &t); /* * Link the subcell into the parent. * This should be the only place where a cell use * gets created as part of the database, and not recorded * on the undo list (because undo is disabled while the * cell is being read in). */ DBPlaceCell(subCellUse, cellDef); /* * Things get real tricky if the our guess about the * timestamp doesn't match the existing timestamp in * subCellDef. This can be because (1) subCellDef has * been read in, so we're just a confused parent, or * (2) the cell hasn't been read in yet, so two parents * disagree, and it's not clear which is correct. In * either event, call the timestamp manager with our guess * area, since it seems to be wrong, and in case (2) also * call the timestamp manager with the existing area, since * that parent is probably confused too. If the cell isn't * available, set the timestamp to zero to force mismatches * forever until the cell gets read from disk. */ if ((childStamp != subCellDef->cd_timestamp) || (childStamp == 0)) { DBStampMismatch(subCellDef, &r); if (!(subCellDef->cd_flags & CDAVAILABLE)) subCellDef->cd_timestamp = 0; else DBStampMismatch(subCellDef, &subCellDef->cd_bbox); } nextLine: return (dbFgets(line, len, f) != NULL); } /* * ---------------------------------------------------------------------------- * * dbReadProperties -- * * Starting with the line << properties >>, read properties for 'cellDef' * up to the end of the properties section. On exit, 'line' contains * the line that terminated the properties section, which will be either * a line of the form "<< something >>" or one beginning with a * character other than 'r', 'l', or 't'. * * Results: * Returns TRUE normally, or FALSE on error or EOF. * * Side effects: * See above. * * ---------------------------------------------------------------------------- */ bool dbReadProperties(cellDef, line, len, f, scalen, scaled) CellDef *cellDef; /* Cell whose elements are being read */ char *line; /* Line containing << elements >> */ int len; /* Size of buffer pointed to by line */ FILE *f; /* Input file */ int scalen; /* Scale up by this factor */ int scaled; /* Scale down by this factor */ { char propertyname[128], propertyvalue[2048], *storedvalue; int ntok; unsigned int noeditflag; /* Save CDNOEDIT flag if set, and clear it */ noeditflag = cellDef->cd_flags & CDNOEDIT; cellDef->cd_flags &= ~CDNOEDIT; /* Get first element line */ if (dbFgets(line, len, f) == NULL) return (FALSE); while (TRUE) { /* Skip blank lines */ while (line[0] == '\0') if (dbFgets(line, len, f) == NULL) { cellDef->cd_flags |= noeditflag; return (TRUE); } /* Stop when at end of properties section (currently, only "string" * is defined) */ if (line[0] != 's') break; /* * Properties may only be "string", for now. This may be the only * property type ever needed. */ if (line[0] == 's') { if ((ntok = sscanf(line, "string %127s %2047[^\n]", propertyname, propertyvalue)) != 2) { TxError("Skipping bad property line: %s", line); goto nextproperty; } /* Go ahead and process the vendor GDS property */ if (!strcmp(propertyname, "GDS_FILE")) cellDef->cd_flags |= CDVENDORGDS; /* Also process FIXED_BBOX property, as units must match */ if (!strcmp(propertyname, "FIXED_BBOX")) { Rect locbbox; if (sscanf(propertyvalue, "%d %d %d %d", &(locbbox.r_xbot), &(locbbox.r_ybot), &(locbbox.r_xtop), &(locbbox.r_ytop)) != 4) { TxError("Cannot read bounding box values in %s property", propertyname); storedvalue = StrDup((char **)NULL, propertyvalue); (void) DBPropPut(cellDef, propertyname, storedvalue); } else { if (scalen > 1) { locbbox.r_xbot *= scalen; locbbox.r_ybot *= scalen; locbbox.r_xtop *= scalen; locbbox.r_ytop *= scalen; } if (scaled > 1) { locbbox.r_xbot /= scaled; locbbox.r_ybot /= scaled; locbbox.r_xtop /= scaled; locbbox.r_ytop /= scaled; } cellDef->cd_flags |= CDFIXEDBBOX; storedvalue = (char *)mallocMagic(40); sprintf(storedvalue, "%d %d %d %d", locbbox.r_xbot, locbbox.r_ybot, locbbox.r_xtop, locbbox.r_ytop); (void) DBPropPut(cellDef, propertyname, storedvalue); } } else { storedvalue = StrDup((char **)NULL, propertyvalue); (void) DBPropPut(cellDef, propertyname, storedvalue); } } nextproperty: if (dbFgets(line, len, f) == NULL) break; } cellDef->cd_flags |= noeditflag; return (TRUE); } /* * ---------------------------------------------------------------------------- * * dbReadElements -- * * Starting with the line << elements >>, read elements for 'cellDef' * up to the end of the elements section. On exit, 'line' contains * the line that terminated the elements section, which will be either * a line of the form "<< something >>" or one beginning with a * character other than 'r', 'l', or 't'. * * Results: * Returns TRUE normally, or FALSE on error or EOF. * * Side effects: * See above. * * ---------------------------------------------------------------------------- */ bool dbReadElements(cellDef, line, len, f, scalen, scaled) CellDef *cellDef; /* Cell whose elements are being read */ char *line; /* Line containing << elements >> */ int len; /* Size of buffer pointed to by line */ FILE *f; /* Input file */ int scalen; /* Scale up by this factor */ int scaled; /* Scale down by this factor */ { char elementname[128], styles[1024], *text, flags[100]; int istyle, ntok; Rect r; char *tstr, *nstr; /* Get first element line */ if (dbFgets(line, len, f) == NULL) return (FALSE); while (TRUE) { /* Skip blank lines */ while (line[0] == '\0') if (dbFgets(line, len, f) == NULL) return (TRUE); /* Stop when at end of elements section (either paint or cell use) */ if (line[0] != 'r' && line[0] != 'l' && line[0] != 't') break; /* * Elements may be either rectangles, lines, or text. */ if (line[0] == 'r') { if ((ntok = sscanf(line, "rectangle %127s %1023s %d %d %d %d %99[^\n]", elementname, styles, &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop, flags)) < 6) { TxError("Skipping bad \"rectangle\" element line: %s", line); goto nextelement; } if (scalen > 1) { r.r_xbot *= scalen; r.r_ybot *= scalen; r.r_xtop *= scalen; r.r_ytop *= scalen; } if (scaled > 1) { r.r_xbot /= scaled; r.r_ybot /= scaled; r.r_xtop /= scaled; r.r_ytop /= scaled; } (void) DBWElementAddRect(NULL, elementname, &r, cellDef, 0); ntok -= 6; } else if (line[0] == 'l') { if ((ntok = sscanf(line, "line %127s %1023s %d %d %d %d %99[^\n]", elementname, styles, &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop, flags)) < 6) { TxError("Skipping bad \"line\" element line: %s", line); goto nextelement; } if (scalen > 1) { r.r_xbot *= scalen; r.r_ybot *= scalen; r.r_xtop *= scalen; r.r_ytop *= scalen; } if (scaled > 1) { r.r_xbot /= scaled; r.r_ybot /= scaled; r.r_xtop /= scaled; r.r_ytop /= scaled; } (void) DBWElementAddLine(NULL, elementname, &r, cellDef, 0); ntok -= 6; } else { char *textend; if (((ntok = sscanf(line, "text %127s %1023s %d %d", elementname, styles, &r.r_xbot, &r.r_ybot)) < 4) || ((text = strchr(line, '"')) == NULL) || ((textend = strrchr(line, '"')) == text)) { TxError("Skipping bad \"text\" element line: %s", line); goto nextelement; } text++; *textend = '\0'; if (scalen > 1) { r.r_xbot *= scalen; r.r_ybot *= scalen; } if (scaled > 1) { r.r_xbot /= scaled; r.r_ybot /= scaled; } (void) DBWElementAddText(NULL, elementname, r.r_xbot, r.r_ybot, text, cellDef, 0); *textend = '"'; ntok += sscanf(textend + 1, "%99[^\n]", flags); ntok -= 4; } DBWElementParseFlags(NULL, elementname, "persistent"); /* Set the style(s) for this element */ tstr = styles; while ((nstr = strchr(tstr, ',')) != NULL) { *nstr = '\0'; istyle = GrGetStyleFromName(tstr); DBWElementStyle(NULL, elementname, istyle, TRUE); *nstr = ','; tstr = nstr + 1; } istyle = GrGetStyleFromName(tstr); DBWElementStyle(NULL, elementname, istyle, TRUE); /* Remove initial style 0, which was temporary */ DBWElementStyle(NULL, elementname, 0, FALSE); /* Any remaining text should be a comma-separated list of flags */ if (ntok > 0) { tstr = flags; while (isspace(*tstr)) tstr++; while ((nstr = strchr(tstr, ',')) != NULL) { *nstr = '\0'; DBWElementParseFlags(NULL, elementname, tstr); *nstr = ','; tstr = nstr + 1; } DBWElementParseFlags(NULL, elementname, tstr); } nextelement: if (dbFgets(line, len, f) == NULL) break; } return (TRUE); } /* * ---------------------------------------------------------------------------- * * dbReadLabels -- * * Starting with the line << labels >>, read labels for 'cellDef' * up until the end of a label section. On exit, 'line' contains * the line that terminated the label section, which will be either * a line of the form "<< something >>" or one beginning with a * character other than 'r', 'l', 'f', or 'p'. * * Results: * Returns TRUE normally, or FALSE on error or EOF. * * Side effects: * See above. * * ---------------------------------------------------------------------------- */ bool dbReadLabels(cellDef, line, len, f, scalen, scaled) CellDef *cellDef; /* Cell whose labels are being read */ char *line; /* Line containing << labels >> */ int len; /* Size of buffer pointed to by line */ FILE *f; /* Input file */ int scalen; /* Scale up by this factor */ int scaled; /* Scale down by this factor */ { char layername[50], text[1024], port_use[50], port_class[50]; TileType type; int ntok, orient, size, rotate, font, flags; Point offset; Rect r; char stickyflag[2]; /* Get first label line */ if (dbFgets(line, len, f) == NULL) return (FALSE); while (TRUE) { /* Skip blank lines */ while (line[0] == '\0') if (dbFgets(line, len, f) == NULL) return (TRUE); /* Stop when at end of labels section (either paint or cell use) */ if (line[0] != 'r' && line[0] != 'l' && line[0] != 'p' && line[0] != 'f') break; /* * Labels may be either point labels or rectangular ones. * Since each label is associated with a particular * tile, the type of tile is also stored. */ if (line[0] == 'r') { if (sscanf(line, "rlabel %*49s %1s", stickyflag) == 1) { font = -1; if (*stickyflag == 's') { flags = LABEL_STICKY; if (sscanf(line, "rlabel %49s %c %d %d %d %d %d %99[^\n]", layername, &stickyflag[0], &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop, &orient, text) != 8) { TxError("Skipping bad \"rlabel\" line: %s", line); goto nextlabel; } } else { flags = 0; if (sscanf(line, "rlabel %49s %d %d %d %d %d %99[^\n]", layername, &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop, &orient, text) != 7) { TxError("Skipping bad \"rlabel\" line: %s", line); goto nextlabel; } } } else { TxError("Skipping bad \"flabel\" line: %s", line); goto nextlabel; } } else if (line[0] == 'f') { char fontname[256]; if (sscanf(line, "flabel %*49s %1s", stickyflag) == 1) { if (*stickyflag == 's') { flags = LABEL_STICKY; if (sscanf(line, "flabel %49s %c %d %d %d %d %d %255s %d %d %d %d %99[^\n]", layername, &stickyflag[0], &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop, &orient, fontname, &size, &rotate, &offset.p_x, &offset.p_y, text) != 13) { TxError("Skipping bad \"flabel\" line: %s", line); goto nextlabel; } } else { flags = 0; if (sscanf(line, "flabel %49s %d %d %d %d %d %255s %d %d %d %d %99[^\n]", layername, &r.r_xbot, &r.r_ybot, &r.r_xtop, &r.r_ytop, &orient, fontname, &size, &rotate, &offset.p_x, &offset.p_y, text) != 12) { TxError("Skipping bad \"flabel\" line: %s", line); goto nextlabel; } } } else { TxError("Skipping bad \"flabel\" line: %s", line); goto nextlabel; } font = DBNameToFont(fontname); if (font < -1) font = -1; /* Force default font if font is unknown */ } else if (line[0] == 'p') { char ppos[5], *pptr; int idx; Label *lab; if (((lab = cellDef->cd_lastLabel) == NULL) || (lab->lab_flags & PORT_DIR_MASK) || (((ntok = sscanf(line, "port %d %4s %49s %49s", &idx, ppos, port_use, port_class)) != 2) && (ntok != 4))) { TxError("Skipping bad \"port\" line: %s", line); goto nextlabel; } /* lab->lab_flags &= ~LABEL_STICKY; */ lab->lab_flags |= idx; for (pptr = &ppos[0]; *pptr != '\0'; pptr++) { switch(*pptr) { case 'n': lab->lab_flags |= PORT_DIR_NORTH; break; case 's': lab->lab_flags |= PORT_DIR_SOUTH; break; case 'e': lab->lab_flags |= PORT_DIR_EAST; break; case 'w': lab->lab_flags |= PORT_DIR_WEST; break; } } if (ntok == 4) { switch(port_use[0]) { case 's': lab->lab_flags |= PORT_USE_SIGNAL; break; case 'a': lab->lab_flags |= PORT_USE_ANALOG; break; case 'p': lab->lab_flags |= PORT_USE_POWER; break; case 'g': lab->lab_flags |= PORT_USE_GROUND; break; case 'c': lab->lab_flags |= PORT_USE_CLOCK; break; case 'd': lab->lab_flags |= PORT_USE_DEFAULT; break; default: TxError("Ignoring unknown \"port\" use: %s", port_use); break; } switch(port_class[0]) { case 'i': lab->lab_flags |= PORT_CLASS_INPUT; break; case 'o': lab->lab_flags |= PORT_CLASS_OUTPUT; break; case 't': lab->lab_flags |= PORT_CLASS_TRISTATE; break; case 'b': lab->lab_flags |= PORT_CLASS_BIDIRECTIONAL; break; case 'f': lab->lab_flags |= PORT_CLASS_FEEDTHROUGH; break; case 'd': lab->lab_flags |= PORT_CLASS_DEFAULT; break; default: TxError("Ignoring unknown \"port\" use: %s", port_use); break; } } goto nextlabel; } else /* deprecated, retained for backward compatibility */ { if (sscanf(line, "label %49s %d %d %d %99[^\n]", layername, &r.r_xbot, &r.r_ybot, &orient, text) != 5) { TxError("Skipping bad \"label\" line: %s", line); goto nextlabel; } r.r_xtop = r.r_xbot; r.r_ytop = r.r_ybot; font = -1; } if (scalen > 1) { r.r_xbot *= scalen; r.r_ybot *= scalen; r.r_xtop *= scalen; r.r_ytop *= scalen; if (font >= 0) { size *= scalen; offset.p_x *= scalen; offset.p_y *= scalen; } } if (scaled > 1) { r.r_xbot /= scaled; r.r_ybot /= scaled; r.r_xtop /= scaled; r.r_ytop /= scaled; if (font >= 0) { size /= scaled; offset.p_x /= scaled; offset.p_y /= scaled; } } type = DBTechNameType(layername); if (type < 0) { TileTypeBitMask rmask; /* Check against alias hash table. Names that are */ /* aliases for multiple types return the first type */ /* encountered (lowest mask bit number). */ type = DBTechNameTypes(layername, &rmask); } if (type < 0) { TxError("Warning: label \"%s\" attached to unknown " "type \"%s\"\n", text, layername); type = TT_SPACE; } else if (type >= DBNumUserLayers) { TileTypeBitMask *rmask; TileType rtype; /* Don't stick labels on stacked types; choose the */ /* topmost (last, usually) residue contact type. */ rmask = DBResidueMask(type); for (rtype = TT_SPACE + 1; rtype < DBNumUserLayers; rtype++) if (TTMaskHasType(rmask, rtype)) type = rtype; } if (font < 0) DBPutLabel(cellDef, &r, orient, text, type, flags); else DBPutFontLabel(cellDef, &r, font, size, rotate, &offset, orient, text, type, flags); nextlabel: if (dbFgets(line, len, f) == NULL) break; } return (TRUE); } /* * ---------------------------------------------------------------------------- * * dbFgets -- * * Like fgets(), but ignore lines beginning with a pound-sign. * * Results: * Returns a pointer to 'line', or NULL on EOF. * * Side effects: * Stores characters into 'line', terminating it with a * NULL byte. * * ---------------------------------------------------------------------------- */ char * dbFgets(line, len, f) char *line; int len; FILE *f; { char *cs; int l; int c; do { cs = line, l = len; while (--l > 0 && (c = getc(f)) != EOF) { if (c != '\r') *cs++ = c; if (c == '\n') break; } if (c == EOF && cs == line) return (NULL); *cs = '\0'; } while (line[0] == '#'); return (line); } /* * ---------------------------------------------------------------------------- * * DBCellFindScale -- * * Results: * Returns the greatest common factor of all the geometry in the cellDef. * This includes tiles, labels, and cell use positions (from the cell use * transform), bounding boxes, and array spacing. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ int DBCellFindScale(cellDef) CellDef *cellDef; { int dbFindGCFFunc(), dbFindCellGCFFunc(); TileType type; TileTypeBitMask typeMask; int pNum; int ggcf; Label *lab; /* We only care about preventing magic from making the grid spacing finer */ /* and finer. If the current scale is lambda = 1 magic unit or larger, */ /* then we simply set the scale factor to 1 and return. Otherwise, do the */ /* search to see if we can write this cell out at a coarser scale. */ if (DBLambda[1] <= DBLambda[0]) return 1; /* Find greatest common factor of all geometry. If this becomes 1, stop. */ ggcf = DBLambda[1]; for (type = TT_PAINTBASE; type < DBNumUserLayers; type++) { if ((pNum = DBPlane(type)) < 0) continue; TTMaskSetOnlyType(&typeMask, type); if (DBSrPaintArea((Tile *) NULL, cellDef->cd_planes[pNum], &TiPlaneRect, &typeMask, dbFindGCFFunc, (ClientData) &ggcf)) return 1; } /* Now labels */ if (cellDef->cd_labels) { for (lab = cellDef->cd_labels; lab; lab = lab->lab_next) { if (lab->lab_rect.r_xtop % ggcf != 0) ggcf = FindGCF(lab->lab_rect.r_xtop, ggcf); if (lab->lab_rect.r_xbot % ggcf != 0) ggcf = FindGCF(lab->lab_rect.r_xbot, ggcf); if (lab->lab_rect.r_ytop % ggcf != 0) ggcf = FindGCF(lab->lab_rect.r_ytop, ggcf); if (lab->lab_rect.r_ybot % ggcf != 0) ggcf = FindGCF(lab->lab_rect.r_ybot, ggcf); if (ggcf == 1) return 1; } } /* Finally, cell uses */ if (DBCellEnum(cellDef, dbFindCellGCFFunc, (ClientData) &ggcf)) return 1; return ggcf; } int dbFindGCFFunc(tile, ggcf) Tile *tile; int *ggcf; { Rect r; TiToRect(tile, &r); if (r.r_xtop % (*ggcf) != 0) *ggcf = FindGCF(r.r_xtop, *ggcf); if (r.r_xbot % (*ggcf) != 0) *ggcf = FindGCF(r.r_xbot, *ggcf); if (r.r_ytop % (*ggcf) != 0) *ggcf = FindGCF(r.r_ytop, *ggcf); if (r.r_ybot % (*ggcf) != 0) *ggcf = FindGCF(r.r_ybot, *ggcf); return (*ggcf == 1) ? 1 : 0; } int dbFindCellGCFFunc(cellUse, ggcf) CellUse *cellUse; /* Cell use whose "call" is to be written to a file */ int *ggcf; /* Greatest common denominator for all geometry */ { Transform *t; Rect *b; t = &(cellUse->cu_transform); b = &(cellUse->cu_def->cd_bbox); /* Check transform translation values */ if (t->t_c % (*ggcf) != 0) *ggcf = FindGCF(t->t_c, *ggcf); if (t->t_f % (*ggcf) != 0) *ggcf = FindGCF(t->t_f, *ggcf); /* Check bounding box */ if (b->r_xtop % (*ggcf) != 0) *ggcf = FindGCF(b->r_xtop, *ggcf); if (b->r_xbot % (*ggcf) != 0) *ggcf = FindGCF(b->r_xbot, *ggcf); if (b->r_ytop % (*ggcf) != 0) *ggcf = FindGCF(b->r_ytop, *ggcf); if (b->r_ybot % (*ggcf) != 0) *ggcf = FindGCF(b->r_ybot, *ggcf); /* Check array separation, if arrayed */ if ((cellUse->cu_xlo != cellUse->cu_xhi) || (cellUse->cu_ylo != cellUse->cu_yhi)) { if (cellUse->cu_xsep % (*ggcf) != 0) *ggcf = FindGCF(cellUse->cu_xsep, *ggcf); if (cellUse->cu_ysep % (*ggcf) != 0) *ggcf = FindGCF(cellUse->cu_ysep, *ggcf); } return (*ggcf == 1) ? 1 : 0; } /* * ---------------------------------------------------------------------------- * * DBCellWriteFile -- * * NOTE: this routine is usually not want you want. Use DBCellWrite(). * * Write out the paint for a cell to the specified file. * Mark the cell as having been written out. Before calling this * procedure, the caller should make sure that timestamps have been * updated where appropriate. * * Results: * TRUE if the cell could be written successfully, FALSE otherwise. * * Side effects: * Writes a file to disk. * Does NOT close the file 'f', but does fflush(f) before * returning. * * If successful, clears the CDMODIFIED, CDBOXESCHANGED, * and CDSTAMPSCHANGED bits in cellDef->cd_flags. * * In the event of an error while writing out the cell, * the external integer errno is set to the UNIX error * encountered, and the above bits are not cleared in * cellDef->cd_flags. * * ---------------------------------------------------------------------------- */ bool DBCellWriteFile(cellDef, f) CellDef *cellDef; /* Pointer to definition of cell to be written out */ FILE *f; /* The FILE to write to */ { int dbWritePaintFunc(), dbWriteCellFunc(), dbWritePropFunc(); int dbClearCellFunc(); Label *lab; struct writeArg arg; int pNum; TileType type, stype; TileTypeBitMask typeMask, *sMask; int reducer; char *estring; char lstring[256]; char *propvalue; bool propfound; #define FPRINTF(f,s)\ {\ if (fprintf(f,s) == EOF) goto ioerror;\ DBFileOffset += strlen(s);\ } #define FPRINTR(f,s)\ {\ if (fprintf(f,s) == EOF) return 1;\ DBFileOffset += strlen(s);\ } if (f == NULL) return FALSE; /* If interrupts are left enabled, a partial file could get written. * This is not good. */ reducer = DBCellFindScale(cellDef); SigDisableInterrupts(); DBFileOffset = 0; if (cellDef->cd_flags & CDGETNEWSTAMP) TxPrintf("Magic error: writing out-of-date timestamp for %s.\n", cellDef->cd_name); { char headerstring[256]; if (DBLambda[0] == (DBLambda[1] / reducer)) /* Default scale */ sprintf(headerstring,"magic\ntech %s\ntimestamp %d\n", DBTechName,cellDef->cd_timestamp); else sprintf(headerstring,"magic\ntech %s\nmagscale %d %d\ntimestamp %d\n", DBTechName, DBLambda[0], DBLambda[1] / reducer, cellDef->cd_timestamp); FPRINTF(f, headerstring); } /* * Output the paint of the cell. * Note that we only output up to the last layer appearing * in the technology file (DBNumUserLayers-1). Automatically * generated stacked contact types are added to typeMask and * will be decomposed into the residue appropriate for the * plane being searched. */ if (cellDef->cd_file) arg.wa_name = cellDef->cd_file; else arg.wa_name = cellDef->cd_name; arg.wa_file = f; arg.wa_reducer = reducer; for (type = TT_PAINTBASE; type < DBNumUserLayers; type++) { if ((pNum = DBPlane(type)) < 0) continue; arg.wa_found = FALSE; arg.wa_type = type; arg.wa_plane = pNum; TTMaskSetOnlyType(&typeMask, type); /* Add to the mask all generated (stacking) types which */ /* have this type as a residue. */ for (stype = DBNumUserLayers; stype < DBNumTypes; stype++) { sMask = DBResidueMask(stype); if (TTMaskHasType(sMask, type)) TTMaskSetType(&typeMask, stype); } if (DBSrPaintArea((Tile *) NULL, cellDef->cd_planes[pNum], &TiPlaneRect, &typeMask, dbWritePaintFunc, (ClientData) &arg)) goto ioerror; } /* Now the cell uses */ if (DBCellEnum(cellDef, dbWriteCellFunc, (ClientData) &arg)) goto ioerror; /* Clear flags set in dbWriteCellFunc */ DBCellEnum(cellDef, dbClearCellFunc, (ClientData)NULL); /* Now labels */ if (cellDef->cd_labels) { FPRINTF(f, "<< labels >>\n"); for (lab = cellDef->cd_labels; lab; lab = lab->lab_next) { if (strlen(lab->lab_text) == 0) continue; // Shouldn't happen if (lab->lab_font < 0) { sprintf(lstring, "rlabel %s %s%d %d %d %d %d %s\n", DBTypeLongName(lab->lab_type), ((lab->lab_flags & LABEL_STICKY) ? "s " : ""), lab->lab_rect.r_xbot / reducer, lab->lab_rect.r_ybot / reducer, lab->lab_rect.r_xtop / reducer, lab->lab_rect.r_ytop / reducer, lab->lab_just, lab->lab_text); } else { sprintf(lstring, "flabel %s %s%d %d %d %d %d %s %d %d %d %d %s\n", DBTypeLongName(lab->lab_type), ((lab->lab_flags & LABEL_STICKY) ? "s " : ""), lab->lab_rect.r_xbot / reducer, lab->lab_rect.r_ybot / reducer, lab->lab_rect.r_xtop / reducer, lab->lab_rect.r_ytop / reducer, lab->lab_just, DBFontList[lab->lab_font]->mf_name, lab->lab_size / reducer, lab->lab_rotate, lab->lab_offset.p_x / reducer, lab->lab_offset.p_y / reducer, lab->lab_text); } FPRINTF(f, lstring); if (lab->lab_flags & PORT_DIR_MASK) { char ppos[5]; ppos[0] = '\0'; if (lab->lab_flags & PORT_DIR_NORTH) strcat(ppos, "n"); if (lab->lab_flags & PORT_DIR_SOUTH) strcat(ppos, "s"); if (lab->lab_flags & PORT_DIR_EAST) strcat(ppos, "e"); if (lab->lab_flags & PORT_DIR_WEST) strcat(ppos, "w"); sprintf(lstring, "port %d %s", lab->lab_flags & PORT_NUM_MASK, ppos); if (lab->lab_flags & (PORT_USE_MASK | PORT_CLASS_MASK)) { switch (lab->lab_flags & PORT_USE_MASK) { case PORT_USE_SIGNAL: strcat(lstring, " signal"); break; case PORT_USE_ANALOG: strcat(lstring, " analog"); break; case PORT_USE_POWER: strcat(lstring, " power"); break; case PORT_USE_GROUND: strcat(lstring, " ground"); break; case PORT_USE_CLOCK: strcat(lstring, " clock"); break; case PORT_USE_DEFAULT: strcat(lstring, " default"); break; } switch (lab->lab_flags & PORT_CLASS_MASK) { case PORT_CLASS_INPUT: strcat(lstring, " input"); break; case PORT_CLASS_OUTPUT: strcat(lstring, " output"); break; case PORT_CLASS_TRISTATE: strcat(lstring, " tristate"); break; case PORT_CLASS_BIDIRECTIONAL: strcat(lstring, " bidirectional"); break; case PORT_CLASS_FEEDTHROUGH: strcat(lstring, " feedthrough"); break; case PORT_CLASS_DEFAULT: strcat(lstring, " default"); break; } } strcat(lstring, "\n"); FPRINTF(f, lstring); } } } /* Now any persistent elements */ estring = DBWPrintElements(cellDef, DBW_ELEMENT_PERSISTENT); if (estring != NULL) { FPRINTF(f, "<< elements >>\n"); FPRINTF(f, estring); freeMagic(estring); } /* And any properties */ /* NOTE: FIXED_BBOX is treated specially; values are database */ /* values and should be divided by reducer. Easiest to do it */ /* here and revert values after. */ propvalue = (char *)DBPropGet(cellDef, "FIXED_BBOX", &propfound); if (propfound) { char *proporig, *propscaled; Rect scalebox, bbox; proporig = StrDup((char **)NULL, propvalue); propscaled = mallocMagic(strlen(propvalue) + 5); if (sscanf(propvalue, "%d %d %d %d", &bbox.r_xbot, &bbox.r_ybot, &bbox.r_xtop, &bbox.r_ytop) == 4) { scalebox.r_xbot = bbox.r_xbot / reducer; scalebox.r_xtop = bbox.r_xtop / reducer; scalebox.r_ybot = bbox.r_ybot / reducer; scalebox.r_ytop = bbox.r_ytop / reducer; sprintf(propscaled, "%d %d %d %d", bbox.r_xbot / reducer, bbox.r_ybot / reducer, bbox.r_xtop / reducer, bbox.r_ytop / reducer); DBPropPut(cellDef, "FIXED_BBOX", propscaled); propvalue = proporig; } } if (cellDef->cd_props != (ClientData)NULL) { FPRINTF(f, "<< properties >>\n"); DBPropEnum(cellDef, dbWritePropFunc, (ClientData)f); } if (propfound) DBPropPut(cellDef, "FIXED_BBOX", propvalue); FPRINTF(f, "<< end >>\n"); if (fflush(f) == EOF || ferror(f)) { ioerror: TxError("Warning: I/O error in writing file\n"); SigEnableInterrupts(); return (FALSE); } cellDef->cd_flags &= ~(CDMODIFIED|CDBOXESCHANGED|CDSTAMPSCHANGED); SigEnableInterrupts(); return (TRUE); } /* * ---------------------------------------------------------------------------- * * dbWritePropFunc -- * * Filter function used to write out a single cell property. * * Results: * Normally returns 0; returns 1 on I/O error. * * Side effects: * Writes to the disk file. * * Warnings: * This function assumes that all property values are strings! * This is currently true; if it changes in the future, this * function will have to check each property against a list of * expected property strings and output the value based on the * known format of what it points to. * * ---------------------------------------------------------------------------- */ int dbWritePropFunc(key, value, cdata) char *key; ClientData value; ClientData cdata; { FILE *f = (FILE *)cdata; char *lstring; lstring = (char *)mallocMagic(10 + strlen((char *)value) + strlen(key)); sprintf(lstring, "string %s %s\n", key, (char *)value); FPRINTR(f, lstring); freeMagic(lstring); return 0; } /* * ---------------------------------------------------------------------------- * * DBCellWrite -- * * Write out the paint for a cell to its associated disk file. * Mark the cell as having been written out. Before calling this * procedure, the caller should make sure that timestamps have been * updated where appropriate. * * This code is fairly tricky to ensure that we never destroy the * original contents of a cell in the event of an I/O error. We * try the following approaches in order. * * 1. If we can create a temporary file in the same directory as the * target cell, do so. Then write to the temporary file and rename * it to the target cell name. * * 2. If we can't create the above temporary file, open the target * cell for APPENDING, then write the new contents to the END of * the file. If successful, rewind the now-expanded file and * overwrite the beginning of the file, then truncate it. * * * Results: * TRUE if the cell could be written successfully, FALSE otherwise. * * Side effects: * Writes a file to disk. * If successful, clears the CDMODIFIED, CDBOXESCHANGED, * and CDSTAMPSCHANGED bits in cellDef->cd_flags. * * In the event of an error while writing out the cell, * the external integer errno is set to the UNIX error * encountered, and the above bits are not cleared in * cellDef->cd_flags. * * ---------------------------------------------------------------------------- */ bool DBCellWrite(cellDef, fileName) CellDef *cellDef; /* Pointer to definition of cell to be written out */ char *fileName; /* If not NULL, name of file to write. If NULL, * the name associated with the CellDef is used */ { #define NAME_SIZE 1000 char *template = ".XXXXXXX"; char *realname, *tmpname, *expandname; char *cp1, *cp2; char expandbuf[NAME_SIZE]; FILE *realf, *tmpf; int tmpres; struct stat statb; bool result, exists; result = FALSE; /* * Figure out the name of the file we will eventually write. */ if (fileName) { realname = (char *) mallocMagic(strlen(fileName) + strlen(DBSuffix) + 1); (void) sprintf(realname, "%s%s", fileName, DBSuffix); /* Bug fix: 7/17/99, Michael D. Godfrey: Forces */ /* cd_name and cd_file to ALWAYS be the same, otherwise ugly */ /* surprises can occur after saving a file as a different */ /* filename. */ cellDef->cd_file = StrDup(&cellDef->cd_file, realname); } else if (cellDef->cd_file) { realname = StrDup((char **) NULL, cellDef->cd_file); } else if (cellDef->cd_name) { realname = (char *) mallocMagic((unsigned) (strlen(cellDef->cd_name) + strlen(DBSuffix) + 1)); (void) sprintf(realname, "%s%s", cellDef->cd_name, DBSuffix); } else return (FALSE); /* * Expand the filename, removing the leading ~, if any. */ expandname = expandbuf; cp1 = realname; cp2 = expandname; if (PaExpand(&cp1, &cp2, NAME_SIZE) == -1) expandname = realname; /* * If the locking logic works, this should not happen. Files which * are not editable should never be set to MODIFIED. But it is * better to be extra safe. If it does happen, it would be good to * figure out why. */ if(cellDef->cd_flags & CDNOEDIT) { #ifdef FILE_LOCKS TxPrintf("File %s is locked by another user or " "is read_only and cannot be written\n", realname); #else TxPrintf("File %s is read_only and cannot be written\n", realname); #endif freeMagic(realname); return(FALSE); } /* Check if the .mag file exists. If not, we don't need to deal */ /* with temporary file names. */ exists = (access(expandname, F_OK) == 0) ? TRUE : FALSE; if (exists) { /* * Determine unique name for a temp file to write. */ tmpname = (char *) mallocMagic((unsigned) (strlen(expandname) + strlen(template) + 1)); (void) sprintf(tmpname, "%s%s", expandname, template); tmpres = mkstemp(tmpname); if (tmpres != -1) { /* Assert the file permissions of the original file */ /* This will prevent the overwriting of a file that */ /* has write permissions blocked. */ if (stat(expandname, &statb) == 0) fchmod(tmpres, statb.st_mode & 0777); close(tmpres); /* We'll re-open it as a stream, below */ } /* Critical: disable interrupts while we do our work */ SigDisableInterrupts(); /* Don't allow a write if the file isn't writeable, or if */ /* mkstemp() returned an error condition. */ if (file_is_not_writeable(expandname)) { if (tmpres != -1) unlink(tmpname); perror(expandname); goto cleanup; } } else { tmpname = StrDup((char **)NULL, expandname); } /* * See if we can create a temporary file in this directory. * If so, write to the temp file and then rename it after * we're done. */ if (tmpf = fopen(tmpname, "w")) { result = DBCellWriteFile(cellDef, tmpf); (void) fclose(tmpf); tmpf = NULL; if (!result) { /* * Total loss -- just can't write the file. * The error message is printed elsewhere. */ (void) unlink(tmpname); goto cleanup; } #ifdef FILE_LOCKS if (cellDef->cd_fd != -1) { close(cellDef->cd_fd); cellDef->cd_fd = -1; } #endif /* * The temp file is in good shape -- rename it to the real name, * thereby completing the write. The error below should NEVER * normally happen. */ if (exists && (rename(tmpname, expandname) < 0)) { result = FALSE; perror("rename"); TxError("ATTENTION: Magic was unable to rename file %s to %s.\n" "If the file %s exists, it is the old copy of the cell %s.\n" "The new copy is in the file %s. Please copy this file\n" "to a safe place before executing any more Magic commands.\n", tmpname, expandname, expandname, cellDef->cd_name, tmpname); goto cleanup; } #ifdef FILE_LOCKS else { bool dereference = (cellDef->cd_flags & CDDEREFERENCE) ? TRUE : FALSE; /* Re-aquire the lock on the new file by opening it. */ DBCellRead(cellDef, NULL, TRUE, dereference, NULL); } #endif } else if (exists) { /* * Couldn't create a temp file in this directory. Instead, open * the original file for APPENDING, write this cell (just to make * sure the file is big enough), then rewind and write this cell * again, and finally truncate the file. The idea here is that * by appending to realf, we don't trash the existing data, but * do guarantee that there's enough space left to rewrite the * file (in effect, we're pre-reserving space for it). */ realf = fopen(expandname, "a"); if (realf == (FILE *) NULL) { perror(expandname); result = FALSE; goto cleanup; } /* Remember the original length of the file for later truncation */ (void) fstat(fileno(realf), &statb); /* Try to write by appending to the end of realf */ if (!(result = DBCellWriteFile(cellDef, realf))) { /* Total loss -- just can't write the file */ (void) fclose(realf); realf = NULL; (void) truncate(expandname, (long) statb.st_size); goto cleanup; } /* * Only try rewriting if the file wasn't zero-size to begin with. * (If the file were zero-size, we're already done). */ if (statb.st_size > 0) { rewind(realf); result = DBCellWriteFile(cellDef, realf); if (!result) { /* Should NEVER happen */ if (errno) perror(expandname); TxError("Something went wrong and the file %s was truncated\n", expandname); TxError("Try saving it in another file that is on a \n"); TxError("filesystem where there is enough space!\n"); (void) fclose(realf); realf = NULL; goto cleanup; } /* Successful writing the second time around */ statb.st_size = ftell(realf); (void) fclose(realf); realf = NULL; (void) truncate(expandname, (long) statb.st_size); } } /* Everything worked so far. */ (void) StrDup(&cellDef->cd_file, expandname); result = TRUE; { struct stat thestat; realf = fopen(expandname,"r"); if (realf == NULL) { cellDef->cd_flags |= CDMODIFIED; TxError("Warning: Cannot open file for writing!\n"); } else { fstat(fileno(realf),&thestat); if (thestat.st_size != DBFileOffset) { cellDef->cd_flags |= CDMODIFIED; TxError("Warning: I/O error in writing file\n"); } fclose(realf); } realf = NULL; } cleanup: SigEnableInterrupts(); freeMagic(realname); freeMagic(tmpname); return result; } /* * ---------------------------------------------------------------------------- * * dbWritePaintFunc -- * * Filter function used to write out a single paint tile. * Only writes out tiles of type arg->wa_type. * If the tile is the first encountered of its type, the header * << typename >> * is output. * * Results: * Normally returns 0; returns 1 on I/O error. * * Side effects: * Writes to the disk file. * * ---------------------------------------------------------------------------- */ int dbWritePaintFunc(tile, cdarg) Tile *tile; ClientData cdarg; { char pstring[256]; struct writeArg *arg = (struct writeArg *) cdarg; TileType type = TiGetType(tile); TileTypeBitMask *lMask, *rMask; int dir; if (IsSplit(tile)) { lMask = DBResidueMask(SplitLeftType(tile)); rMask = DBResidueMask(SplitRightType(tile)); if ((SplitLeftType(tile) == arg->wa_type) || ((SplitLeftType(tile) >= DBNumUserLayers) && TTMaskHasType(lMask, arg->wa_type))) { type = arg->wa_type; dir = 0x0; } else if ((SplitRightType(tile) == arg->wa_type) || ((SplitRightType(tile) >= DBNumUserLayers) && TTMaskHasType(rMask, arg->wa_type))) { type = arg->wa_type; dir = 0x2; } else return 0; } else if (type != arg->wa_type) { rMask = DBResidueMask(type); if ((type < DBNumUserLayers) || (!TTMaskHasType(rMask, arg->wa_type))) return 0; type = arg->wa_type; } if (!arg->wa_found) { sprintf(pstring, "<< %s >>\n", DBTypeLongName(type)); FPRINTR(arg->wa_file,pstring); arg->wa_found = TRUE; } if (IsSplit(tile)) { static char *pos_diag[] = {"nw", "sw", "se", "ne"}; dir |= SplitDirection(tile); sprintf(pstring, "tri %d %d %d %d %s\n", LEFT(tile) / arg->wa_reducer, BOTTOM(tile) / arg->wa_reducer, RIGHT(tile) / arg->wa_reducer, TOP(tile) / arg->wa_reducer, pos_diag[dir]); } else sprintf(pstring, "rect %d %d %d %d\n", LEFT(tile) / arg->wa_reducer, BOTTOM(tile) / arg->wa_reducer, RIGHT(tile) / arg->wa_reducer, TOP(tile) / arg->wa_reducer); FPRINTR(arg->wa_file,pstring); return 0; } /* * ---------------------------------------------------------------------------- * * dbClearCellFunc -- * * Filter function that clears flags set by dbWriteCellFunc. * * Results: * Always returns 0 * * Side effects: * Cell use flags changed. * * ---------------------------------------------------------------------------- */ int dbClearCellFunc(cellUse, cdarg) CellUse *cellUse; /* Cell use */ ClientData cdarg; /* Not used */ { cellUse->cu_def->cd_flags &= ~CDVISITED; return 0; } /* * ---------------------------------------------------------------------------- * * dbWriteCellFunc -- * * Filter function used to write out a single cell use in the * subcell tile plane for a cell. * * Results: * Normally returns 0; return 1 on I/O error * * Side effects: * Writes to the disk file. * * ---------------------------------------------------------------------------- */ int dbWriteCellFunc(cellUse, cdarg) CellUse *cellUse; /* Cell use whose "call" is to be written to a file */ ClientData cdarg; { struct writeArg *arg = (struct writeArg *) cdarg; Transform *t; Rect *b; char cstring[256], *pathend, *pathstart, *parent; t = &(cellUse->cu_transform); b = &(cellUse->cu_def->cd_bbox); pathstart = cellUse->cu_def->cd_file; parent = arg->wa_name; if (pathstart == NULL) pathend = NULL; else { char *slashptr, *pathorigin; /* Get child path relative to the parent path */ pathorigin = pathstart; pathend = strrchr(pathstart, '/'); slashptr = strchr(pathstart, '/'); while (slashptr) { if (!strncmp(pathorigin, parent, (int)(slashptr - pathorigin + 1))) { pathstart = slashptr + 1; slashptr = strchr(pathstart, '/'); } else break; } if (pathend != NULL) { *pathend = '\0'; if (pathstart >= pathend) pathstart = NULL; } } if ((cellUse->cu_def->cd_flags & CDVISITED) || (pathend == NULL) || (pathstart == NULL) || (*pathstart == '\0')) { sprintf(cstring, "use %s %c%s\n", cellUse->cu_def->cd_name, (cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ', cellUse->cu_id); } else { /* If path starts with home path, then replace with "~" */ /* to make IP semi-portable between home directories */ /* with the same file structure. */ char *homedir = getenv("HOME"); if (!strncmp(cellUse->cu_def->cd_file, homedir, strlen(homedir)) && (*(cellUse->cu_def->cd_file + strlen(homedir)) == '/')) { sprintf(cstring, "use %s %c%s ~%s\n", cellUse->cu_def->cd_name, (cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ', cellUse->cu_id, cellUse->cu_def->cd_file + strlen(homedir)); } else { sprintf(cstring, "use %s %c%s %s\n", cellUse->cu_def->cd_name, (cellUse->cu_flags & CU_LOCKED) ? CULOCKCHAR : ' ', cellUse->cu_id, pathstart); } } FPRINTR(arg->wa_file, cstring); cellUse->cu_def->cd_flags |= CDVISITED; if (pathend != NULL) *pathend = '/'; if ((cellUse->cu_xlo != cellUse->cu_xhi) || (cellUse->cu_ylo != cellUse->cu_yhi)) { sprintf(cstring, "array %d %d %d %d %d %d\n", cellUse->cu_xlo, cellUse->cu_xhi, cellUse->cu_xsep / arg->wa_reducer, cellUse->cu_ylo, cellUse->cu_yhi, cellUse->cu_ysep / arg->wa_reducer); FPRINTR(arg->wa_file,cstring); } sprintf(cstring, "timestamp %d\n", cellUse->cu_def->cd_timestamp); FPRINTR(arg->wa_file,cstring) sprintf(cstring, "transform %d %d %d %d %d %d\n", t->t_a, t->t_b, t->t_c / arg->wa_reducer, t->t_d, t->t_e, t->t_f / arg->wa_reducer); FPRINTR(arg->wa_file,cstring) sprintf(cstring, "box %d %d %d %d\n", b->r_xbot / arg->wa_reducer, b->r_ybot / arg->wa_reducer, b->r_xtop / arg->wa_reducer, b->r_ytop / arg->wa_reducer); FPRINTR(arg->wa_file,cstring) return 0; } /* * ---------------------------------------------------------------------------- * * DBGetTech -- * * Reads the first few lines of a file to find out what technology * it is. * * Results: * The return value is a pointer to a string containing the name * of the technology of the file containing cell cellName. NULL * is returned if the file couldn't be read or isn't in Magic * format. The string is stored locally to this procedure and * will be overwritten on the next call to this procedure. * * Side effects: * None. * * ---------------------------------------------------------------------------- */ char * DBGetTech(cellName) char *cellName; /* Name of cell whose technology * is desired. */ { FILE *f; static char line[512]; char *p; f = PaOpen(cellName, "r", DBSuffix, Path, CellLibPath, (char **) NULL); if (f == NULL) return NULL; p = (char *) NULL; if (dbFgets(line, sizeof line - 1, f) == NULL) goto ret; if (strcmp(line, "magic\n") != 0) goto ret; if (dbFgets(line, sizeof line - 1, f) == NULL) goto ret; if (strncmp(line, "tech ", 5) != 0) goto ret; for (p = &line[5]; (*p != '\n') && (*p != 0); p++) /* Find the newline */; *p = 0; for (p = &line[5]; isspace(*p); p++) /* Find the tech name */; ret: (void) fclose(f); f = NULL; return (p); } /* * ---------------------------------------------------------------------------- * * DBWriteBackup -- * * Save all modified cells to "filename", if specified, or the current * setting of DBbackupFile, which is the current name of the crash * backup file. If "filename" is NULL and no name has been set for * DBbackupFile, then DBbackupFile is generated as a unique filename * in the temp directory. If "filename" is non-null, then DBbackupFile * is set to this name, erasing any previous value. If "filename" is * an empty string, then the DBbackupFile reverts to NULL. * * Results: * TRUE if the backup file was created, FALSE if an error was * encountered. * * Side effects: * Writes cells to disk (in a single file). * Allocates and sets global variable DBbackupFile. * Does NOT clear the modified bits. * * ---------------------------------------------------------------------------- */ bool DBWriteBackup(filename) char *filename; { FILE *f; int fd, pid; char *tempdir; MagWindow *mw; int dbWriteBackupFunc(), dbCheckModifiedCellsFunc(); int flags = CDMODIFIED; int result; /* First check if there are any modified cells that need to be written */ result = DBCellSrDefs(flags, dbCheckModifiedCellsFunc, (ClientData)NULL); if (result == 0) return TRUE; /* Nothing to write */ if (filename == NULL) { if (DBbackupFile == (char *)NULL) { char *doslash, *template; tempdir = getenv("TMPDIR"); if (tempdir == NULL) tempdir = _PATH_TMP; template = (char *)mallocMagic(20 + strlen(tempdir)); pid = (int)getpid(); doslash = (tempdir[strlen(tempdir) - 1] == '/') ? "" : "/"; sprintf(template, "%s/MAG%d.XXXXXX", tempdir, pid); fd = mkstemp(template); if (fd == -1) { TxError("Error generating backup file\n"); freeMagic(template); return FALSE; } close(fd); DBbackupFile = StrDup(&DBbackupFile, template); freeMagic(template); TxPrintf("Created database crash recovery file %s\n", DBbackupFile); } filename = DBbackupFile; } else { if (strlen(filename) == 0) { DBbackupFile = StrDup(&DBbackupFile, (char *)NULL); return TRUE; } DBbackupFile = StrDup(&DBbackupFile, filename); TxPrintf("Created database crash recovery file %s\n", DBbackupFile); } f = fopen(filename, "w"); if (f == NULL) { TxError("Backup file %s cannot be opened for writing.\n", filename); return FALSE; } result = DBCellSrDefs(flags, dbWriteBackupFunc, (ClientData)f); /* End by printing the keyword "end" followed by the cell to load */ /* into the first available window, so that we don't have a default */ /* blank display after crash recovery. */ mw = WindSearchWid(0); if (mw != NULL) fprintf(f, "end %s\n", ((CellUse *)mw->w_surfaceID)->cu_def->cd_name); else fprintf(f, "end\n"); fclose(f); return TRUE; } /* * Filter function used by DBWriteBackup() above. * This function writes a single cell definition to the crash backup * file. Only editable cells whose paint, labels, or subcells have * changed are considered. */ int dbWriteBackupFunc(def, f) CellDef *def; /* Pointer to CellDef to be saved */ FILE *f; /* File to append to */ { char *name = def->cd_file; int result, save_flags; if (def->cd_flags & (CDINTERNAL | CDNOEDIT | CDNOTFOUND)) return 0; else if (!(def->cd_flags & CDAVAILABLE)) return 0; if (name == NULL) name = def->cd_name; fprintf(f, "file %s\n", name); /* Save/restore flags such that the crash recovery file write does */ /* *not* clear the CDMODIFIED, et al., bits */ save_flags = def->cd_flags; def->cd_flags &= ~(CDGETNEWSTAMP); result = DBCellWriteFile(def, f); def->cd_flags = save_flags; return (result == TRUE) ? FALSE : TRUE; } /* * Filter function used by DBWriteBackup() above. * This function checks if at least one cell needs to be written to the * crash backup file. Only editable cells whose paint, labels, or * subcells have changed are considered. */ int dbCheckModifiedCellsFunc(def, cdata) CellDef *def; /* Pointer to CellDef to be saved */ ClientData cdata; /* Unused */ { if (def->cd_flags & (CDINTERNAL | CDNOEDIT | CDNOTFOUND)) return 0; else if (!(def->cd_flags & CDAVAILABLE)) return 0; return 1; }