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
SharedFileSetInit(SharedFileSet * fileset,dsm_segment * seg)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 /* If the GUC is empty, use current database's default tablespace */
67 fileset->tablespaces[0] = MyDatabaseTableSpace;
68 fileset->ntablespaces = 1;
69 }
70 else
71 {
72 int i;
73
74 /*
75 * An entry of InvalidOid means use the default tablespace for the
76 * current database. Replace that now, to be sure that all users of
77 * the SharedFileSet agree on what to do.
78 */
79 for (i = 0; i < fileset->ntablespaces; i++)
80 {
81 if (fileset->tablespaces[i] == InvalidOid)
82 fileset->tablespaces[i] = MyDatabaseTableSpace;
83 }
84 }
85
86 /* Register our cleanup callback. */
87 on_dsm_detach(seg, SharedFileSetOnDetach, PointerGetDatum(fileset));
88 }
89
90 /*
91 * Attach to a set of directories that was created with SharedFileSetInit.
92 */
93 void
SharedFileSetAttach(SharedFileSet * fileset,dsm_segment * seg)94 SharedFileSetAttach(SharedFileSet *fileset, dsm_segment *seg)
95 {
96 bool success;
97
98 SpinLockAcquire(&fileset->mutex);
99 if (fileset->refcnt == 0)
100 success = false;
101 else
102 {
103 ++fileset->refcnt;
104 success = true;
105 }
106 SpinLockRelease(&fileset->mutex);
107
108 if (!success)
109 ereport(ERROR,
110 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
111 errmsg("could not attach to a SharedFileSet that is already destroyed")));
112
113 /* Register our cleanup callback. */
114 on_dsm_detach(seg, SharedFileSetOnDetach, PointerGetDatum(fileset));
115 }
116
117 /*
118 * Create a new file in the given set.
119 */
120 File
SharedFileSetCreate(SharedFileSet * fileset,const char * name)121 SharedFileSetCreate(SharedFileSet *fileset, const char *name)
122 {
123 char path[MAXPGPATH];
124 File file;
125
126 SharedFilePath(path, fileset, name);
127 file = PathNameCreateTemporaryFile(path, false);
128
129 /* If we failed, see if we need to create the directory on demand. */
130 if (file <= 0)
131 {
132 char tempdirpath[MAXPGPATH];
133 char filesetpath[MAXPGPATH];
134 Oid tablespace = ChooseTablespace(fileset, name);
135
136 TempTablespacePath(tempdirpath, tablespace);
137 SharedFileSetPath(filesetpath, fileset, tablespace);
138 PathNameCreateTemporaryDir(tempdirpath, filesetpath);
139 file = PathNameCreateTemporaryFile(path, true);
140 }
141
142 return file;
143 }
144
145 /*
146 * Open a file that was created with SharedFileSetCreate(), possibly in
147 * another backend.
148 */
149 File
SharedFileSetOpen(SharedFileSet * fileset,const char * name)150 SharedFileSetOpen(SharedFileSet *fileset, const char *name)
151 {
152 char path[MAXPGPATH];
153 File file;
154
155 SharedFilePath(path, fileset, name);
156 file = PathNameOpenTemporaryFile(path);
157
158 return file;
159 }
160
161 /*
162 * Delete a file that was created with SharedFileSetCreate().
163 * Return true if the file existed, false if didn't.
164 */
165 bool
SharedFileSetDelete(SharedFileSet * fileset,const char * name,bool error_on_failure)166 SharedFileSetDelete(SharedFileSet *fileset, const char *name,
167 bool error_on_failure)
168 {
169 char path[MAXPGPATH];
170
171 SharedFilePath(path, fileset, name);
172
173 return PathNameDeleteTemporaryFile(path, error_on_failure);
174 }
175
176 /*
177 * Delete all files in the set.
178 */
179 void
SharedFileSetDeleteAll(SharedFileSet * fileset)180 SharedFileSetDeleteAll(SharedFileSet *fileset)
181 {
182 char dirpath[MAXPGPATH];
183 int i;
184
185 /*
186 * Delete the directory we created in each tablespace. Doesn't fail
187 * because we use this in error cleanup paths, but can generate LOG
188 * message on IO error.
189 */
190 for (i = 0; i < fileset->ntablespaces; ++i)
191 {
192 SharedFileSetPath(dirpath, fileset, fileset->tablespaces[i]);
193 PathNameDeleteTemporaryDir(dirpath);
194 }
195 }
196
197 /*
198 * Callback function that will be invoked when this backend detaches from a
199 * DSM segment holding a SharedFileSet that it has created or attached to. If
200 * we are the last to detach, then try to remove the directories and
201 * everything in them. We can't raise an error on failures, because this runs
202 * in error cleanup paths.
203 */
204 static void
SharedFileSetOnDetach(dsm_segment * segment,Datum datum)205 SharedFileSetOnDetach(dsm_segment *segment, Datum datum)
206 {
207 bool unlink_all = false;
208 SharedFileSet *fileset = (SharedFileSet *) DatumGetPointer(datum);
209
210 SpinLockAcquire(&fileset->mutex);
211 Assert(fileset->refcnt > 0);
212 if (--fileset->refcnt == 0)
213 unlink_all = true;
214 SpinLockRelease(&fileset->mutex);
215
216 /*
217 * If we are the last to detach, we delete the directory in all
218 * tablespaces. Note that we are still actually attached for the rest of
219 * this function so we can safely access its data.
220 */
221 if (unlink_all)
222 SharedFileSetDeleteAll(fileset);
223 }
224
225 /*
226 * Build the path for the directory holding the files backing a SharedFileSet
227 * in a given tablespace.
228 */
229 static void
SharedFileSetPath(char * path,SharedFileSet * fileset,Oid tablespace)230 SharedFileSetPath(char *path, SharedFileSet *fileset, Oid tablespace)
231 {
232 char tempdirpath[MAXPGPATH];
233
234 TempTablespacePath(tempdirpath, tablespace);
235 snprintf(path, MAXPGPATH, "%s/%s%lu.%u.sharedfileset",
236 tempdirpath, PG_TEMP_FILE_PREFIX,
237 (unsigned long) fileset->creator_pid, fileset->number);
238 }
239
240 /*
241 * Sorting hat to determine which tablespace a given shared temporary file
242 * belongs in.
243 */
244 static Oid
ChooseTablespace(const SharedFileSet * fileset,const char * name)245 ChooseTablespace(const SharedFileSet *fileset, const char *name)
246 {
247 uint32 hash = hash_any((const unsigned char *) name, strlen(name));
248
249 return fileset->tablespaces[hash % fileset->ntablespaces];
250 }
251
252 /*
253 * Compute the full path of a file in a SharedFileSet.
254 */
255 static void
SharedFilePath(char * path,SharedFileSet * fileset,const char * name)256 SharedFilePath(char *path, SharedFileSet *fileset, const char *name)
257 {
258 char dirpath[MAXPGPATH];
259
260 SharedFileSetPath(dirpath, fileset, ChooseTablespace(fileset, name));
261 snprintf(path, MAXPGPATH, "%s/%s", dirpath, name);
262 }
263