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 | |