#include "internal.hpp" /*------------------------------------------------------------------------*/ // Some more low-level 'C' headers. extern "C" { #include #include #include #include #include #include } /*------------------------------------------------------------------------*/ namespace CaDiCaL { /*------------------------------------------------------------------------*/ // Private constructor. File::File (Internal *i, bool w, int c, FILE * f, const char * n) : #ifndef QUIET internal (i), #endif #if !defined(QUIET) || !defined(NDEBUG) writing (w), #endif close_file (c), file (f), _name (n), _lineno (1), _bytes (0) { (void) i, (void) w; assert (f), assert (n); } /*------------------------------------------------------------------------*/ bool File::exists (const char * path) { struct stat buf; if (stat (path, &buf)) return false; if (access (path, R_OK)) return false; return true; } bool File::writable (const char * path) { int res; if (!path) res = 1; else if (!strcmp (path, "/dev/null")) res = 0; else { if (!*path) res = 2; else { struct stat buf; const char * p = strrchr (path, '/'); if (!p) { if (stat (path, &buf)) res = ((errno == ENOENT) ? 0 : -2); else if (S_ISDIR (buf.st_mode)) res = 3; else res = (access (path, W_OK) ? 4 : 0); } else if (!p[1]) res = 5; else { size_t len = p - path; char * dirname = new char[len + 1]; strncpy (dirname, path, len); dirname[len] = 0; if (stat (dirname, &buf)) res = 6; else if (!S_ISDIR (buf.st_mode)) res = 7; else if (access (dirname, W_OK)) res = 8; else if (stat (path, &buf)) res = (errno == ENOENT) ? 0 : -3; else res = access (path, W_OK) ? 9 : 0; delete [] dirname; } } } return !res; } // These are signatures for supported compressed file types. In 2018 the // SAT Competition was running on StarExec and used internally 'bzip2' // compressed files, but gave them uncompressed to the solver using exactly // the same path (with '.bz2' suffix). Then 'CaDiCaL' tried to read that // actually uncompressed file through 'bzip2', which of course failed. Now // we double check and fall back to reading the file as is, if the signature // does not match after issuing a warning. static int xzsig[] = { 0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00, 0x00, EOF }; static int bz2sig[] = { 0x42, 0x5A, 0x68, EOF }; static int gzsig[] = { 0x1F, 0x8B, EOF }; static int sig7z[] = { 0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, EOF }; static int lzmasig[] = { 0x5D, 0x00, 0x00, 0x80, 0x00, EOF }; bool File::match (Internal * internal, const char * path, const int * sig) { assert (path); FILE * tmp = fopen (path, "r"); if (!tmp) { WARNING ("failed to open '%s' to check signature", path); return false; } bool res = true; for (const int *p = sig; res && (*p != EOF); p++) res = (cadical_getc_unlocked (tmp) == *p); fclose (tmp); if (!res) WARNING ("file type signature check for '%s' failed", path); return res; } size_t File::size (const char * path) { struct stat buf; if (stat (path, &buf)) return 0; return (size_t) buf.st_size; } // Check that 'prg' is in the 'PATH' and thus can be found if executed // through 'popen'. char * File::find (const char * prg) { size_t prglen = strlen (prg); const char * c = getenv ("PATH"); if (!c) return 0;; size_t len = strlen (c); char * e = new char[len + 1]; strcpy (e, c); char * res = 0; for (char * p = e, * q; !res && p < e + len; p = q) { for (q = p; *q && *q != ':'; q++) ; *q++ = 0; size_t pathlen = (q - p) + prglen; char * path = new char [pathlen + 1]; sprintf (path, "%s/%s", p, prg); assert (strlen (path) == pathlen); if (exists (path)) res = path; else delete [] path; } delete [] e; return res; } /*------------------------------------------------------------------------*/ FILE * File::open_file (Internal * internal, const char * path, const char * mode) { (void) internal; return fopen (path, mode); } FILE * File::read_file (Internal * internal, const char * path) { MSG ("opening file to read '%s'", path); return open_file (internal, path, "r"); } FILE * File::write_file (Internal * internal, const char * path) { MSG ("opening file to write '%s'", path); return open_file (internal, path, "w"); } /*------------------------------------------------------------------------*/ FILE * File::open_pipe (Internal * internal, const char * fmt, const char * path, const char * mode) { #ifdef QUIET (void) internal; #endif size_t prglen = 0; while (fmt[prglen] && fmt[prglen] != ' ') prglen++; char * prg = new char [prglen + 1]; strncpy (prg, fmt, prglen); prg[prglen] = 0; char * found = find (prg); if (found) MSG ("found '%s' in path for '%s'", found, prg); if (!found) MSG ("did not find '%s' in path", prg); delete [] prg; if (!found) return 0; delete [] found; char * cmd = new char [strlen (fmt) + strlen (path)]; sprintf (cmd, fmt, path); FILE * res = popen (cmd, mode); delete [] cmd; return res; } FILE * File::read_pipe (Internal * internal, const char * fmt, const int * sig, const char * path) { if (!File::exists (path)) { LOG ("file '%s' does not exist", path); return 0; } LOG ("file '%s' exists", path); if (sig && !File::match (internal, path, sig)) return 0; LOG ("file '%s' matches signature for '%s'", path, fmt); MSG ("opening pipe to read '%s'", path); return open_pipe (internal, fmt, path, "r"); } FILE * File::write_pipe (Internal * internal, const char * fmt, const char * path) { MSG ("opening pipe to write '%s'", path); return open_pipe (internal, fmt, path, "w"); } /*------------------------------------------------------------------------*/ File * File::read (Internal * internal, FILE * f, const char * n) { return new File (internal, false, 0, f, n); } File * File::write (Internal * internal, FILE * f, const char * n) { return new File (internal, true, 0, f, n); } File * File::read (Internal * internal, const char * path) { FILE * file; int close_input = 2; if (has_suffix (path, ".xz")) { file = read_pipe (internal, "xz -c -d %s", xzsig, path); if (!file) goto READ_FILE; } else if (has_suffix (path, ".lzma")) { file = read_pipe (internal, "lzma -c -d %s", lzmasig, path); if (!file) goto READ_FILE; } else if (has_suffix (path, ".bz2")) { file = read_pipe (internal, "bzip2 -c -d %s", bz2sig, path); if (!file) goto READ_FILE; } else if (has_suffix (path, ".gz")) { file = read_pipe (internal, "gzip -c -d %s", gzsig, path); if (!file) goto READ_FILE; } else if (has_suffix (path, ".7z")) { file = read_pipe (internal, "7z x -so %s 2>/dev/null", sig7z, path); if (!file) goto READ_FILE; } else { READ_FILE: file = read_file (internal, path); close_input = 1; } return file ? new File (internal, false, close_input, file, path) : 0; } File * File::write (Internal * internal, const char * path) { FILE * file; int close_input = 2; if (has_suffix (path, ".xz")) file = write_pipe (internal, "xz -c > %s", path); else if (has_suffix (path, ".bz2")) file = write_pipe (internal, "bzip2 -c > %s", path); else if (has_suffix (path, ".gz")) file = write_pipe (internal, "gzip -c > %s", path); else if (has_suffix (path, ".7z")) file = write_pipe (internal, "7z a -an -txz -si -so > %s 2>/dev/null", path); else file = write_file (internal, path), close_input = 1; return file ? new File (internal, true, close_input, file, path) : 0; } void File::close () { assert (file); if (close_file == 0) { VERBOSE (1, "disconnecting from '%s'", name ()); } if (close_file == 1) { VERBOSE (1, "closing file '%s'", name ()); fclose (file); } if (close_file == 2) { VERBOSE (1, "closing pipe command on '%s'", name ()); pclose (file); } file = 0; // mark as closed #ifndef QUIET if (internal->opts.verbose > 1) return; double mb = bytes () / (double) (1 << 20); if (writing) VERBOSE (1, "after writing %" PRId64 " bytes %.1f MB", bytes (), mb); else VERBOSE (1, "after reading %" PRId64 " bytes %.1f MB", bytes (), mb); if (close_file == 2) { int64_t s = size (name ()); double mb = s / (double) (1<<20); if (writing) VERBOSE (1, "deflated to %" PRId64 " bytes %.1f MB by factor %.2f " "(%.2f%% compression)", s, mb, relative (bytes (), s), percent (bytes () - s, bytes ())); else VERBOSE (1, "inflated from %" PRId64 " bytes %.1f MB by factor %.2f " "(%.2f%% compression)", s, mb, relative (bytes (), s), percent (bytes () - s, bytes ())); } #endif } File::~File () { if (file) close (); } }