Replace opendir/closedir calls throughout the backend with AllocateDir
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 23 Feb 2004 23:03:43 +0000 (23:03 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 23 Feb 2004 23:03:43 +0000 (23:03 +0000)
and FreeDir routines modeled on the existing AllocateFile/FreeFile.
Like the latter, these routines will avoid failing on EMFILE/ENFILE
conditions whenever possible, and will prevent leakage of directory
descriptors if an elog() occurs while one is open.
Also, reduce PANIC to ERROR in MoveOfflineLogs() --- this is not
critical code and there is no reason to force a DB restart on failure.
All per recent trouble report from Olivier Hubaut.

contrib/dbsize/dbsize.c
src/backend/access/transam/slru.c
src/backend/access/transam/xlog.c
src/backend/storage/file/fd.c
src/include/storage/fd.h
src/port/copydir.c

index 0037c14e706da940d266d5dd4499d0e07584a051..7b976822371ff94fddaa55fd8b465edf086cf8aa 100644 (file)
@@ -1,16 +1,15 @@
 #include "postgres.h"
 
 #include <sys/types.h>
-#include <dirent.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
 
 #include "access/heapam.h"
 #include "catalog/catalog.h"
 #include "catalog/namespace.h"
 #include "commands/dbcommands.h"
 #include "fmgr.h"
+#include "storage/fd.h"
 #include "utils/builtins.h"
 
 
@@ -58,7 +57,7 @@ database_size(PG_FUNCTION_ARGS)
 
    dbpath = GetDatabasePath(dbid);
 
-   dirdesc = opendir(dbpath);
+   dirdesc = AllocateDir(dbpath);
    if (!dirdesc)
        ereport(ERROR,
                (errcode_for_file_access(),
@@ -93,7 +92,7 @@ database_size(PG_FUNCTION_ARGS)
        pfree(fullname);
    }
 
-   closedir(dirdesc);
+   FreeDir(dirdesc);
 
    PG_RETURN_INT64(totalsize);
 }
index f0ca7ffb76a83d8842d6373b0270f60692b78b3b..dfa1fba8870a5af8fcc00d97110581cf670696fb 100644 (file)
@@ -6,15 +6,13 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/access/transam/slru.c,v 1.7 2003/09/25 06:57:57 petere Exp $
+ * $Header: /cvsroot/pgsql/src/backend/access/transam/slru.c,v 1.7.2.1 2004/02/23 23:03:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include <fcntl.h>
-#include <dirent.h>
-#include <errno.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
@@ -872,7 +870,7 @@ SlruScanDirectory(SlruCtl ctl, int cutoffPage, bool doDeletions)
    int         segpage;
    char        path[MAXPGPATH];
 
-   cldir = opendir(ctl->Dir);
+   cldir = AllocateDir(ctl->Dir);
    if (cldir == NULL)
        ereport(ERROR,
                (errcode_for_file_access(),
@@ -905,7 +903,7 @@ SlruScanDirectory(SlruCtl ctl, int cutoffPage, bool doDeletions)
        ereport(ERROR,
                (errcode_for_file_access(),
               errmsg("could not read directory \"%s\": %m", ctl->Dir)));
-   closedir(cldir);
+   FreeDir(cldir);
 
    return found;
 }
index f9c202f702308b733fcdf27c23900c0360e791e6..8eb154f7bab5f0a0a0ffcdd23f5bb74b49e7cf10 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/access/transam/xlog.c,v 1.125 2003/09/27 18:16:35 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/access/transam/xlog.c,v 1.125.2.1 2004/02/23 23:03:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include <fcntl.h>
 #include <signal.h>
 #include <unistd.h>
-#include <errno.h>
 #include <sys/stat.h>
 #include <sys/time.h>
-#include <dirent.h>
 
 #include "access/clog.h"
 #include "access/transam.h"
@@ -1617,9 +1615,9 @@ MoveOfflineLogs(uint32 log, uint32 seg, XLogRecPtr endptr)
 
    XLByteToPrevSeg(endptr, endlogId, endlogSeg);
 
-   xldir = opendir(XLogDir);
+   xldir = AllocateDir(XLogDir);
    if (xldir == NULL)
-       ereport(PANIC,
+       ereport(ERROR,
                (errcode_for_file_access(),
            errmsg("could not open transaction log directory \"%s\": %m",
                   XLogDir)));
@@ -1670,11 +1668,11 @@ MoveOfflineLogs(uint32 log, uint32 seg, XLogRecPtr endptr)
        errno = 0;
    }
    if (errno)
-       ereport(PANIC,
+       ereport(ERROR,
                (errcode_for_file_access(),
            errmsg("could not read transaction log directory \"%s\": %m",
                   XLogDir)));
-   closedir(xldir);
+   FreeDir(xldir);
 }
 
 /*
index 329e749b5877b625d5512c5490e214b2da46f2e0..d9a9c07087da6710765c58a9c6ff0212b3bfa5c8 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/storage/file/fd.c,v 1.102.2.1 2004/02/23 20:46:16 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/storage/file/fd.c,v 1.102.2.2 2004/02/23 23:03:43 tgl Exp $
  *
  * NOTES:
  *
@@ -43,8 +43,6 @@
 #include <sys/file.h>
 #include <sys/param.h>
 #include <sys/stat.h>
-#include <dirent.h>
-#include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
 
@@ -92,11 +90,11 @@ int         max_files_per_process = 1000;
 
 /*
  * Maximum number of file descriptors to open for either VFD entries or
- * AllocateFile files.  This is initialized to a conservative value, and
- * remains that way indefinitely in bootstrap or standalone-backend cases.
- * In normal postmaster operation, the postmaster calls set_max_safe_fds()
- * late in initialization to update the value, and that value is then
- * inherited by forked subprocesses.
+ * AllocateFile/AllocateDir operations.  This is initialized to a conservative
+ * value, and remains that way indefinitely in bootstrap or standalone-backend
+ * cases.  In normal postmaster operation, the postmaster calls
+ * set_max_safe_fds() late in initialization to update the value, and that
+ * value is then inherited by forked subprocesses.
  *
  * Note: the value of max_files_per_process is taken into account while
  * setting this variable, and so need not be tested separately.
@@ -164,6 +162,17 @@ static int nfile = 0;
 static int numAllocatedFiles = 0;
 static FILE *allocatedFiles[MAX_ALLOCATED_FILES];
 
+/*
+ * List of <dirent.h> DIRs opened with AllocateDir.
+ *
+ * Since we don't have heavy use of AllocateDir, it seems OK to put a pretty
+ * small maximum limit on the number of simultaneously allocated dirs.
+ */
+#define MAX_ALLOCATED_DIRS  10
+
+static int numAllocatedDirs = 0;
+static DIR *allocatedDirs[MAX_ALLOCATED_DIRS];
+
 /*
  * Number of temporary files opened during the current session;
  * this is used in generation of tempfile names.
@@ -494,7 +503,7 @@ LruInsert(File file)
 
    if (FileIsNotOpen(file))
    {
-       while (nfile + numAllocatedFiles >= max_safe_fds)
+       while (nfile + numAllocatedFiles + numAllocatedDirs >= max_safe_fds)
        {
            if (!ReleaseLruFile())
                break;
@@ -753,7 +762,7 @@ fileNameOpenFile(FileName fileName,
    file = AllocateVfd();
    vfdP = &VfdCache[file];
 
-   while (nfile + numAllocatedFiles >= max_safe_fds)
+   while (nfile + numAllocatedFiles + numAllocatedDirs >= max_safe_fds)
    {
        if (!ReleaseLruFile())
            break;
@@ -1104,8 +1113,8 @@ AllocateFile(char *name, char *mode)
     * looping.
     */
    if (numAllocatedFiles >= MAX_ALLOCATED_FILES ||
-       numAllocatedFiles >= max_safe_fds - 1)
-       elog(ERROR, "too many private FDs demanded");
+       numAllocatedFiles + numAllocatedDirs >= max_safe_fds - 1)
+       elog(ERROR, "too many private files demanded");
 
 TryAgain:
    if ((file = fopen(name, mode)) != NULL)
@@ -1154,6 +1163,86 @@ FreeFile(FILE *file)
    fclose(file);
 }
 
+
+/*
+ * Routines that want to use <dirent.h> (ie, DIR*) should use AllocateDir
+ * rather than plain opendir().  This lets fd.c deal with freeing FDs if
+ * necessary to open the directory, and with closing it after an elog.
+ * When done, call FreeDir rather than closedir.
+ *
+ * Ideally this should be the *only* direct call of opendir() in the backend.
+ */
+DIR *
+AllocateDir(const char *dirname)
+{
+   DIR    *dir;
+
+   DO_DB(elog(LOG, "AllocateDir: Allocated %d", numAllocatedDirs));
+
+   /*
+    * The test against MAX_ALLOCATED_DIRS prevents us from overflowing
+    * allocatedDirs[]; the test against max_safe_fds prevents AllocateDir
+    * from hogging every one of the available FDs, which'd lead to infinite
+    * looping.
+    */
+   if (numAllocatedDirs >= MAX_ALLOCATED_DIRS ||
+       numAllocatedDirs + numAllocatedFiles >= max_safe_fds - 1)
+       elog(ERROR, "too many private dirs demanded");
+
+TryAgain:
+   if ((dir = opendir(dirname)) != NULL)
+   {
+       allocatedDirs[numAllocatedDirs] = dir;
+       numAllocatedDirs++;
+       return dir;
+   }
+
+   if (errno == EMFILE || errno == ENFILE)
+   {
+       int         save_errno = errno;
+
+       ereport(LOG,
+               (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
+             errmsg("out of file descriptors: %m; release and retry")));
+       errno = 0;
+       if (ReleaseLruFile())
+           goto TryAgain;
+       errno = save_errno;
+   }
+
+   return NULL;
+}
+
+/*
+ * Close a directory opened with AllocateDir.
+ *
+ * Note we do not check closedir's return value --- it is up to the caller
+ * to handle close errors.
+ */
+int
+FreeDir(DIR *dir)
+{
+   int         i;
+
+   DO_DB(elog(LOG, "FreeDir: Allocated %d", numAllocatedDirs));
+
+   /* Remove dir from list of allocated dirs, if it's present */
+   for (i = numAllocatedDirs; --i >= 0;)
+   {
+       if (allocatedDirs[i] == dir)
+       {
+           numAllocatedDirs--;
+           allocatedDirs[i] = allocatedDirs[numAllocatedDirs];
+           break;
+       }
+   }
+   if (i < 0)
+       elog(WARNING, "dir passed to FreeDir was not obtained from AllocateDir");
+
+   return closedir(dir);
+}
+
+
 /*
  * closeAllVfds
  *
@@ -1210,7 +1299,7 @@ AtProcExit_Files(void)
  * exiting. If that's the case, we should remove all temporary files; if
  * that's not the case, we are being called for transaction commit/abort
  * and should only remove transaction-local temp files.  In either case,
- * also clean up "allocated" stdio files.
+ * also clean up "allocated" stdio files and dirs.
  */
 static void
 CleanupTempFiles(bool isProcExit)
@@ -1239,6 +1328,9 @@ CleanupTempFiles(bool isProcExit)
 
    while (numAllocatedFiles > 0)
        FreeFile(allocatedFiles[0]);
+
+   while (numAllocatedDirs > 0)
+       FreeDir(allocatedDirs[0]);
 }
 
 
@@ -1270,7 +1362,7 @@ RemovePgTempFiles(void)
     * files.
     */
    snprintf(db_path, sizeof(db_path), "%s/base", DataDir);
-   if ((db_dir = opendir(db_path)) != NULL)
+   if ((db_dir = AllocateDir(db_path)) != NULL)
    {
        while ((db_de = readdir(db_dir)) != NULL)
        {
@@ -1282,7 +1374,7 @@ RemovePgTempFiles(void)
                     "%s/%s/%s",
                     db_path, db_de->d_name,
                     PG_TEMP_FILES_DIR);
-           if ((temp_dir = opendir(temp_path)) != NULL)
+           if ((temp_dir = AllocateDir(temp_path)) != NULL)
            {
                while ((temp_de = readdir(temp_dir)) != NULL)
                {
@@ -1305,9 +1397,9 @@ RemovePgTempFiles(void)
                             "unexpected file found in temporary-files directory: \"%s\"",
                             rm_path);
                }
-               closedir(temp_dir);
+               FreeDir(temp_dir);
            }
        }
-       closedir(db_dir);
+       FreeDir(db_dir);
    }
 }
index 537fec5af9da1355609a6a5201e80b2d4e25f5c5..11226901f13d2b9c63f5f8a75c941367f5b1d11c 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: fd.h,v 1.39.4.1 2004/02/23 20:46:16 tgl Exp $
+ * $Id: fd.h,v 1.39.4.2 2004/02/23 23:03:43 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
  * use FreeFile, not fclose, to close it.  AVOID using stdio for files
  * that you intend to hold open for any length of time, since there is
  * no way for them to share kernel file descriptors with other files.
+ *
+ * Likewise, use AllocateDir/FreeDir, not opendir/closedir, to allocate
+ * open directories (DIR*).
  */
 #ifndef FD_H
 #define FD_H
 
+#include <dirent.h>
+
+
 /*
  * FileSeek uses the standard UNIX lseek(2) flags.
  */
@@ -65,7 +71,11 @@ extern int   FileTruncate(File file, long offset);
 
 /* Operations that allow use of regular stdio --- USE WITH CAUTION */
 extern FILE *AllocateFile(char *name, char *mode);
-extern void FreeFile(FILE *);
+extern void FreeFile(FILE *file);
+
+/* Operations to allow use of the <dirent.h> library routines */
+extern DIR *AllocateDir(const char *dirname);
+extern int FreeDir(DIR *dir);
 
 /* If you've really really gotta have a plain kernel FD, use this */
 extern int BasicOpenFile(FileName fileName, int fileFlags, int fileMode);
index 29fbad8e259efcae930fd27ca84edfd784ca086c..97677576b84815a6d379cab660c514fc4ef69ed9 100644 (file)
@@ -3,16 +3,16 @@
  * it requires a Window handle which prevents it from working when invoked
  * as a service.
  *
- * $Header: /cvsroot/pgsql/src/port/Attic/copydir.c,v 1.5 2003/09/10 20:12:01 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/port/Attic/copydir.c,v 1.5.2.1 2004/02/23 23:03:43 tgl Exp $
  */
 
 #include "postgres.h"
 
+#include "storage/fd.h"
+
 #undef mkdir                   /* no reason to use that macro because we
                                 * ignore the 2nd arg */
 
-#include <dirent.h>
-
 
 /*
  * copydir: copy a directory (we only need to go one level deep)
@@ -37,7 +37,7 @@ copydir(char *fromdir, char *todir)
                 errmsg("could not create directory \"%s\": %m", todir)));
        return -1;
    }
-   xldir = opendir(fromdir);
+   xldir = AllocateDir(fromdir);
    if (xldir == NULL)
    {
        ereport(WARNING,
@@ -55,11 +55,11 @@ copydir(char *fromdir, char *todir)
            ereport(WARNING,
                    (errcode_for_file_access(),
                     errmsg("could not copy file \"%s\": %m", fromfl)));
-           closedir(xldir);
+           FreeDir(xldir);
            return -1;
        }
    }
 
-   closedir(xldir);
+   FreeDir(xldir);
    return 0;
 }