aboutsummaryrefslogtreecommitdiff
blob: ed30d6d3478d7a88d5c8e0a536bf31d5f5caf47a (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
/* Emulate `mkdir -p -m MODE PATH` */
static int
mkdir_p_at(int dfd, const char *path, mode_t mode)
{
	char *_p, *p, *s;

	/* Assume that most of the time, only the last element
	 * is missing.  So if we can mkdir it right away, bail. */
	if (mkdirat(dfd, path, mode) == 0 || errno == EEXIST)
		return 0;

	/* Build up the whole tree */
	_p = p = xstrdup(path);

	while (*p) {
		/* Skip duplicate slashes */
		while (*p == '/')
			++p;

		/* Find the next path element */
		s = strchr(p, '/');
		if (!s) {
			mkdirat(dfd, _p, mode);
			break;
		}

		/* Make it */
		*s = '\0';
		mkdirat(dfd, _p, mode);
		*s = '/';

		p = s;
	}

	free(_p);

	return 0;
}
static int
mkdir_p(const char *path, mode_t mode)
{
	return mkdir_p_at(AT_FDCWD, path, mode);
}

/* Emulate `rm -rf PATH` */
static int
rm_rf_at(int dfd, const char *path)
{
	int subdfd;
	DIR *dir;
	struct dirent *de;

	/* Cannot use O_PATH as we want to use fdopendir() */
	subdfd = openat(dfd, path, O_RDONLY|O_CLOEXEC|O_NOFOLLOW);
	if (subdfd < 0)
		return -1;

	dir = fdopendir(subdfd);
	if (!dir) {
		close(subdfd);
		return unlinkat(dfd, path, 0);
	}

	while ((de = readdir(dir)) != NULL) {
		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
			continue;
		if (unlinkat(subdfd, de->d_name, 0) == -1) {
			if (unlikely(errno != EISDIR)) {
				struct stat st;
				/* above is a linux short-cut, we really just want to
				 * know whether we're really with a directory or not */
				if (fstatat(subdfd, de->d_name, &st, 0) != 0 ||
						!(st.st_mode & S_IFDIR))
					errp("could not unlink %s", de->d_name);
			}
			rm_rf_at(subdfd, de->d_name);
			unlinkat(subdfd, de->d_name, AT_REMOVEDIR);
		}
	}

	unlinkat(dfd, path, AT_REMOVEDIR);

	/* this also does close(subdfd); */
	closedir(dir);

	return 0;
}

static int
rm_rf(const char *path)
{
	rm_rf_at(AT_FDCWD, path);

	if (rmdir(path) == 0)
		return 0;

	/* if path is a symlink, unlink it */
	if (unlink(path) == 0)
		return 0;

	/* XXX: we don't handle:
	 *      trailing slashes: `rm -rf a/b/c/` -> need to change to a/b/c */
	return -1;
}

static int
rmdir_r_at(int dfd, const char *path)
{
	size_t len;
	char *p, *e;

	p = xstrdup_len(path, &len);
	e = p + len;

	while (e != p) {
		if (unlinkat(dfd, p, AT_REMOVEDIR) && errno == ENOTEMPTY)
			break;
		while (*e != '/' && e > p)
			--e;
		*e = '\0';
	}

	free(p);

	return 0;
}

/*
static int
rmdir_r(const char *path)
{
	return rmdir_r_at(AT_FDCWD, path);
}
*/