From 85d8ad6622ac562bb28930ec0496e2b1a45c698f Mon Sep 17 00:00:00 2001 From: Tim Edwards Date: Thu, 5 May 2022 17:40:56 -0400 Subject: [PATCH] Added the capability to handle compressed GDS files through the use of systems calls to "gzip" and "gunzip". A compressed GDS file can be made simply by doing "gds write .gds.gz", and can be read simply by doing "gds read .gds.gz". Names of compressed files can be put in the GDS_FILE property of a cell. --- VERSION | 2 +- calma/CalmaWrite.c | 48 ++++++++++++++++++++- commands/CmdCD.c | 92 +++++++++++++++++++++++++++++++++++++++- scripts/configure | 99 ++++++++++++++++++++++++++++++++++++++++++++ scripts/configure.in | 17 ++++++++ utils/utils.h | 1 + 6 files changed, 254 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 643711b8..8465a3b5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.3.298 +8.3.299 diff --git a/calma/CalmaWrite.c b/calma/CalmaWrite.c index 7657c046..d06325ee 100644 --- a/calma/CalmaWrite.c +++ b/calma/CalmaWrite.c @@ -943,12 +943,43 @@ calmaProcessDef(def, outf, do_library) size_t defsize, numbytes; off_t cellstart, cellend, structstart; dlong cval; + int namelen; + char *modName; FILE *fi; + /* Handle compressed files */ + + modName = filename; + namelen = strlen(filename); + if ((namelen > 4) && !strcmp(filename + namelen - 3, ".gz")) + { + char *sysCmd, sptr; + + sptr = strrchr(filename, '/'); + if (sptr == NULL) + sptr = filename; + else + sptr++; + + modName = StrDup((char **)NULL, sptr); + *(modName + strlen(modName) - 3) = '\0'; + + sysCmd = mallocMagic(18 + namelen + strlen(modName)); + sprinf(sysCmd, "gunzip -c -k %s > %s", filename, modName); + if (system(sysCmd) != 0) + { + /* File didn't uncompress. Go back to original name, + * although that will probably fail and raise an error. + */ + freeMagic(modName); + modName = filename; + } + } + /* Use PaOpen() so the paths searched are the same as were */ /* searched to find the .mag file that indicated this GDS file. */ - fi = PaOpen(filename, "r", "", Path, CellLibPath, &retfilename); + fi = PaOpen(modName, "r", "", Path, CellLibPath, &retfilename); if (fi == NULL) { /* This is a rare error, but if the subcell is inside */ @@ -964,7 +995,9 @@ calmaProcessDef(def, outf, do_library) TxError("Calma output error: Can't find GDS file \"%s\" " "for vendor cell \"%s\". It will not be output.\n", - filename, def->cd_name); + modName, def->cd_name); + + if (modName != filename) freeMagic(modName); if (CalmaAllowUndefined) return 0; @@ -1112,6 +1145,17 @@ calmaProcessDef(def, outf, do_library) } fclose(fi); + if (modName != filename) + { + /* Remove the uncompressed file */ + if (unlink(modName) != 0) + { + TxError("Error attempting to delete uncompressed file \"%s\"\n", + modName); + } + freeMagic(modName); + } + /* Mark the definition as vendor GDS so that magic doesn't */ /* try to generate subcell interaction or array interaction */ /* paint for it. */ diff --git a/commands/CmdCD.c b/commands/CmdCD.c index 114aacb9..44189c47 100644 --- a/commands/CmdCD.c +++ b/commands/CmdCD.c @@ -125,6 +125,9 @@ CmdCalma(w, cmd) char **msg, *namep, *dotptr; CellDef *rootDef; FILE *f; + int namelen; + bool gzipd; + char *realName, *saveName, *modName; extern int CalmaFlattenLimit; @@ -870,11 +873,54 @@ CmdCalma(w, cmd) case CALMA_READ: if (cmd->tx_argc != 3) goto wrongNumArgs; + /* Check for compressed files, and uncompress them. */ + /* Always uncompress into the current working directory */ + /* because the original compressed file might be in an */ + /* unwriteable directory. */ + + modName = cmd->tx_argv[2]; + namelen = strlen(modName); + if ((namelen > 4) && !strcmp(modName + namelen - 3, ".gz")) + { + char *sysCmd, *sptr; + + /* First try to open the uncompressed file name. If */ + /* the file exists, then don't try to uncompress on top */ + /* of it, but just fail. */ + + sptr = strrchr(modName, '/'); + if (sptr == NULL) + sptr = modName; + else + sptr++; + modName = StrDup((char **)NULL, sptr); + *(modName + strlen(modName) - 3) = '\0'; + if ((f = PaOpen(modName, "r", NULL, Path, + (char *)NULL, NULL)) != (FILE *)NULL) + { + fclose(f); + TxError("Uncompressed file \"%s\" already exists!\n", modName); + freeMagic(modName); + return; + } + + sysCmd = mallocMagic(18 + namelen + strlen(modName)); + /* Note: "-k" keeps the original compressed file */ + TxPrintf("Uncompressing file \"%s\".\n", cmd->tx_argv[2]); + sprintf(sysCmd, "gunzip -c -k %s > %s", cmd->tx_argv[2], modName); + if (system(sysCmd) != 0) + { + freeMagic(modName); + modName = NULL; + } + freeMagic(sysCmd); + } + /* Check for various common file extensions, including */ /* no extension (as-is), ".gds", ".gds2", and ".strm". */ for (ext = 0; gdsExts[ext] != NULL; ext++) - if ((f = PaOpen(cmd->tx_argv[2], "r", gdsExts[ext], Path, + if ((f = PaOpen(modName, "r", gdsExts[ext], Path, (char *) NULL, &namep)) != (FILE *)NULL) break; @@ -887,6 +933,18 @@ CmdCalma(w, cmd) } CalmaReadFile(f, namep); (void) fclose(f); + if (modName != cmd->tx_argv[2]) + { + /* A gzipped file was read and now the uncompressed */ + /* file that was generated should be removed. */ + + if (unlink(namep) != 0) + { + TxError("Error attempting to delete uncompressed file \"%s\"\n", + namep); + } + freeMagic(modName); + } return; } @@ -898,8 +956,20 @@ CmdCalma(w, cmd) outputCalma: dotptr = strrchr(namep, '.'); + /* Check for additional ".gz" extension */ + if (!strcmp(dotptr, ".gz")) + { + gzipd = TRUE; + *dotptr = '\0'; + dotptr = strrchr(namep, '.'); + } + else + gzipd = FALSE; + f = PaOpen(namep, "w", (dotptr == NULL) ? ".gds" : "", ".", - (char *) NULL, (char **) NULL); + (char *) NULL, (char **)&realName); + if (gzipd) + saveName = StrDup((char **)NULL, realName); if (f == (FILE *) NULL) { @@ -914,6 +984,24 @@ outputCalma: TxError("File may be incompletely written.\n"); } (void) fclose(f); + + if (gzipd) + { + char *sysCmd; + sysCmd = mallocMagic(16 + strlen(saveName)); + TxPrintf("Compressing file \"%s\"\n", saveName); + sprintf(sysCmd, "gzip -n --best %s", saveName); + + /* Note that without additional arguments, "gzip" will wholly */ + /* replace the uncompressed file with the compressed one. */ + + if (system(sysCmd) != 0) + { + TxError("Failed to compress file \"%s\"\n", saveName); + } + freeMagic(sysCmd); + freeMagic(saveName); + } } #endif diff --git a/scripts/configure b/scripts/configure index ddf28479..9ab0f802 100755 --- a/scripts/configure +++ b/scripts/configure @@ -672,6 +672,8 @@ X_PRE_LIBS X_CFLAGS XMKMF PYTHON3 +GUNZIP +GZIP CSH GCORE EGREP @@ -5327,6 +5329,103 @@ if test "x${CSH}" = "xno"; then as_fn_error $? "cannot find /bin/csh---cannot compile!" "$LINENO" 5 fi +# Extract the first word of "gzip", so it can be a program name with args. +set dummy gzip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_GZIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $GZIP in + [\\/]* | ?:[\\/]*) + ac_cv_path_GZIP="$GZIP" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_GZIP="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_GZIP" && ac_cv_path_GZIP="no" + ;; +esac +fi +GZIP=$ac_cv_path_GZIP +if test -n "$GZIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GZIP" >&5 +$as_echo "$GZIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +if test "x${GZIP}" = "xno"; then + as_fn_error $? "cannot find gzip---will not be able to compress large files" "$LINENO" 5 +else + $as_echo "#define HAVE_GZIP 1" >>confdefs.h + +fi + +# Extract the first word of "gunzip", so it can be a program name with args. +set dummy gunzip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_GUNZIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $GUNZIP in + [\\/]* | ?:[\\/]*) + ac_cv_path_GUNZIP="$GUNZIP" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_GUNZIP="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_path_GUNZIP" && ac_cv_path_GUNZIP="no" + ;; +esac +fi +GUNZIP=$ac_cv_path_GUNZIP +if test -n "$GUNZIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $GUNZIP" >&5 +$as_echo "$GUNZIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +if test "x${GUNZIP}" = "xno"; then + as_fn_error $? "cannot find gunzip---will not be able to automatically uncompress files" "$LINENO" 5 +else + $as_echo "#define HAVE_GUNZIP 1" >>confdefs.h + +fi + + # Extract the first word of "python3", so it can be a program name with args. set dummy python3; ac_word=$2 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 diff --git a/scripts/configure.in b/scripts/configure.in index 2d0558b0..ddd4a9d8 100644 --- a/scripts/configure.in +++ b/scripts/configure.in @@ -285,6 +285,23 @@ if test "x${CSH}" = "xno"; then AC_MSG_ERROR([cannot find /bin/csh---cannot compile!]) fi +dnl gzip and gunzip are used for file compression and uncompression of +dnl large files (e.g., GDS) +AC_PATH_PROG([GZIP], [gzip], [no]) +if test "x${GZIP}" = "xno"; then + AC_MSG_ERROR([cannot find gzip---will not be able to compress large files]) +else + AC_DEFINE(HAVE_GZIP) +fi + +AC_PATH_PROG([GUNZIP], [gunzip], [no]) +if test "x${GUNZIP}" = "xno"; then + AC_MSG_ERROR([cannot find gunzip---will not be able to automatically uncompress files]) +else + AC_DEFINE(HAVE_GUNZIP) +fi + + dnl Python3 is preferred for running the preprocessor script dnl but CPP can be used instead. AC_PATH_PROG([PYTHON3], [python3], [no]) diff --git a/utils/utils.h b/utils/utils.h index 3f1b481c..6f69266c 100644 --- a/utils/utils.h +++ b/utils/utils.h @@ -44,6 +44,7 @@ extern int LookupStructFull(); extern int PaExpand(char **, char **, int); extern char *nextName(); extern FILE *PaOpen(char *, char *, char *, char *, char *, char **); +extern FILE *PaZOpen(char *, char *, char *, char *, char *, char **, bool *); extern FILE *PaLockOpen(char *, char *, char *, char *, char *, char **, bool *); extern char *StrDup(char **, char *); extern int Match();