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 delete[] rootNodes; 373 374 return 1; 375 } 376