| 1 | /*------------------------------------------------------------------------- |
| 2 | * |
| 3 | * sharedfileset.c |
| 4 | * Shared temporary file management. |
| 5 | * |
| 6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 7 | * Portions Copyright (c) 1994, Regents of the University of California |
| 8 | * |
| 9 | * IDENTIFICATION |
| 10 | * src/backend/storage/file/sharedfileset.c |
| 11 | * |
| 12 | * SharedFileSets provide a temporary namespace (think directory) so that |
| 13 | * files can be discovered by name, and a shared ownership semantics so that |
| 14 | * shared files survive until the last user detaches. |
| 15 | * |
| 16 | *------------------------------------------------------------------------- |
| 17 | */ |
| 18 | |
| 19 | #include "postgres.h" |
| 20 | |
| 21 | #include <limits.h> |
| 22 | |
| 23 | #include "catalog/pg_tablespace.h" |
| 24 | #include "commands/tablespace.h" |
| 25 | #include "miscadmin.h" |
| 26 | #include "storage/dsm.h" |
| 27 | #include "storage/sharedfileset.h" |
| 28 | #include "utils/builtins.h" |
| 29 | #include "utils/hashutils.h" |
| 30 | |
| 31 | static void SharedFileSetOnDetach(dsm_segment *segment, Datum datum); |
| 32 | static void SharedFileSetPath(char *path, SharedFileSet *fileset, Oid tablespace); |
| 33 | static void SharedFilePath(char *path, SharedFileSet *fileset, const char *name); |
| 34 | static Oid ChooseTablespace(const SharedFileSet *fileset, const char *name); |
| 35 | |
| 36 | /* |
| 37 | * Initialize a space for temporary files that can be opened for read-only |
| 38 | * access by other backends. Other backends must attach to it before |
| 39 | * accessing it. Associate this SharedFileSet with 'seg'. Any contained |
| 40 | * files will be deleted when the last backend detaches. |
| 41 | * |
| 42 | * Files will be distributed over the tablespaces configured in |
| 43 | * temp_tablespaces. |
| 44 | * |
| 45 | * Under the covers the set is one or more directories which will eventually |
| 46 | * be deleted when there are no backends attached. |
| 47 | */ |
| 48 | void |
| 49 | SharedFileSetInit(SharedFileSet *fileset, dsm_segment *seg) |
| 50 | { |
| 51 | static uint32 counter = 0; |
| 52 | |
| 53 | SpinLockInit(&fileset->mutex); |
| 54 | fileset->refcnt = 1; |
| 55 | fileset->creator_pid = MyProcPid; |
| 56 | fileset->number = counter; |
| 57 | counter = (counter + 1) % INT_MAX; |
| 58 | |
| 59 | /* Capture the tablespace OIDs so that all backends agree on them. */ |
| 60 | PrepareTempTablespaces(); |
| 61 | fileset->ntablespaces = |
| 62 | GetTempTablespaces(&fileset->tablespaces[0], |
| 63 | lengthof(fileset->tablespaces)); |
| 64 | if (fileset->ntablespaces == 0) |
| 65 | { |
| 66 | fileset->tablespaces[0] = DEFAULTTABLESPACE_OID; |
| 67 | fileset->ntablespaces = 1; |
| 68 | } |
| 69 | |
| 70 | /* Register our cleanup callback. */ |
| 71 | on_dsm_detach(seg, SharedFileSetOnDetach, PointerGetDatum(fileset)); |
| 72 | } |
| 73 | |
| 74 | /* |
| 75 | * Attach to a set of directories that was created with SharedFileSetInit. |
| 76 | */ |
| 77 | void |
| 78 | SharedFileSetAttach(SharedFileSet *fileset, dsm_segment *seg) |
| 79 | { |
| 80 | bool success; |
| 81 | |
| 82 | SpinLockAcquire(&fileset->mutex); |
| 83 | if (fileset->refcnt == 0) |
| 84 | success = false; |
| 85 | else |
| 86 | { |
| 87 | ++fileset->refcnt; |
| 88 | success = true; |
| 89 | } |
| 90 | SpinLockRelease(&fileset->mutex); |
| 91 | |
| 92 | if (!success) |
| 93 | ereport(ERROR, |
| 94 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
| 95 | errmsg("could not attach to a SharedFileSet that is already destroyed" ))); |
| 96 | |
| 97 | /* Register our cleanup callback. */ |
| 98 | on_dsm_detach(seg, SharedFileSetOnDetach, PointerGetDatum(fileset)); |
| 99 | } |
| 100 | |
| 101 | /* |
| 102 | * Create a new file in the given set. |
| 103 | */ |
| 104 | File |
| 105 | SharedFileSetCreate(SharedFileSet *fileset, const char *name) |
| 106 | { |
| 107 | char path[MAXPGPATH]; |
| 108 | File file; |
| 109 | |
| 110 | SharedFilePath(path, fileset, name); |
| 111 | file = PathNameCreateTemporaryFile(path, false); |
| 112 | |
| 113 | /* If we failed, see if we need to create the directory on demand. */ |
| 114 | if (file <= 0) |
| 115 | { |
| 116 | char tempdirpath[MAXPGPATH]; |
| 117 | char filesetpath[MAXPGPATH]; |
| 118 | Oid tablespace = ChooseTablespace(fileset, name); |
| 119 | |
| 120 | TempTablespacePath(tempdirpath, tablespace); |
| 121 | SharedFileSetPath(filesetpath, fileset, tablespace); |
| 122 | PathNameCreateTemporaryDir(tempdirpath, filesetpath); |
| 123 | file = PathNameCreateTemporaryFile(path, true); |
| 124 | } |
| 125 | |
| 126 | return file; |
| 127 | } |
| 128 | |
| 129 | /* |
| 130 | * Open a file that was created with SharedFileSetCreate(), possibly in |
| 131 | * another backend. |
| 132 | */ |
| 133 | File |
| 134 | SharedFileSetOpen(SharedFileSet *fileset, const char *name) |
| 135 | { |
| 136 | char path[MAXPGPATH]; |
| 137 | File file; |
| 138 | |
| 139 | SharedFilePath(path, fileset, name); |
| 140 | file = PathNameOpenTemporaryFile(path); |
| 141 | |
| 142 | return file; |
| 143 | } |
| 144 | |
| 145 | /* |
| 146 | * Delete a file that was created with SharedFileSetCreate(). |
| 147 | * Return true if the file existed, false if didn't. |
| 148 | */ |
| 149 | bool |
| 150 | SharedFileSetDelete(SharedFileSet *fileset, const char *name, |
| 151 | bool error_on_failure) |
| 152 | { |
| 153 | char path[MAXPGPATH]; |
| 154 | |
| 155 | SharedFilePath(path, fileset, name); |
| 156 | |
| 157 | return PathNameDeleteTemporaryFile(path, error_on_failure); |
| 158 | } |
| 159 | |
| 160 | /* |
| 161 | * Delete all files in the set. |
| 162 | */ |
| 163 | void |
| 164 | SharedFileSetDeleteAll(SharedFileSet *fileset) |
| 165 | { |
| 166 | char dirpath[MAXPGPATH]; |
| 167 | int i; |
| 168 | |
| 169 | /* |
| 170 | * Delete the directory we created in each tablespace. Doesn't fail |
| 171 | * because we use this in error cleanup paths, but can generate LOG |
| 172 | * message on IO error. |
| 173 | */ |
| 174 | for (i = 0; i < fileset->ntablespaces; ++i) |
| 175 | { |
| 176 | SharedFileSetPath(dirpath, fileset, fileset->tablespaces[i]); |
| 177 | PathNameDeleteTemporaryDir(dirpath); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | /* |
| 182 | * Callback function that will be invoked when this backend detaches from a |
| 183 | * DSM segment holding a SharedFileSet that it has created or attached to. If |
| 184 | * we are the last to detach, then try to remove the directories and |
| 185 | * everything in them. We can't raise an error on failures, because this runs |
| 186 | * in error cleanup paths. |
| 187 | */ |
| 188 | static void |
| 189 | SharedFileSetOnDetach(dsm_segment *segment, Datum datum) |
| 190 | { |
| 191 | bool unlink_all = false; |
| 192 | SharedFileSet *fileset = (SharedFileSet *) DatumGetPointer(datum); |
| 193 | |
| 194 | SpinLockAcquire(&fileset->mutex); |
| 195 | Assert(fileset->refcnt > 0); |
| 196 | if (--fileset->refcnt == 0) |
| 197 | unlink_all = true; |
| 198 | SpinLockRelease(&fileset->mutex); |
| 199 | |
| 200 | /* |
| 201 | * If we are the last to detach, we delete the directory in all |
| 202 | * tablespaces. Note that we are still actually attached for the rest of |
| 203 | * this function so we can safely access its data. |
| 204 | */ |
| 205 | if (unlink_all) |
| 206 | SharedFileSetDeleteAll(fileset); |
| 207 | } |
| 208 | |
| 209 | /* |
| 210 | * Build the path for the directory holding the files backing a SharedFileSet |
| 211 | * in a given tablespace. |
| 212 | */ |
| 213 | static void |
| 214 | SharedFileSetPath(char *path, SharedFileSet *fileset, Oid tablespace) |
| 215 | { |
| 216 | char tempdirpath[MAXPGPATH]; |
| 217 | |
| 218 | TempTablespacePath(tempdirpath, tablespace); |
| 219 | snprintf(path, MAXPGPATH, "%s/%s%lu.%u.sharedfileset" , |
| 220 | tempdirpath, PG_TEMP_FILE_PREFIX, |
| 221 | (unsigned long) fileset->creator_pid, fileset->number); |
| 222 | } |
| 223 | |
| 224 | /* |
| 225 | * Sorting hat to determine which tablespace a given shared temporary file |
| 226 | * belongs in. |
| 227 | */ |
| 228 | static Oid |
| 229 | ChooseTablespace(const SharedFileSet *fileset, const char *name) |
| 230 | { |
| 231 | uint32 hash = hash_any((const unsigned char *) name, strlen(name)); |
| 232 | |
| 233 | return fileset->tablespaces[hash % fileset->ntablespaces]; |
| 234 | } |
| 235 | |
| 236 | /* |
| 237 | * Compute the full path of a file in a SharedFileSet. |
| 238 | */ |
| 239 | static void |
| 240 | SharedFilePath(char *path, SharedFileSet *fileset, const char *name) |
| 241 | { |
| 242 | char dirpath[MAXPGPATH]; |
| 243 | |
| 244 | SharedFileSetPath(dirpath, fileset, ChooseTablespace(fileset, name)); |
| 245 | snprintf(path, MAXPGPATH, "%s/%s" , dirpath, name); |
| 246 | } |
| 247 | |