xref: /haiku/src/bin/diff_zip.cpp (revision d2e1e872611179c9cfaa43ce11bd58b1e3554e4b)
1 /*
2  * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All rights reserved.
3  *
4  * Distributed under the terms of the MIT License.
5  */
6 
7 #include <dirent.h>
8 #include <errno.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 #include <unistd.h>
14 
15 #include <map>
16 #include <string>
17 
18 
19 using std::string;
20 using std::map;
21 
22 
23 class Directory;
24 class Node;
25 
26 static Node* create_node(Directory* parent, const string& name,
27 	const struct stat& st);
28 
29 
30 enum diff_status {
31 	DIFF_UNCHANGED,
32 	DIFF_REMOVED,
33 	DIFF_CHANGED,
34 	DIFF_ERROR
35 };
36 
37 
38 class EntryWriter {
39 public:
40 	EntryWriter(int fd)
41 		: fFD(fd)
42 	{
43 	}
44 
45 	void Write(const char* entry)
46 	{
47 		write(fFD, entry, strlen(entry));
48 		write(fFD, "\n", 1);
49 	}
50 
51 private:
52 	int	fFD;
53 };
54 
55 
56 class Node {
57 public:
58 	Node(Directory* parent, const string& name, const struct stat& st)
59 		: fParent(parent),
60 		  fName(name),
61 		  fStat(st)
62 	{
63 	}
64 
65 	virtual ~Node()
66 	{
67 	}
68 
69 	Directory* Parent() const		{ return fParent; }
70 	const string& Name() const		{ return fName; }
71 	const struct stat& Stat() const	{ return fStat; }
72 
73 	string Path() const;
74 
75 	bool DoStat(struct stat& st) const
76 	{
77 		string path(Path());
78 		if (lstat(path.c_str(), &st) != 0)
79 			return false;
80 		return true;
81 	}
82 
83 	virtual bool Scan()
84 	{
85 		return true;
86 	}
87 
88 	virtual diff_status CollectDiffEntries(EntryWriter* out) const
89 	{
90 		string path(Path());
91 		struct stat st;
92 
93 		diff_status status = DiffEntry(path, st);
94 		if (status == DIFF_CHANGED)
95 			out->Write(path.c_str());
96 
97 		return status;
98 	}
99 
100 	virtual void Dump(int level) const
101 	{
102 		printf("%*s%s\n", level, "", fName.c_str());
103 	}
104 
105 protected:
106 	diff_status DiffEntry(const string& path, struct stat& st) const
107 	{
108 		if (lstat(path.c_str(), &st) == 0) {
109 			if (st.st_mode != fStat.st_mode
110 				|| st.st_mtime != fStat.st_mtime
111 				|| st.st_size != fStat.st_size) {
112 				return DIFF_CHANGED;
113 			}
114 			return DIFF_UNCHANGED;
115 		} else if (errno == ENOENT) {
116 			// that's OK, the entry was removed
117 			return DIFF_REMOVED;
118 		} else {
119 			// some error
120 			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n",
121 				path.c_str(), strerror(errno));
122 			return DIFF_ERROR;
123 		}
124 	}
125 
126 private:
127 	Directory*	fParent;
128 	string		fName;
129 	struct stat	fStat;
130 };
131 
132 
133 class Directory : public Node {
134 public:
135 	Directory(Directory* parent, const string& name, const struct stat& st)
136 		: Node(parent, name, st),
137 		  fEntries()
138 	{
139 	}
140 
141 	void AddEntry(const char* name, Node* node)
142 	{
143 		fEntries[name] = node;
144 	}
145 
146 	virtual bool Scan()
147 	{
148 		string path(Path());
149 		DIR* dir = opendir(path.c_str());
150 		if (dir == NULL) {
151 			fprintf(stderr, "Error: Failed to open directory \"%s\": %s\n",
152 				path.c_str(), strerror(errno));
153 			return false;
154 		}
155 
156 		errno = 0;
157 		while (dirent* entry = readdir(dir)) {
158 			if (strcmp(entry->d_name, ".") == 0
159 				|| strcmp(entry->d_name, "..") == 0) {
160 				continue;
161 			}
162 
163 			string entryPath = path + '/' + entry->d_name;
164 			struct stat st;
165 			if (lstat(entryPath.c_str(), &st) != 0) {
166 				fprintf(stderr, "Error: Failed to stat entry \"%s\": %s\n",
167 					entryPath.c_str(), strerror(errno));
168 				closedir(dir);
169 				return false;
170 			}
171 
172 			Node* node = create_node(this, entry->d_name, st);
173 			fEntries[entry->d_name] = node;
174 
175 			errno = 0;
176 		}
177 
178 		if (errno != 0) {
179 			fprintf(stderr, "Error: Failed to read directory \"%s\": %s\n",
180 				path.c_str(), strerror(errno));
181 			closedir(dir);
182 			return false;
183 		}
184 
185 		closedir(dir);
186 
187 		// recursively scan the entries
188 		for (EntryMap::iterator it = fEntries.begin(); it != fEntries.end();
189 				++it) {
190 			Node* node = it->second;
191 			if (!node->Scan())
192 				return false;
193 		}
194 
195 		return true;
196 	}
197 
198 	virtual diff_status CollectDiffEntries(EntryWriter* out) const
199 	{
200 		string path(Path());
201 		struct stat st;
202 
203 		diff_status status = DiffEntry(path, st);
204 		if (status == DIFF_REMOVED || status == DIFF_ERROR)
205 			return status;
206 
207 		// we print it only if it is no longer a directory
208 		if (!S_ISDIR(st.st_mode)) {
209 			out->Write(path.c_str());
210 			return DIFF_CHANGED;
211 		}
212 
213 		// iterate through the "new" entries
214 		DIR* dir = opendir(path.c_str());
215 		if (dir == NULL) {
216 			fprintf(stderr, "Error: Failed to open directory \"%s\": %s\n",
217 				path.c_str(), strerror(errno));
218 			return DIFF_ERROR;
219 		}
220 
221 		errno = 0;
222 		while (dirent* entry = readdir(dir)) {
223 			if (strcmp(entry->d_name, ".") == 0
224 				|| strcmp(entry->d_name, "..") == 0) {
225 				continue;
226 			}
227 
228 			EntryMap::const_iterator it = fEntries.find(entry->d_name);
229 			if (it == fEntries.end()) {
230 				// new entry
231 				string entryPath = path + "/" + entry->d_name;
232 				out->Write(entryPath.c_str());
233 			} else {
234 				// old entry -- recurse
235 				diff_status entryStatus = it->second->CollectDiffEntries(out);
236 				if (entryStatus == DIFF_ERROR) {
237 					closedir(dir);
238 					return status;
239 				}
240 				if (entryStatus != DIFF_UNCHANGED)
241 					status = DIFF_CHANGED;
242 			}
243 
244 			errno = 0;
245 		}
246 
247 		if (errno != 0) {
248 			fprintf(stderr, "Error: Failed to read directory \"%s\": %s\n",
249 				path.c_str(), strerror(errno));
250 			closedir(dir);
251 			return DIFF_ERROR;
252 		}
253 
254 		closedir(dir);
255 		return status;
256 	}
257 
258 	virtual void Dump(int level) const
259 	{
260 		Node::Dump(level);
261 
262 		for (EntryMap::const_iterator it = fEntries.begin();
263 				it != fEntries.end(); ++it) {
264 			it->second->Dump(level + 1);
265 		}
266 	}
267 
268 
269 private:
270 	typedef map<string, Node*> EntryMap;
271 
272 	EntryMap	fEntries;
273 };
274 
275 
276 string
277 Node::Path() const
278 {
279 	if (fParent == NULL)
280 		return fName;
281 
282 	return fParent->Path() + '/' + fName;
283 }
284 
285 
286 static Node*
287 create_node(Directory* parent, const string& name, const struct stat& st)
288 {
289 	if (S_ISDIR(st.st_mode))
290 		return new Directory(parent, name, st);
291 	return new Node(parent, name, st);
292 }
293 
294 
295 int
296 main(int argc, const char* const* argv)
297 {
298 	// the paths are listed after a "--" argument
299 	int argi = 1;
300 	for (; argi < argc; argi++) {
301 		if (strcmp(argv[argi], "--") == 0)
302 			break;
303 	}
304 
305 	if (argi + 1 >= argc) {
306 		fprintf(stderr, "Usage: %s <zip arguments> ... -- <paths>\n", argv[0]);
307 		exit(1);
308 	}
309 
310 	int zipArgCount = argi;
311 	const char* const* paths = argv + argi + 1;
312 	int pathCount = argc - argi - 1;
313 
314 	// snapshot the hierarchy
315 	Node** rootNodes = new Node*[pathCount];
316 
317 	for (int i = 0; i < pathCount; i++) {
318 		const char* path = paths[i];
319 		struct stat st;
320 		if (lstat(path, &st) != 0) {
321 			fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", path,
322 				strerror(errno));
323 			exit(1);
324 		}
325 
326 		rootNodes[i] = create_node(NULL, path, st);
327 		if (!rootNodes[i]->Scan())
328 			exit(1);
329 	}
330 
331 	// create a temp file
332 	char tmpFileName[64];
333 	sprintf(tmpFileName, "/tmp/diff_zip_%d_XXXXXX", (int)getpid());
334 	int tmpFileFD = mkstemp(tmpFileName);
335 	if (tmpFileFD < 0) {
336 		fprintf(stderr, "Error: Failed create temp file: %s\n",
337 			strerror(errno));
338 		exit(1);
339 	}
340 	unlink(tmpFileName);
341 
342 	// wait
343 	{
344 		printf("Waiting for changes. Press RETURN to continue...");
345 		fflush(stdout);
346 		char buffer[32];
347 		fgets(buffer, sizeof(buffer), stdin);
348 	}
349 
350 	EntryWriter tmpFile(tmpFileFD);
351 
352 	for (int i = 0; i < pathCount; i++) {
353 		if (rootNodes[i]->CollectDiffEntries(&tmpFile) == DIFF_ERROR)
354 			exit(1);
355 	}
356 
357 	// dup the temp file FD to our stdin and exec()
358 	dup2(tmpFileFD, 0);
359 	close(tmpFileFD);
360 	lseek(0, 0, SEEK_SET);
361 
362 	char** zipArgs = new char*[zipArgCount + 2];
363 	zipArgs[0] = strdup("zip");
364 	zipArgs[1] = strdup("-@");
365 	for (int i = 1; i < zipArgCount; i++)
366 		zipArgs[i + 1] = strdup(argv[i]);
367 	zipArgs[zipArgCount + 1] = NULL;
368 
369 	execvp("zip", zipArgs);
370 
371 	fprintf(stderr, "Error: Failed exec*() zip: %s\n", strerror(errno));
372 
373 	return 1;
374 }
375