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