mirror of
https://review.haiku-os.org/haiku
synced 2025-02-02 11:46:25 +01:00
375 lines
7.2 KiB
C++
375 lines
7.2 KiB
C++
|
/*
|
||
|
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All rights reserved.
|
||
|
*
|
||
|
* Distributed under the terms of the MIT License.
|
||
|
*/
|
||
|
|
||
|
#include <dirent.h>
|
||
|
#include <errno.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#include <map>
|
||
|
#include <string>
|
||
|
|
||
|
|
||
|
using std::string;
|
||
|
using std::map;
|
||
|
|
||
|
|
||
|
class Directory;
|
||
|
class Node;
|
||
|
|
||
|
static Node* create_node(Directory* parent, const string& name,
|
||
|
const struct stat& st);
|
||
|
|
||
|
|
||
|
enum diff_status {
|
||
|
DIFF_UNCHANGED,
|
||
|
DIFF_REMOVED,
|
||
|
DIFF_CHANGED,
|
||
|
DIFF_ERROR
|
||
|
};
|
||
|
|
||
|
|
||
|
class EntryWriter {
|
||
|
public:
|
||
|
EntryWriter(int fd)
|
||
|
: fFD(fd)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void Write(const char* entry)
|
||
|
{
|
||
|
write(fFD, entry, strlen(entry));
|
||
|
write(fFD, "\n", 1);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
int fFD;
|
||
|
};
|
||
|
|
||
|
|
||
|
class Node {
|
||
|
public:
|
||
|
Node(Directory* parent, const string& name, const struct stat& st)
|
||
|
: fParent(parent),
|
||
|
fName(name),
|
||
|
fStat(st)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
virtual ~Node()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
Directory* Parent() const { return fParent; }
|
||
|
const string& Name() const { return fName; }
|
||
|
const struct stat& Stat() const { return fStat; }
|
||
|
|
||
|
string Path() const;
|
||
|
|
||
|
bool DoStat(struct stat& st) const
|
||
|
{
|
||
|
string path(Path());
|
||
|
if (lstat(path.c_str(), &st) != 0)
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual bool Scan()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual diff_status CollectDiffEntries(EntryWriter* out) const
|
||
|
{
|
||
|
string path(Path());
|
||
|
struct stat st;
|
||
|
|
||
|
diff_status status = DiffEntry(path, st);
|
||
|
if (status == DIFF_CHANGED)
|
||
|
out->Write(path.c_str());
|
||
|
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
virtual void Dump(int level) const
|
||
|
{
|
||
|
printf("%*s%s\n", level, "", fName.c_str());
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
diff_status DiffEntry(const string& path, struct stat& st) const
|
||
|
{
|
||
|
if (lstat(path.c_str(), &st) == 0) {
|
||
|
if (st.st_mode != fStat.st_mode
|
||
|
|| st.st_mtime != fStat.st_mtime
|
||
|
|| st.st_size != fStat.st_size) {
|
||
|
return DIFF_CHANGED;
|
||
|
}
|
||
|
return DIFF_UNCHANGED;
|
||
|
} else if (errno == ENOENT) {
|
||
|
// that's OK, the entry was removed
|
||
|
return DIFF_REMOVED;
|
||
|
} else {
|
||
|
// some error
|
||
|
fprintf(stderr, "Error: Failed to stat \"%s\": %s\n",
|
||
|
path.c_str(), strerror(errno));
|
||
|
return DIFF_ERROR;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
Directory* fParent;
|
||
|
string fName;
|
||
|
struct stat fStat;
|
||
|
};
|
||
|
|
||
|
|
||
|
class Directory : public Node {
|
||
|
public:
|
||
|
Directory(Directory* parent, const string& name, const struct stat& st)
|
||
|
: Node(parent, name, st),
|
||
|
fEntries()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
void AddEntry(const char* name, Node* node)
|
||
|
{
|
||
|
fEntries[name] = node;
|
||
|
}
|
||
|
|
||
|
virtual bool Scan()
|
||
|
{
|
||
|
string path(Path());
|
||
|
DIR* dir = opendir(path.c_str());
|
||
|
if (dir == NULL) {
|
||
|
fprintf(stderr, "Error: Failed to open directory \"%s\": %s\n",
|
||
|
path.c_str(), strerror(errno));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
errno = 0;
|
||
|
while (dirent* entry = readdir(dir)) {
|
||
|
if (strcmp(entry->d_name, ".") == 0
|
||
|
|| strcmp(entry->d_name, "..") == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
string entryPath = path + '/' + entry->d_name;
|
||
|
struct stat st;
|
||
|
if (lstat(entryPath.c_str(), &st) != 0) {
|
||
|
fprintf(stderr, "Error: Failed to stat entry \"%s\": %s\n",
|
||
|
entryPath.c_str(), strerror(errno));
|
||
|
closedir(dir);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Node* node = create_node(this, entry->d_name, st);
|
||
|
fEntries[entry->d_name] = node;
|
||
|
|
||
|
errno = 0;
|
||
|
}
|
||
|
|
||
|
if (errno != 0) {
|
||
|
fprintf(stderr, "Error: Failed to read directory \"%s\": %s\n",
|
||
|
path.c_str(), strerror(errno));
|
||
|
closedir(dir);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
closedir(dir);
|
||
|
|
||
|
// recursively scan the entries
|
||
|
for (EntryMap::iterator it = fEntries.begin(); it != fEntries.end();
|
||
|
++it) {
|
||
|
Node* node = it->second;
|
||
|
if (!node->Scan())
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
virtual diff_status CollectDiffEntries(EntryWriter* out) const
|
||
|
{
|
||
|
string path(Path());
|
||
|
struct stat st;
|
||
|
|
||
|
diff_status status = DiffEntry(path, st);
|
||
|
if (status == DIFF_REMOVED || status == DIFF_ERROR)
|
||
|
return status;
|
||
|
|
||
|
// we print it only if it is no longer a directory
|
||
|
if (!S_ISDIR(st.st_mode)) {
|
||
|
out->Write(path.c_str());
|
||
|
return DIFF_CHANGED;
|
||
|
}
|
||
|
|
||
|
// iterate through the "new" entries
|
||
|
DIR* dir = opendir(path.c_str());
|
||
|
if (dir == NULL) {
|
||
|
fprintf(stderr, "Error: Failed to open directory \"%s\": %s\n",
|
||
|
path.c_str(), strerror(errno));
|
||
|
return DIFF_ERROR;
|
||
|
}
|
||
|
|
||
|
errno = 0;
|
||
|
while (dirent* entry = readdir(dir)) {
|
||
|
if (strcmp(entry->d_name, ".") == 0
|
||
|
|| strcmp(entry->d_name, "..") == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
EntryMap::const_iterator it = fEntries.find(entry->d_name);
|
||
|
if (it == fEntries.end()) {
|
||
|
// new entry
|
||
|
string entryPath = path + "/" + entry->d_name;
|
||
|
out->Write(entryPath.c_str());
|
||
|
} else {
|
||
|
// old entry -- recurse
|
||
|
diff_status entryStatus = it->second->CollectDiffEntries(out);
|
||
|
if (entryStatus == DIFF_ERROR) {
|
||
|
closedir(dir);
|
||
|
return status;
|
||
|
}
|
||
|
if (entryStatus != DIFF_UNCHANGED)
|
||
|
status = DIFF_CHANGED;
|
||
|
}
|
||
|
|
||
|
errno = 0;
|
||
|
}
|
||
|
|
||
|
if (errno != 0) {
|
||
|
fprintf(stderr, "Error: Failed to read directory \"%s\": %s\n",
|
||
|
path.c_str(), strerror(errno));
|
||
|
closedir(dir);
|
||
|
return DIFF_ERROR;
|
||
|
}
|
||
|
|
||
|
closedir(dir);
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
virtual void Dump(int level) const
|
||
|
{
|
||
|
Node::Dump(level);
|
||
|
|
||
|
for (EntryMap::const_iterator it = fEntries.begin();
|
||
|
it != fEntries.end(); ++it) {
|
||
|
it->second->Dump(level + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private:
|
||
|
typedef map<string, Node*> EntryMap;
|
||
|
|
||
|
EntryMap fEntries;
|
||
|
};
|
||
|
|
||
|
|
||
|
string
|
||
|
Node::Path() const
|
||
|
{
|
||
|
if (fParent == NULL)
|
||
|
return fName;
|
||
|
|
||
|
return fParent->Path() + '/' + fName;
|
||
|
}
|
||
|
|
||
|
|
||
|
static Node*
|
||
|
create_node(Directory* parent, const string& name, const struct stat& st)
|
||
|
{
|
||
|
if (S_ISDIR(st.st_mode))
|
||
|
return new Directory(parent, name, st);
|
||
|
return new Node(parent, name, st);
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
main(int argc, const char* const* argv)
|
||
|
{
|
||
|
// the paths are listed after a "--" argument
|
||
|
int argi = 1;
|
||
|
for (; argi < argc; argi++) {
|
||
|
if (strcmp(argv[argi], "--") == 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (argi + 1 >= argc) {
|
||
|
fprintf(stderr, "Usage: %s <zip arguments> ... -- <paths>\n", argv[0]);
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
int zipArgCount = argi;
|
||
|
const char* const* paths = argv + argi + 1;
|
||
|
int pathCount = argc - argi - 1;
|
||
|
|
||
|
// snapshot the hierarchy
|
||
|
Node** rootNodes = new Node*[pathCount];
|
||
|
|
||
|
for (int i = 0; i < pathCount; i++) {
|
||
|
const char* path = paths[i];
|
||
|
struct stat st;
|
||
|
if (lstat(path, &st) != 0) {
|
||
|
fprintf(stderr, "Error: Failed to stat \"%s\": %s\n", path,
|
||
|
strerror(errno));
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
rootNodes[i] = create_node(NULL, path, st);
|
||
|
if (!rootNodes[i]->Scan())
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
// create a temp file
|
||
|
char tmpFileName[64];
|
||
|
sprintf(tmpFileName, "/tmp/diff_zip_%d_XXXXXX", (int)getpid());
|
||
|
int tmpFileFD = mkstemp(tmpFileName);
|
||
|
if (tmpFileFD < 0) {
|
||
|
fprintf(stderr, "Error: Failed create temp file: %s\n",
|
||
|
strerror(errno));
|
||
|
exit(1);
|
||
|
}
|
||
|
unlink(tmpFileName);
|
||
|
|
||
|
// wait
|
||
|
{
|
||
|
printf("Waiting for changes. Press RETURN to continue...");
|
||
|
fflush(stdout);
|
||
|
char buffer[32];
|
||
|
fgets(buffer, sizeof(buffer), stdin);
|
||
|
}
|
||
|
|
||
|
EntryWriter tmpFile(tmpFileFD);
|
||
|
|
||
|
for (int i = 0; i < pathCount; i++) {
|
||
|
if (rootNodes[i]->CollectDiffEntries(&tmpFile) == DIFF_ERROR)
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
// dup the temp file FD to our stdin and exec()
|
||
|
dup2(tmpFileFD, 0);
|
||
|
close(tmpFileFD);
|
||
|
lseek(0, 0, SEEK_SET);
|
||
|
|
||
|
char** zipArgs = new char*[zipArgCount + 2];
|
||
|
zipArgs[0] = strdup("zip");
|
||
|
zipArgs[1] = strdup("-@");
|
||
|
for (int i = 1; i < zipArgCount; i++)
|
||
|
zipArgs[i + 1] = strdup(argv[i]);
|
||
|
zipArgs[zipArgCount + 1] = NULL;
|
||
|
|
||
|
execvp("zip", zipArgs);
|
||
|
|
||
|
fprintf(stderr, "Error: Failed exec*() zip: %s\n", strerror(errno));
|
||
|
|
||
|
return 1;
|
||
|
}
|