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
31static void SharedFileSetOnDetach(dsm_segment *segment, Datum datum);
32static void SharedFileSetPath(char *path, SharedFileSet *fileset, Oid tablespace);
33static void SharedFilePath(char *path, SharedFileSet *fileset, const char *name);
34static 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 */
48void
49SharedFileSetInit(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 */
77void
78SharedFileSetAttach(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 */
104File
105SharedFileSetCreate(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 */
133File
134SharedFileSetOpen(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 */
149bool
150SharedFileSetDelete(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 */
163void
164SharedFileSetDeleteAll(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 */
188static void
189SharedFileSetOnDetach(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 */
213static void
214SharedFileSetPath(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 */
228static Oid
229ChooseTablespace(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 */
239static void
240SharedFilePath(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