1/*
2 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6#include <dirent.h>
7#include <errno.h>
8#include <limits.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <sys/stat.h>
13#include <unistd.h>
14
15
16// exported by the generic attribute support in libroot_build.so
17extern "C" bool __get_attribute_dir_path(const struct stat* st,
18	const char* path, char* buffer);
19
20
21class Path {
22public:
23	bool Init(const char* path)
24	{
25		size_t len = strlen(path);
26		if (len == 0 || len >= PATH_MAX)
27			return false;
28
29		strcpy(fPath, path);
30		fPathLen = len;
31
32		return true;
33	}
34
35	const char* GetPath() const
36	{
37		return fPath;
38	}
39
40	char* Buffer()
41	{
42		return fPath;
43	}
44
45	void BufferChanged()
46	{
47		fPathLen = strlen(fPath);
48	}
49
50	bool PushLeaf(const char* leaf)
51	{
52		size_t leafLen = strlen(leaf);
53
54		int separatorLen = (fPath[fPathLen - 1] == '/' ? 0 : 1);
55		if (fPathLen + separatorLen + leafLen >= PATH_MAX)
56			return false;
57
58		if (separatorLen > 0)
59			fPath[fPathLen++] = '/';
60
61		strcpy(fPath + fPathLen, leaf);
62		fPathLen += leafLen;
63
64		return true;
65	}
66
67	bool PopLeaf()
68	{
69		char* lastSlash = strrchr(fPath, '/');
70		if (lastSlash == NULL || lastSlash == fPath)
71			return false;
72
73		*lastSlash = '\0';
74		fPathLen = lastSlash - fPath;
75
76		return true;
77	}
78
79	char	fPath[PATH_MAX];
80	size_t	fPathLen;
81};
82
83
84static bool remove_entry(Path& entry, bool recursive, bool force,
85	bool removeAttributes);
86
87
88static void
89remove_dir_contents(Path& path, bool force, bool removeAttributes)
90{
91	// open the dir
92	DIR* dir = opendir(path.GetPath());
93	if (dir == NULL) {
94		fprintf(stderr, "Error: Failed to open dir \"%s\": %s\n",
95			path.GetPath(), strerror(errno));
96		return;
97	}
98
99	// iterate through the entries
100	errno = 0;
101	while (dirent* entry = readdir(dir)) {
102		// skip "." and ".."
103		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
104			continue;
105
106		if (!path.PushLeaf(entry->d_name)) {
107			fprintf(stderr, "Error: Path name of entry too long: dir: \"%s\", "
108				"entry: \"%s\"\n", path.GetPath(), entry->d_name);
109			continue;
110		}
111
112		remove_entry(path, true, force, removeAttributes);
113
114		path.PopLeaf();
115
116		errno = 0;
117	}
118
119	if (errno != 0) {
120		fprintf(stderr, "Error: Failed to read directory \"%s\": %s\n",
121			path.GetPath(), strerror(errno));
122	}
123
124	// close
125	closedir(dir);
126}
127
128
129static bool
130remove_entry(Path& path, bool recursive, bool force, bool removeAttributes)
131{
132	// stat the file
133	struct stat st;
134	if (lstat(path.GetPath(), &st) < 0) {
135		// errno == 0 shouldn't happen, but found on OpenSUSE Linux 10.3
136		if (force && (errno == ENOENT || errno == 0))
137			return true;
138
139		fprintf(stderr, "Error: Failed to remove \"%s\": %s\n", path.GetPath(),
140			strerror(errno));
141		return false;
142	}
143
144	// remove the file's attributes
145	if (removeAttributes) {
146		Path attrDirPath;
147		if (__get_attribute_dir_path(&st, path.GetPath(),
148				attrDirPath.Buffer())) {
149			attrDirPath.BufferChanged();
150			remove_entry(attrDirPath, true, true, false);
151		}
152	}
153
154	if (S_ISDIR(st.st_mode)) {
155		if (!recursive) {
156			fprintf(stderr, "Error: \"%s\" is a directory.\n", path.GetPath());
157			return false;
158		}
159
160		// remove the contents
161		remove_dir_contents(path, force, removeAttributes);
162
163		// remove the directory
164		if (rmdir(path.GetPath()) < 0) {
165			fprintf(stderr, "Error: Failed to remove directory \"%s\": %s\n",
166				path.GetPath(), strerror(errno));
167			return false;
168		}
169	} else {
170		// remove the entry
171		if (unlink(path.GetPath()) < 0) {
172			fprintf(stderr, "Error: Failed to remove entry \"%s\": %s\n",
173				path.GetPath(), strerror(errno));
174			return false;
175		}
176	}
177
178	return true;
179}
180
181
182int
183main(int argc, const char* const* argv)
184{
185	bool recursive = false;
186	bool force = false;
187
188	// parse parameters
189	int argi = 1;
190	for (argi = 1; argi < argc; argi++) {
191		const char *arg = argv[argi];
192		if (arg[0] != '-')
193			break;
194
195		if (arg[1] == '\0') {
196			fprintf(stderr, "Error: Invalid option \"-\"\n");
197			exit(1);
198		}
199
200		for (int i = 1; arg[i]; i++) {
201			switch (arg[i]) {
202				case 'f':
203					force = true;
204					break;
205				case 'r':
206					recursive = true;
207					break;
208				default:
209					fprintf(stderr, "Error: Unknown option \"-%c\"\n", arg[i]);
210					exit(1);
211			}
212		}
213	}
214
215	// check params
216	if (argi >= argc) {
217		fprintf(stderr, "Usage: %s [ -rf ] <file>...\n", argv[0]);
218		exit(1);
219	}
220
221	// remove loop
222	for (; argi < argc; argi++) {
223		Path path;
224		if (!path.Init(argv[argi])) {
225			fprintf(stderr, "Error: Invalid path: \"%s\".\n", argv[argi]);
226			continue;
227		}
228
229		remove_entry(path, recursive, force, true);
230	}
231
232	return 0;
233}
234