1    	#define _GNU_SOURCE
2    	#include "config.h"
3    	#include "log.h"
4    	#include "nfs_core.h"
5    	#include "nfs4.h"
6    	#include "sal_functions.h"
7    	#include <sys/stat.h>
8    	#include <sys/types.h>
9    	#include <fcntl.h>
10   	#include <ctype.h>
11   	#include <string.h>
12   	#include <netdb.h>
13   	#include "bsd-base64.h"
14   	#include "client_mgr.h"
15   	#include "fsal.h"
16   	#include "recovery_fs.h"
17   	#include <libgen.h>
18   	
19   	static char v4_recov_link[sizeof(NFS_V4_RECOV_ROOT) +
20   				  sizeof(NFS_V4_RECOV_DIR) +
21   				  NI_MAXHOST + 1];
22   	
23   	/*
24   	 * If we have a "legacy" fs driver database, we can allow clients to recover
25   	 * using that. In order to handle it though, we must rename the thing and
26   	 * set symlink for it.
27   	 *
28   	 * Unfortunately this is not atomic, but it should be a one-time thing.
29   	 */
30   	static void legacy_fs_db_migrate(void)
31   	{
32   		int ret;
33   		struct stat st;
34   	
(1) Event fs_check_call: Calling function "lstat" to perform check on "v4_recov_link".
Also see events: [toctou]
35   		ret = lstat(v4_recov_link, &st);
(2) Event cond_true: Condition "!ret", taking true branch.
(3) Event cond_true: Condition "(st.st_mode & 61440) == 16384", taking true branch.
36   		if (!ret && S_ISDIR(st.st_mode)) {
37   			char pathbuf[PATH_MAX];
38   			char *dname;
39   	
40   			/* create empty tmpdir in same parent */
41   			snprintf(pathbuf, sizeof(pathbuf), "%s.XXXXXX", v4_recov_link);
42   	
43   			dname = mkdtemp(pathbuf);
(4) Event cond_false: Condition "!dname", taking false branch.
44   			if (!dname) {
45   				LogEvent(COMPONENT_CLIENTID,
46   					"Failed to create temp file (%s): %s",
47   					pathbuf, strerror(errno));
48   				return;
(5) Event if_end: End of if statement.
49   			}
50   	
(6) Event toctou: Calling function "rename" that uses "v4_recov_link" after a check function. This can cause a time-of-check, time-of-use race condition.
Also see events: [fs_check_call]
51   			ret = rename(v4_recov_link, dname);
52   			if (ret != 0) {
53   				LogEvent(COMPONENT_CLIENTID,
54   					"Failed to rename v4 recovery dir (%s) to (%s): %s",
55   					v4_recov_link, dname, strerror(errno));
56   				return;
57   			}
58   	
59   			ret = symlink(basename(dname), v4_recov_link);
60   			if (ret != 0) {
61   				LogEvent(COMPONENT_CLIENTID,
62   					"Failed to set recoverydir symlink at %s: %s",
63   					dname, strerror(errno));
64   				return;
65   			}
66   		}
67   	}
68   	
69   	static int fs_ng_create_recov_dir(void)
70   	{
71   		int err;
72   		char *newdir;
73   		char host[NI_MAXHOST];
74   	
75   		err = mkdir(NFS_V4_RECOV_ROOT, 0700);
76   		if (err == -1 && errno != EEXIST) {
77   			LogEvent(COMPONENT_CLIENTID,
78   				 "Failed to create v4 recovery dir (%s): %s",
79   				 NFS_V4_RECOV_ROOT, strerror(errno));
80   		}
81   	
82   		snprintf(v4_recov_dir, sizeof(v4_recov_dir), "%s/%s", NFS_V4_RECOV_ROOT,
83   			 NFS_V4_RECOV_DIR);
84   		err = mkdir(v4_recov_dir, 0700);
85   		if (err == -1 && errno != EEXIST) {
86   			LogEvent(COMPONENT_CLIENTID,
87   				 "Failed to create v4 recovery dir(%s): %s",
88   				 v4_recov_dir, strerror(errno));
89   		}
90   	
91   		/* Populate link path string */
92   		if (nfs_param.core_param.clustered) {
93   			snprintf(host, sizeof(host), "node%d", g_nodeid);
94   		} else {
95   			err = gethostname(host, sizeof(host));
96   			if (err) {
97   				LogEvent(COMPONENT_CLIENTID,
98   					 "Failed to gethostname: %s",
99   					 strerror(errno));
100  				return -errno;
101  			}
102  		}
103  	
104  		snprintf(v4_recov_link, sizeof(v4_recov_link), "%s/%s/%s",
105  			 NFS_V4_RECOV_ROOT, NFS_V4_RECOV_DIR, host);
106  	
107  		snprintf(v4_recov_dir, sizeof(v4_recov_dir), "%s.XXXXXX",
108  			 v4_recov_link);
109  	
110  		newdir = mkdtemp(v4_recov_dir);
111  		if (newdir != v4_recov_dir) {
112  			LogEvent(COMPONENT_CLIENTID,
113  				 "Failed to create v4 recovery dir(%s): %s",
114  				 v4_recov_dir, strerror(errno));
115  		}
116  	
117  		legacy_fs_db_migrate();
118  		return 0;
119  	}
120  	
121  	/**
122  	 * @brief Create the client reclaim list from previous database
123  	 *
124  	 * @param[in] dp       Recovery directory
125  	 * @param[in] srcdir   Path to the source directory on failover
126  	 *
127  	 * @return POSIX error codes.
128  	 */
129  	static int fs_ng_read_recov_clids_impl(const char *parent_path,
130  					    char *clid_str,
131  					    add_clid_entry_hook add_clid_entry,
132  					    add_rfh_entry_hook add_rfh_entry)
133  	{
134  		struct dirent *dentp;
135  		DIR *dp;
136  		clid_entry_t *new_ent;
137  		char *sub_path = NULL;
138  		char *build_clid = NULL;
139  		int rc = 0;
140  		int num = 0;
141  		char *ptr, *ptr2;
142  		char temp[10];
143  		int cid_len, len;
144  		int segment_len;
145  		int total_len;
146  		int total_clid_len;
147  	
148  		dp = opendir(parent_path);
149  		if (dp == NULL) {
150  			LogEvent(COMPONENT_CLIENTID,
151  				 "Failed to open v4 recovery dir (%s): %s",
152  				 parent_path, strerror(errno));
153  			return -1;
154  		}
155  	
156  		for (dentp = readdir(dp); dentp != NULL; dentp = readdir(dp)) {
157  			/* don't add '.' and '..' entry */
158  			if (!strcmp(dentp->d_name, ".") || !strcmp(dentp->d_name, ".."))
159  				continue;
160  	
161  			/* Skip names that start with '\x1' as they are files
162  			 * representing revoked file handles
163  			 */
164  			if (dentp->d_name[0] == '\x1')
165  				continue;
166  	
167  			num++;
168  	
169  			/* construct the path by appending the subdir for the
170  			 * next readdir. This recursion keeps reading the
171  			 * subdirectory until reaching the end.
172  			 */
173  			segment_len = strlen(dentp->d_name);
174  			total_len = segment_len + 2 + strlen(parent_path);
175  			sub_path = gsh_malloc(total_len);
176  	
177  			memset(sub_path, 0, total_len);
178  	
179  			strcpy(sub_path, parent_path);
180  			strcat(sub_path, "/");
181  			strncat(sub_path, dentp->d_name, segment_len);
182  			/* keep building the clientid str by recursively */
183  			/* reading the directory structure */
184  			total_clid_len = segment_len + 1;
185  			if (clid_str)
186  				total_clid_len += strlen(clid_str);
187  			build_clid = gsh_calloc(1, total_clid_len);
188  			if (clid_str)
189  				strcpy(build_clid, clid_str);
190  			strncat(build_clid, dentp->d_name, segment_len);
191  	
192  			rc = fs_ng_read_recov_clids_impl(sub_path,
193  						      build_clid,
194  						      add_clid_entry,
195  						      add_rfh_entry);
196  	
197  			/* after recursion, if the subdir has no non-hidden
198  			 * directory this is the end of this clientid str. Add
199  			 * the clientstr to the list.
200  			 */
201  			if (rc == 0) {
202  				/* the clid format is
203  				 * <IP>-(clid-len:long-form-clid-in-string-form)
204  				 * make sure this reconstructed string is valid
205  				 * by comparing clid-len and the actual
206  				 * long-form-clid length in the string. This is
207  				 * to prevent getting incompleted strings that
208  				 * might exist due to program crash.
209  				 */
210  				if (strlen(build_clid) >= PATH_MAX) {
211  					LogEvent(COMPONENT_CLIENTID,
212  						"invalid clid format: %s, too long",
213  						build_clid);
214  					gsh_free(sub_path);
215  					gsh_free(build_clid);
216  					continue;
217  				}
218  				ptr = strchr(build_clid, '(');
219  				if (ptr == NULL) {
220  					LogEvent(COMPONENT_CLIENTID,
221  						 "invalid clid format: %s",
222  						 build_clid);
223  					gsh_free(sub_path);
224  					gsh_free(build_clid);
225  					continue;
226  				}
227  				ptr2 = strchr(ptr, ':');
228  				if (ptr2 == NULL) {
229  					LogEvent(COMPONENT_CLIENTID,
230  						 "invalid clid format: %s",
231  						 build_clid);
232  					gsh_free(sub_path);
233  					gsh_free(build_clid);
234  					continue;
235  				}
236  				len = ptr2-ptr-1;
237  				if (len >= 9) {
238  					LogEvent(COMPONENT_CLIENTID,
239  						 "invalid clid format: %s",
240  						 build_clid);
241  					gsh_free(sub_path);
242  					gsh_free(build_clid);
243  					continue;
244  				}
245  				strlcpy(temp, ptr+1, len+1);
246  				cid_len = atoi(temp);
247  				len = strlen(ptr2);
248  				if ((len == (cid_len+2)) && (ptr2[len-1] == ')')) {
249  					new_ent = add_clid_entry(build_clid);
250  					LogDebug(COMPONENT_CLIENTID,
251  						 "added %s to clid list",
252  						 new_ent->cl_name);
253  				}
254  			}
255  			gsh_free(build_clid);
256  			gsh_free(sub_path);
257  		}
258  	
259  		(void)closedir(dp);
260  	
261  		return num;
262  	}
263  	
264  	static void fs_ng_read_recov_clids_recover(add_clid_entry_hook add_clid_entry,
265  						add_rfh_entry_hook add_rfh_entry)
266  	{
267  		int rc;
268  	
269  		rc = fs_ng_read_recov_clids_impl(v4_recov_link, NULL,
270  					      add_clid_entry,
271  					      add_rfh_entry);
272  		if (rc == -1) {
273  			LogEvent(COMPONENT_CLIENTID,
274  				 "Failed to read v4 recovery dir (%s)",
275  				 v4_recov_link);
276  			return;
277  		}
278  	}
279  	
280  	/**
281  	 * @brief Load clients for recovery, with no lock
282  	 *
283  	 * @param[in] nodeid Node, on takeover
284  	 */
285  	static void fs_ng_read_recov_clids(nfs_grace_start_t *gsp,
286  					  add_clid_entry_hook add_clid_entry,
287  					  add_rfh_entry_hook add_rfh_entry)
288  	{
289  		int rc;
290  		char path[PATH_MAX];
291  	
292  		if (!gsp) {
293  			fs_ng_read_recov_clids_recover(add_clid_entry, add_rfh_entry);
294  			return;
295  		}
296  	
297  		/*
298  		 * FIXME: make the rest of this work
299  		 */
300  		return;
301  	
302  		switch (gsp->event) {
303  		case EVENT_TAKE_NODEID:
304  			snprintf(path, sizeof(path), "%s/%s/node%d",
305  				 NFS_V4_RECOV_ROOT, NFS_V4_RECOV_DIR,
306  				 gsp->nodeid);
307  			break;
308  		default:
309  			LogWarn(COMPONENT_STATE, "Recovery unknown event: %d",
310  					gsp->event);
311  			return;
312  		}
313  	
314  		LogEvent(COMPONENT_CLIENTID, "Recovery for nodeid %d dir (%s)",
315  			 gsp->nodeid, path);
316  	
317  		rc = fs_ng_read_recov_clids_impl(path, NULL,
318  					      add_clid_entry,
319  					      add_rfh_entry);
320  		if (rc == -1) {
321  			LogEvent(COMPONENT_CLIENTID,
322  				 "Failed to read v4 recovery dir (%s)", path);
323  			return;
324  		}
325  	}
326  	
327  	static void fs_ng_swap_recov_dir(void)
328  	{
329  		int ret;
330  		char old_pathbuf[PATH_MAX];
331  		char tmp_link[PATH_MAX];
332  		char *old_path;
333  	
334  		/* save off the old link path so we can do some cleanup afterward */
335  		old_path = realpath(v4_recov_link, old_pathbuf);
336  	
337  		/* Make a new symlink at a temporary location, pointing to new dir */
338  		snprintf(tmp_link, PATH_MAX, "%s.tmp", v4_recov_link);
339  	
340  		/* unlink old symlink, if any */
341  		ret = unlink(tmp_link);
342  		if (ret != 0 && errno != ENOENT) {
343  			LogEvent(COMPONENT_CLIENTID,
344  				 "Unable to remove recoverydir symlink: %s",
345  				 strerror(errno));
346  			return;
347  		}
348  	
349  		/* make a new symlink in a temporary spot */
350  		ret = symlink(basename(v4_recov_dir), tmp_link);
351  		if (ret != 0) {
352  			LogEvent(COMPONENT_CLIENTID,
353  				 "Unable to create recoverydir symlink: %s",
354  				 strerror(errno));
355  			return;
356  		}
357  	
358  		/* rename tmp link into place */
359  		ret = rename(tmp_link, v4_recov_link);
360  		if (ret != 0) {
361  			LogEvent(COMPONENT_CLIENTID,
362  				 "Unable to rename recoverydir symlink: %s",
363  				 strerror(errno));
364  			return;
365  		}
366  	
367  		/* now clean up old path, if any */
368  		if (old_path) {
369  			fs_clean_old_recov_dir_impl(old_path);
370  			rmdir(old_path);
371  		}
372  	}
373  	
374  	static struct nfs4_recovery_backend fs_ng_backend = {
375  		.recovery_init = fs_ng_create_recov_dir,
376  		.end_grace = fs_ng_swap_recov_dir,
377  		.recovery_read_clids = fs_ng_read_recov_clids,
378  		.add_clid = fs_add_clid,
379  		.rm_clid = fs_rm_clid,
380  		.add_revoke_fh = fs_add_revoke_fh,
381  	};
382  	
383  	void fs_ng_backend_init(struct nfs4_recovery_backend **backend)
384  	{
385  		*backend = &fs_ng_backend;
386  	}
387