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