summaryrefslogtreecommitdiff
path: root/src/opts.c
blob: 74f9625df5c89599beaca1dc62364cfe5779a97d (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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/*
*  C Implementation: opts.c
*
* Option parser
*
* License: BSD-style license
* Copyright: Radek Podgorny <radek@podgorny.cz>,
*            Bernd Schubert <bernd-schubert@gmx.de>
*/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "conf.h"
#include "opts.h"
#include "stats.h"
#include "version.h"
#include "string.h"

/**
 * Set the maximum number of open files
 */
int set_max_open_files(const char *arg)
{
	struct rlimit rlim;
	unsigned long max_files;
	if (sscanf(arg, "max_files=%ld\n", &max_files) != 1) {
		fprintf(stderr, "%s Converting %s to number failed, aborting!\n",
			__func__, arg);
		exit(1);
	}
	rlim.rlim_cur = max_files;
	rlim.rlim_max = max_files;
	if (setrlimit(RLIMIT_NOFILE, &rlim)) {
		fprintf(stderr, "%s: Setting the maximum number of files failed: %s\n",
			__func__, strerror(errno));
		exit(1);
	}

	return 0;
}


uopt_t uopt;

void uopt_init() {
	memset(&uopt, 0, sizeof(uopt_t)); // initialize options with zeros first

	// so far nothing more required, so far initializing with zeros sufficient
}

/**
 * Take a relative path as argument and return the absolute path by using the
 * current working directory. The return string is malloc'ed with this function.
 */
char *make_absolute(char *relpath) {
	// Already an absolute path
	if (*relpath == '/') return relpath;

	char cwd[PATHLEN_MAX];
	if (!getcwd(cwd, PATHLEN_MAX)) {
		perror("Unable to get current working directory");
		return NULL;
	}

	size_t cwdlen = strlen(cwd);
	if (!cwdlen) {
		fprintf(stderr, "Zero-sized length of CWD!\n");
		return NULL;
	}

	// 2 due to: +1 for '/' between cwd and relpath
	//           +1 for trailing '/'
	int abslen = cwdlen + strlen(relpath) + 2;
	if (abslen > PATHLEN_MAX) {
		fprintf(stderr, "Absolute path too long!\n");
		return NULL;
	}

	char *abspath = malloc(abslen);
	if (abspath == NULL) {
		fprintf(stderr, "%s: malloc failed\n", __func__);
		exit(1); // still at early stage, we can abort
	}
	
	// the ending required slash is added later by add_trailing_slash()
	snprintf(abspath, abslen, "%s/%s", cwd, relpath);

	return abspath;
}

/**
 * Add a trailing slash at the end of a branch. So functions using this
 * path don't have to care about this slash themselves.
 **/
char *add_trailing_slash(char *path) {
	int len = strlen(path);
	if (path[len - 1] == '/') {
		return path; // no need to add a slash, already there
	}

	path = realloc(path, len + 2); // +1 for '/' and +1 for '\0'
	if (!path) {
		fprintf(stderr, "%s: realloc() failed, aborting\n", __func__);
		exit(1); // still very early stage, we can abort here
	}

	strcat(path, "/");
	return path;
}

/**
 * Add a given branch and its options to the array of available branches.
 * example branch string "branch1=RO" or "/path/path2=RW"
 */
static void add_branch(char *branch) {
	uopt.branches = realloc(uopt.branches, (uopt.nbranches+1) * sizeof(branch_entry_t));
	if (uopt.branches == NULL) {
		fprintf(stderr, "%s: realloc failed\n", __func__);
		exit(1); // still at early stage, we can't abort
	}

	char *res;
	char **ptr = (char **)&branch;

	res = strsep(ptr, "=");
	if (!res) return;

	// for string manipulations it is important to copy the string, otherwise
	// make_absolute() and add_trailing_slash() will corrupt our input (parse string)
	uopt.branches[uopt.nbranches].path = strdup(res);
	uopt.branches[uopt.nbranches].rw = 0;

	res = strsep(ptr, "=");
	if (res) {
		if (strcasecmp(res, "rw") == 0) {
			uopt.branches[uopt.nbranches].rw = 1;
		} else if (strcasecmp(res, "ro") == 0) {
			// no action needed here
		} else {
			fprintf(stderr, "Failed to parse RO/RW flag, setting RO.\n");
			// no action needed here either
		}
	}

	uopt.nbranches++;
}

/**
 * These options define our branch paths.
 * example arg string: "branch1=RW:branch2=RO:branch3=RO"
 */
static int parse_branches(const char *arg) {
	// the last argument is  our mountpoint, don't take it as branch!
	if (uopt.nbranches) return 0;

	// We don't free the buf as parts of it may go to branches
	char *buf = strdup(arg);
	char **ptr = (char **)&buf;
	char *branch;
	while ((branch = strsep(ptr, ROOT_SEP)) != NULL) {
		if (strlen(branch) == 0) continue;

		add_branch(branch);
	}

	free(buf);

	return uopt.nbranches;
}

/**
  * get_opt_str - get the parameter string
  * @arg	- option argument
  * @opt_name	- option name, used for error messages
  * fuse passes arguments with the argument prefix, e.g.
  * "-o chroot=/path/to/chroot/" will give us "chroot=/path/to/chroot/"
  * and we need to cut off the "chroot=" part
  * NOTE: If the user specifies a relative path of the branches
  *       to the chroot, it is absolutely required
  *       -o chroot=path is provided before specifying the braches!
  */
static char * get_opt_str(const char *arg, char *opt_name)
{
	char *str = index(arg, '=');

	if (!str) {
		fprintf(stderr, "-o %s parameter not properly specified, aborting!\n",
		        opt_name);
		exit(1); // still early phase, we can abort
	}

	if (strlen(str) < 3) {
		fprintf(stderr, "%s path has not sufficient characters, aborting!\n",
		        opt_name);
		exit(1);
	}

	str++; // just jump over the '='

	// copy of the given parameter, just in case something messes around
	// with command line parameters later on
	str = strdup(str);
	if (!str) {
		fprintf(stderr, "strdup failed: %s Aborting!\n", strerror(errno));
		exit(1);
	}
	return str;
}

static void print_help(const char *progname) {
	printf(
	"unionfs-fuse version "VERSION"\n"
	"by Radek Podgorny <radek@podgorny.cz>\n"
	"\n"
	"Usage: %s [options] branch[=RO/RW][:branch...] mountpoint\n"
	"The first argument is a colon separated list of directories to merge\n"
	"When neither RO nor RW is specified, selection defaults to RO.\n"
	"\n"
	"general options:\n"
	"    -d                     Enable debug output\n"
	"    -o opt,[opt...]        mount options\n"
	"    -h   --help            print help\n"
	"    -V   --version         print version\n"
	"\n"
	"UnionFS options:\n"
	"    -o chroot=path         chroot into this path. Use this if you \n"
        "                           want to have a union of \"/\" \n"
	"    -o cow                 enable copy-on-write\n"
	"                           mountpoint\n"
	"    -o debug_file          file to write debug information into\n"
	"    -o dirs=branch[=RO/RW][:branch...]\n"
	"                           alternate way to specify directories to merge\n"
	"    -o hide_meta_files     \".unionfs\" is a secret directory not\n"
	"                           visible by readdir(), and so are\n" 
        "                           .fuse_hidden* files\n"
	"    -o max_files=number    Increase the maximum number of open files\n"
	"    -o relaxed_permissions Disable permissions checks, but only if\n"
	"                           running neither as UID=0 or GID=0\n"
	"    -o statfs_omit_ro      do not count blocks of ro-branches\n"
	"    -o stats               show statistics in the file 'stats' under the\n"
	"\n",
	progname);
}

/**
  * This method is to post-process options once we know all of them
  */
void unionfs_post_opts(void) {
	// chdir to the given chroot, we
	if (uopt.chroot) {
		int res = chdir(uopt.chroot);
		if (res) {
			fprintf(stderr, "Chdir to %s failed: %s ! Aborting!\n",
				  uopt.chroot, strerror(errno));
			exit(1);
		}
	}

	// Make the pathes absolute and add trailing slashes
	int i;
	for (i = 0; i<uopt.nbranches; i++) {
		// if -ochroot= is specified, the path has to be given absolute
		// or relative to the chroot, so no need to make it absolute
		// also won't work, since we are not yet in the chroot here
		if (!uopt.chroot) {
			uopt.branches[i].path = make_absolute(uopt.branches[i].path);
		}
		uopt.branches[i].path = add_trailing_slash(uopt.branches[i].path);

		// Prevent accidental umounts. Especially system shutdown scripts tend
		// to umount everything they can. If we don't have an open file descriptor,
		// this might cause unexpected behaviour.
		char path[PATHLEN_MAX];

		if (!uopt.chroot) {
			BUILD_PATH(path, uopt.branches[i].path);
		} else {
			BUILD_PATH(path, uopt.chroot, uopt.branches[i].path);
		}

		int fd = open(path, O_RDONLY);
		if (fd == -1) {
			fprintf(stderr, "\nFailed to open %s: %s. Aborting!\n\n",
				path, strerror(errno));
			exit(1);
		}
		uopt.branches[i].fd = fd;
		uopt.branches[i].path_len = strlen(path);
	}
}

int unionfs_opt_proc(void *data, const char *arg, int key, struct fuse_args *outargs) {
	(void)data;

	int res = 0; // for general purposes

	switch (key) {
		case FUSE_OPT_KEY_NONOPT:
			res = parse_branches(arg);
			if (res > 0) return 0;
			uopt.retval = 1;
			return 1;
		case KEY_DIRS:
			// skip the "dirs="
			res = parse_branches(arg+5);
			if (res > 0) return 0;
			uopt.retval = 1;
			return 1;
		case KEY_CHROOT:
			uopt.chroot = get_opt_str(arg, "chroot");
			return 0;
		case KEY_COW:
			uopt.cow_enabled = true;
			return 0;
		case KEY_DEBUG:
			uopt.debug = true;
			return 1;
		case KEY_DEBUG_FILE:
			uopt.dbgpath = get_opt_str(arg, "debug_file");
			uopt.debug = true;
			return 0;
		case KEY_HELP:
			print_help(outargs->argv[0]);
			fuse_opt_add_arg(outargs, "-ho");
			uopt.doexit = 1;
			return 0;
		case KEY_HIDE_META_FILES:
		case KEY_HIDE_METADIR:
			uopt.hide_meta_files = true;
			return 0;
		case KEY_MAX_FILES:
			set_max_open_files(arg);
			return 0;
		case KEY_NOINITGROUPS:
			// option only for compatibility with older versions
			return 0;
		case KEY_STATFS_OMIT_RO:
			uopt.statfs_omit_ro = true;
			return 0;
		case KEY_RELAXED_PERMISSIONS:
			uopt.relaxed_permissions = true;
			return 0;
		case KEY_STATS:
			uopt.stats_enabled = 1;
			return 0;
		case KEY_VERSION:
			printf("unionfs-fuse version: "VERSION"\n");
#ifdef HAVE_XATTR
			printf("(compiled with xattr support)\n");
#endif
			uopt.doexit = 1;
			return 1;
		default:
 			uopt.retval = 1;
			return 1;
	}
}