summaryrefslogtreecommitdiff
path: root/src/findbranch.c
blob: 8c8d70346a91e80a81864445d3804ca259379a51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*
* Description: find a file in a branch
*
* License: BSD-style license
*
* original implementation by Radek Podgorny
*
* License: BSD-style license
* Copyright: Radek Podgorny <radek@podgorny.cz>,
*            Bernd Schubert <bernd-schubert@gmx.de>
*
*
* Details about finding branches:
* 	We begin at the top-level branch to look for a file or directory, in
*	the code usually called "path". If path was not found, we check for a 
*	whiteout-file/directory. If we find a whiteout, we won't check further
*	in lower level branches. If neither path nor the corresponding whiteout
*	have been found, we do the test the next branch and so on.
*	If a file was found, but it is on a read-only branch and a read-write 
*	branch was requested we return EACCESS. On the other hand we ignore
*	directories on read-only branches, since the directory in the higher
*	level branch doesn't prevent the user can later on see the file on the
*	lower level branch - so no problem to create path in the lower level 
*	branch.
*	It also really important the files in higher level branches have 
*	priority, since this is the reason, we can't write to file in a 
*	lower level branch, when another file already exists in a higher
*	level ro-branch - on further access to the file the unmodified
*	file in the ro-branch will visible.
* TODO Terminology:
* 	In the code below we have functions like find_lowest_rw_branch().
* 	IMHO this is rather mis-leading, since it actually will find the
* 	top-level rw-branch. The naming of the function is due to the fact
* 	we begin to cound branches with 0, so 0 is actually the top-level
* 	branch with highest file serving priority.
*/

#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>

#include "unionfs.h"
#include "opts.h"
#include "general.h"
#include "cow.h"
#include "findbranch.h"
#include "string.h"
#include "debug.h"
#include "usyslog.h"

/**
 *  Find a branch that has "path". Return the branch number.
 */
static int find_branch(const char *path, searchflag_t flag) {
	DBG("%s\n", path);

	int i = 0;
	for (i = 0; i < uopt.nbranches; i++) {
		char p[PATHLEN_MAX];
		if (BUILD_PATH(p, uopt.branches[i].path, path)) RETURN(-ENAMETOOLONG);

		struct stat stbuf;
		int res = lstat(p, &stbuf);

		DBG("%s: res = %d\n", p, res);

		if (res == 0) { // path was found
			switch (flag) {
			case RWRO:
				// any path we found is fine
				RETURN(i);
			case RWONLY:
				// we need a rw-branch
				if (uopt.branches[i].rw) RETURN(i);
				break;
			default:
				USYSLOG(LOG_ERR, "%s: Unknown flag %d\n", __func__, flag);
			}
		}

		// check check for a hide file, checking first here is the magic to hide files *below* this level
		res = path_hidden(path, i);
		if (res > 0) {
			// So no path, but whiteout found. No need to search in further branches
			errno = ENOENT;
			RETURN(-1);
		} else if (res < 0) {
			errno = res; // error
			RETURN(-1);
		}
	}

	errno = ENOENT;
	RETURN(-1);
}

/**
 * Find a ro or rw branch.
 */
int find_rorw_branch(const char *path) {
	DBG("%s\n", path);
	RETURN(find_branch(path, RWRO));
}

/**
 * Find a writable branch. If file does not exist, we check for
 * the parent directory.
 * @path 	- the path to find or to copy (with last element cut off)
 * @ rw_hint	- the rw branch to copy to, set to -1 to autodetect it
 */
int __find_rw_branch_cutlast(const char *path, int rw_hint) {
	int branch = find_rw_branch_cow(path);
	DBG("branch = %d\n", branch);

	if (branch >= 0 || (branch < 0 && errno != ENOENT)) RETURN(branch);

	DBG("Check for parent directory\n");

	// So path does not exist, now again, but with dirname only.
	// We MUST NOT call find_rw_branch_cow() // since this function 
	// doesn't work properly for directories.
	char *dname = u_dirname(path);
	if (dname == NULL) {
		errno = ENOMEM;
		RETURN(-1);
	}

	branch = find_rorw_branch(dname);
	DBG("branch = %d\n", branch);

	// No branch found, so path does nowhere exist, error
	if (branch < 0) goto out; 

	// Reminder rw_hint == -1 -> autodetect, we do not care which branch it is
	if (uopt.branches[branch].rw 
	&& (rw_hint == -1 || branch == rw_hint)) goto out;

	if (!uopt.cow_enabled) {
		// So path exists, but is not writable.
		branch = -1;
		errno = EACCES;
		goto out;
	}

	int branch_rw;
	// since it is a directory, any rw-branch is fine
	if (rw_hint == -1)
		branch_rw = find_lowest_rw_branch(uopt.nbranches);
	else
		branch_rw = rw_hint;

	DBG("branch_rw = %d\n", branch_rw);

	// no writable branch found, we must return an error
	if (branch_rw < 0) {
		branch = -1;
		errno = EACCES;
		goto out;
	}

	if (path_create(dname, branch, branch_rw) == 0) branch = branch_rw; // path successfully copied

out:
	free(dname);

	RETURN(branch);
}

/**
 * Call __find_rw_branch_cutlast()
 */
int find_rw_branch_cutlast(const char *path) {
	int rw_hint = -1; // autodetect rw_branch
	RETURN(__find_rw_branch_cutlast(path, rw_hint));
}

/**
 * copy-on-write
 * Find path in a union branch and if this branch is read-only, 
 * copy the file to a read-write branch.
 * NOTE: Don't call this to copy directories. Use path_create() for that!
 *       It will definitely fail, when a ro-branch is on top of a rw-branch
 *       and a directory is to be copied from ro- to rw-branch.
 */
int find_rw_branch_cow(const char *path) {
	DBG("%s\n", path);

	int branch_rorw = find_rorw_branch(path);

	// not found anywhere
	if (branch_rorw < 0) RETURN(-1);

	// the found branch is writable, good!
	if (uopt.branches[branch_rorw].rw) RETURN(branch_rorw);

	// cow is disabled and branch is not writable, so deny write permission
	if (!uopt.cow_enabled) {
		errno = EACCES;
		RETURN(-1);
	}

	int branch_rw = find_lowest_rw_branch(branch_rorw);
	if (branch_rw < 0) {
		// no writable branch found
		errno = EACCES;
		RETURN(-1);
	}

	if (cow_cp(path, branch_rorw, branch_rw)) RETURN(-1);

	// remove a file that might hide the copied file
	remove_hidden(path, branch_rw);

	RETURN(branch_rw);
}

/**
 * Find lowest possible writable branch but only lower than branch_ro.
 */
int find_lowest_rw_branch(int branch_ro) {
	DBG_IN();

	int i = 0;
	for (i = 0; i < branch_ro; i++) {
		if (uopt.branches[i].rw) RETURN(i); // found it it.
	}

	RETURN(-1);
}