-
Notifications
You must be signed in to change notification settings - Fork 39
/
main.c
308 lines (287 loc) · 11.1 KB
/
main.c
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
/*
* main.c
*
* the command line user interface
*/
#define _GNU_SOURCE
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <linux/limits.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <errno.h>
#ifndef _SYS_STAT_H
#include <linux/stat.h>
#endif
#include "logic.h"
#include "sh.h"
#include "common.h"
#define STRING_BUFFER_SIZE PATH_MAX * 2
// currently, brief and verbose are mutually exclusive
bool verbose;
bool brief;
bool ignore;
bool force;
extern const char *program_name;
#ifndef __GLIBC__
/*
* GNU basename implementation
*/
static const char *__basename(const char *filename) {
char *p = strrchr(filename, '/');
return p ? p + 1 : filename;
}
#define basename(x) __basename(x)
#endif
void print_help(const char *program) {
printf("Usage: %s command options\n", program);
puts("");
puts("Commands:");
puts(" vacuum - remove duplicated files in upperdir where copy_up is done but the file is not actually modified");
puts(" diff - show the list of actually changed files");
puts(" merge - merge all changes from upperdir to lowerdir, and clear upperdir");
puts(" deref - copy changes from upperdir to a new upperdir unfolding redirect and metacopy");
puts("");
puts("Options:");
puts(" -l, --lowerdir=LOWERDIR the lowerdir of OverlayFS (required)");
puts(" -u, --upperdir=UPPERDIR the upperdir of OverlayFS (required)");
puts(" -m, --mountdir=MOUNTDIR the mountdir of OverlayFS (optional)");
puts(" -L, --lowernew=LOWERNEW the lowerdir of new OverlayFS (optional)");
puts(" -U, --uppernew=UPPERNEW the upperdir of new OverlayFS (optional)");
puts(" -i --ignore-mounted don't prompt if OverlayFS is still mounted (optional)");
puts(" -f --force-execution also execute (and clean) the generated script in case of merge or vacuum (optional)");
puts(" -v, --verbose with diff action only: when a directory only exists in one version, still list every file of the directory");
puts(" -V, --version print project version");
puts(" -b, --brief with diff action only: conform to output of diff --brief --recursive --no-dereference");
puts(" -h, --help show this help text");
puts("");
puts("See https://github.com/kmxz/overlayfs-tools/ for warnings and more information.");
}
bool starts_with(const char *haystack, const char* needle) {
return strncmp(needle, haystack, strlen(needle)) == 0;
}
bool is_mounted(const char *lower, const char *upper) {
FILE *f = fopen("/proc/mounts", "r");
if (!f) {
fprintf(stderr, "Cannot read /proc/mounts to test whether OverlayFS is mounted.\n");
return true;
}
char buf[STRING_BUFFER_SIZE];
while (fgets(buf, STRING_BUFFER_SIZE, f)) {
if (!starts_with(buf, "overlay")) {
continue;
}
if (strlen(buf) == STRING_BUFFER_SIZE) {
fprintf(stderr, "OverlayFS line in /proc/mounts is too long.\n");
return true;
}
char *m_lower = strstr(buf, "lowerdir=");
char *m_upper = strstr(buf, "upperdir=");
if (m_lower == NULL || m_upper == NULL) {
fprintf(stderr, "Cannot extract information from OverlayFS line in /proc/mounts.\n");
return true;
}
m_lower = &(m_lower[strlen("lowerdir=")]);
m_upper = &(m_upper[strlen("upperdir=")]);
if (!(strncmp(lower, m_lower, strlen(lower)) && strncmp(upper, m_upper, strlen(upper)))) {
printf("The OverlayFS involved is still mounted.\n");
return true;
}
}
return false;
}
bool check_mounted(const char *lower, const char *upper) {
if (is_mounted(lower, upper) && !ignore) {
printf("It is strongly recommended to unmount OverlayFS first. Still continue (not recommended)?: \n");
int r = getchar();
if (r != 'Y' && r != 'y') {
return true;
}
}
return false;
}
bool directory_exists(const char *path) {
struct stat sb;
if (lstat(path, &sb) != 0) { return false; }
return (sb.st_mode & S_IFMT) == S_IFDIR;
}
bool directory_create(const char *name, const char *path) {
if (mkdir(path, 0755) == 0 || errno == EEXIST) { return true; }
fprintf(stderr, "%s directory '%s' does not exist and cannot be created.\n", name, path);
exit(EXIT_FAILURE);
}
bool real_check_xattr_trusted(const char *tmp_path, int tmp_file) {
int ret = fsetxattr(tmp_file, "trusted.overlay.test", "naive", 5, 0);
close(tmp_file);
if (ret) { return false; }
char verify_buffer[10];
if (getxattr(tmp_path, "trusted.overlay.test", verify_buffer, 10) != 5) { return false; }
return !strncmp(verify_buffer, "naive", 5);
}
bool check_xattr_trusted(const char *upper) {
char tmp_path[PATH_MAX];
strcpy(tmp_path, upper);
strcat(tmp_path, "/.xattr_test_XXXXXX.tmp");
int tmp_file = mkstemps(tmp_path, 4);
if (tmp_file < 0) { return false; }
bool ret = real_check_xattr_trusted(tmp_path, tmp_file);
unlink(tmp_path);
return ret;
}
int main(int argc, char *argv[]) {
char *lower = NULL;
char *upper = NULL;
char *dir, *mnt = NULL;
static struct option long_options[] = {
{ "lowerdir", required_argument, 0, 'l' },
{ "upperdir", required_argument, 0, 'u' },
{ "mountdir", required_argument, 0, 'm' },
{ "lowernew", required_argument, 0, 'L' },
{ "uppernew", required_argument, 0, 'U' },
{ "ignore-mounted", no_argument , 0, 'i' },
{ "force-execution", no_argument , 0, 'f' },
{ "help", no_argument , 0, 'h' },
{ "verbose", no_argument , 0, 'v' },
{ "version", no_argument , 0, 'V' },
{ "brief", no_argument , 0, 'b' },
{ 0, 0, 0, 0 }
};
int opt = 0;
int long_index = 0;
program_name = basename(argv[0]);
while ((opt = getopt_long_only(argc, argv, "l:u:m:L:U:ihvVb", long_options, &long_index)) != -1) {
switch (opt) {
case 'l':
lower = realpath(optarg, NULL);
if (lower) { vars[LOWERDIR] = lower; }
break;
case 'u':
upper = realpath(optarg, NULL);
if (upper) { vars[UPPERDIR] = upper; }
break;
case 'm':
mnt = realpath(optarg, NULL);
if (mnt) { vars[MOUNTDIR] = mnt; }
break;
case 'L':
directory_create("New lowerdir", optarg);
dir = realpath(optarg, NULL);
if (dir) { vars[LOWERNEW] = dir; }
break;
case 'U':
directory_create("New upperdir", optarg);
dir = realpath(optarg, NULL);
if (dir) { vars[UPPERNEW] = dir; }
break;
case 'i':
ignore = true;
break;
case 'f':
force = true;
break;
case 'h':
print_help(program_name);
return EXIT_SUCCESS;
case 'v':
verbose = true;
brief = false;
break;
case 'b':
verbose = false;
brief = true;
break;
case 'V':
version();
exit(EXIT_SUCCESS);
default:
fprintf(stderr, "Option %c is not supported.\n", opt);
goto see_help;
}
}
if (!lower) {
fprintf(stderr, "Lower directory is not specified or doesn't exist.\n");
goto see_help;
}
if (!directory_exists(lower)) {
fprintf(stderr, "Lower directory cannot be opened.\n");
goto see_help;
}
if (!upper) {
fprintf(stderr, "Upper directory not specified.\n");
goto see_help;
}
if (!directory_exists(upper)) {
fprintf(stderr, "Upper directory cannot be opened.\n");
goto see_help;
}
if (!check_xattr_trusted(upper)) {
fprintf(stderr, "The program cannot write trusted.* xattr. Try run again as root.\n");
return EXIT_FAILURE;
}
// Relax check for mounted overlay if we are not going to modify lowerdir/upperdir
if ((!vars[LOWERNEW] || !vars[UPPERNEW]) && check_mounted(lower, upper)) {
return EXIT_FAILURE;
}
if (optind == argc - 1) {
int out;
char filename_template[] = "overlay-tools-XXXXXX.sh";
FILE *script = NULL;
if (strcmp(argv[optind], "diff") == 0) {
out = diff(lower, upper);
} else if (strcmp(argv[optind], "vacuum") == 0) {
script = create_shell_script(filename_template);
if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; }
out = vacuum(lower, upper, script);
} else if (strcmp(argv[optind], "merge") == 0) {
script = create_shell_script(filename_template);
if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; }
out = merge(lower, upper, script);
} else if (strcmp(argv[optind], "deref") == 0) {
if (!mnt || !vars[UPPERNEW]) { fprintf(stderr, "'deref' command requires --uppernew and --mountdir.\n"); return EXIT_FAILURE; }
if (!directory_exists(mnt)) {
fprintf(stderr, "OverlayFS mount directory cannot be opened.\n");
goto see_help;
}
script = create_shell_script(filename_template);
if (script == NULL) { fprintf(stderr, "Script file cannot be created.\n"); return EXIT_FAILURE; }
out = deref(mnt, upper, script);
} else {
fprintf(stderr, "Action not supported.\n");
goto see_help;
}
if (script != NULL) {
fclose(script);
if (force)
{
printf("The script %s is created. Running the script now, as force is set to true.\n", filename_template);
char *command = malloc(strlen(filename_template) * sizeof(char) + 10);
if (command != NULL)
{
sprintf(command, "bash %s", filename_template);
system(command);
sprintf(command, "rm %s", filename_template);
system(command);
free(command);
}
}
else
{
printf("The script %s is created. Run the script to do the actual work please. Remember to run it when the OverlayFS is not mounted.\n", filename_template);
}
}
if (out) {
fprintf(stderr, "Action aborted due to fatal error.\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
fprintf(stderr, "Please specify one action.\n");
see_help:
fprintf(stderr, "Try '%s --help' for more information.\n", program_name);
return EXIT_FAILURE;
}