genfilelist.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. // -*- mode: cpp; mode: fold -*-
  2. // Description /*{{{*/
  3. // $Id: genfilelist.cc,v 1.10 1999/12/26 06:59:01 jgg Exp $
  4. /* ######################################################################
  5. Generate File List
  6. File list generation can be done with modification to the generation
  7. order, ordering can be done by depth, breadth or by tree with and
  8. a fitler can be applied to delay a directory till the end of processing.
  9. The emitter simply generates the necessary structure and writes it to
  10. the IO. The client can hook some of the functions to provide progress
  11. reporting and md5 caching if so desired.
  12. ##################################################################### */
  13. /*}}}*/
  14. // Include files /*{{{*/
  15. #ifdef __GNUG__
  16. #pragma implementation "dsync/genfilelist.h"
  17. #endif
  18. #include <dsync/genfilelist.h>
  19. #include <dsync/error.h>
  20. #include <dsync/fileutl.h>
  21. #include <dsync/md5.h>
  22. #include <dsync/fileutl.h>
  23. #include <dsync/rsync-algo.h>
  24. #include <sys/stat.h>
  25. #include <unistd.h>
  26. #include <dirent.h>
  27. #include <stdio.h>
  28. /*}}}*/
  29. // GenFileList::dsGenFileList - Constructor /*{{{*/
  30. // ---------------------------------------------------------------------
  31. /* */
  32. dsGenFileList::dsGenFileList() : IO(0), Type(Tree)
  33. {
  34. }
  35. /*}}}*/
  36. // GenFileList::~dsGenFileList - Destructor /*{{{*/
  37. // ---------------------------------------------------------------------
  38. /* */
  39. dsGenFileList::~dsGenFileList()
  40. {
  41. }
  42. /*}}}*/
  43. // GenFileList::Go - Generate the list /*{{{*/
  44. // ---------------------------------------------------------------------
  45. /* This invokes the proper recursive directory scanner to build the file
  46. names. Depth and Breath use a queue */
  47. bool dsGenFileList::Go(string Base,dsFList::IO &IO)
  48. {
  49. // Setup the queues and store the current directory
  50. string StartDir = SafeGetCWD();
  51. Queue.erase(Queue.begin(),Queue.end());
  52. DelayQueue.erase(Queue.begin(),Queue.end());
  53. struct stat St;
  54. if (stat(Base.c_str(),&St) != 0)
  55. return _error->Errno("stat","Could not stat the base directory");
  56. // Begin
  57. this->IO = &IO;
  58. IO.Header.Write(IO);
  59. switch (Type)
  60. {
  61. case Depth:
  62. {
  63. // Change to the base directory
  64. if (chdir(Base.c_str()) != 0)
  65. return _error->Errno("chdir","Could not change to %s",Base.c_str());
  66. Base = SafeGetCWD();
  67. char Cwd[1024];
  68. Cwd[0] = 0;
  69. if (DirDepthFirst(Cwd) == false)
  70. {
  71. chdir(StartDir.c_str());
  72. return false;
  73. }
  74. // Now deal with the delay list
  75. while (DelayQueue.empty() == false)
  76. {
  77. // Get the first delayed directory
  78. string Dir = DelayQueue.front();
  79. DelayQueue.pop_front();
  80. // Change to it and emit it.
  81. strcpy(Cwd,Dir.c_str());
  82. chdir(Base.c_str());
  83. chdir(Cwd);
  84. if (DirDepthFirst(Cwd) == false)
  85. {
  86. chdir(StartDir.c_str());
  87. return false;
  88. }
  89. }
  90. break;
  91. }
  92. case Tree:
  93. case Breadth:
  94. {
  95. // Change to the base directory
  96. if (chdir(Base.c_str()) != 0)
  97. return _error->Errno("chdir","Could not change to %s",Base.c_str());
  98. Base = SafeGetCWD();
  99. Queue.push_back("");
  100. while (Queue.empty() == false || DelayQueue.empty() == false)
  101. {
  102. if (DirTree() == false)
  103. {
  104. chdir(StartDir.c_str());
  105. return false;
  106. }
  107. chdir(Base.c_str());
  108. }
  109. break;
  110. }
  111. default:
  112. return _error->Error("Internal Error");
  113. };
  114. chdir(StartDir.c_str());
  115. dsFList::Trailer Trail;
  116. return Trail.Write(IO);
  117. }
  118. /*}}}*/
  119. // GenFileList::DirDepthFirst - Depth first directory ordering /*{{{*/
  120. // ---------------------------------------------------------------------
  121. /* */
  122. bool dsGenFileList::DirDepthFirst(char *CurDir)
  123. {
  124. // Scan the directory, first pass is to descend into the sub directories
  125. DIR *DirSt = opendir(".");
  126. if (DirSt == 0)
  127. return _error->Errno("opendir","Unable to open direcotry %s",CurDir);
  128. struct dirent *Ent;
  129. bool EmittedThis = false;
  130. struct stat St;
  131. while ((Ent = readdir(DirSt)) != 0)
  132. {
  133. // Skip . and ..
  134. if (strcmp(Ent->d_name,".") == 0 ||
  135. strcmp(Ent->d_name,"..") == 0)
  136. continue;
  137. if (lstat(Ent->d_name,&St) != 0)
  138. {
  139. closedir(DirSt);
  140. return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
  141. }
  142. // it is a directory
  143. if (S_ISDIR(St.st_mode) != 0)
  144. {
  145. char S[1024];
  146. snprintf(S,sizeof(S),"%s/",Ent->d_name);
  147. // Check the Filter
  148. if (Filter.Test(CurDir,S) == false)
  149. continue;
  150. // Emit a directory marker record for this directory
  151. if (EmittedThis == false)
  152. {
  153. EmittedThis = true;
  154. if (lstat(".",&St) != 0)
  155. {
  156. closedir(DirSt);
  157. return _error->Errno("stat","Could not stat %s",CurDir);
  158. }
  159. if (DirectoryMarker(CurDir,St) == false)
  160. {
  161. closedir(DirSt);
  162. return false;
  163. }
  164. }
  165. // Check the delay filter
  166. if (PreferFilter.Test(CurDir,S) == false)
  167. {
  168. snprintf(S,sizeof(S),"%s%s/",CurDir,Ent->d_name);
  169. DelayQueue.push_back(S);
  170. continue;
  171. }
  172. // Append the new directory to CurDir and decend
  173. char *End = CurDir + strlen(CurDir);
  174. strcat(End,S);
  175. if (chdir(S) != 0)
  176. {
  177. closedir(DirSt);
  178. return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
  179. }
  180. // Recurse
  181. if (DirDepthFirst(CurDir) == false)
  182. {
  183. closedir(DirSt);
  184. return false;
  185. }
  186. if (chdir("..") != 0)
  187. {
  188. closedir(DirSt);
  189. return _error->Errno("chdir","Could not chdir to %s%s",CurDir,S);
  190. }
  191. // Chop off the directory we added to the current dir
  192. *End = 0;
  193. }
  194. }
  195. rewinddir(DirSt);
  196. // Begin emitting this directory
  197. if (lstat(".",&St) != 0)
  198. {
  199. closedir(DirSt);
  200. return _error->Errno("stat","Could not stat %s",CurDir);
  201. }
  202. if (EnterDir(CurDir,St) == false)
  203. {
  204. closedir(DirSt);
  205. return false;
  206. }
  207. while ((Ent = readdir(DirSt)) != 0)
  208. {
  209. // Skip . and ..
  210. if (strcmp(Ent->d_name,".") == 0 ||
  211. strcmp(Ent->d_name,"..") == 0)
  212. continue;
  213. struct stat St;
  214. if (lstat(Ent->d_name,&St) != 0)
  215. {
  216. closedir(DirSt);
  217. return _error->Errno("stat","Could not stat %s%s",CurDir,Ent->d_name);
  218. }
  219. // it is a directory
  220. if (S_ISDIR(St.st_mode) != 0)
  221. {
  222. char S[1024];
  223. snprintf(S,sizeof(S),"%s/",Ent->d_name);
  224. // Check the Filter
  225. if (Filter.Test(CurDir,S) == false)
  226. continue;
  227. }
  228. else
  229. {
  230. // Check the Filter
  231. if (Filter.Test(CurDir,Ent->d_name) == false)
  232. continue;
  233. }
  234. if (DoFile(CurDir,Ent->d_name,St) == false)
  235. {
  236. closedir(DirSt);
  237. return false;
  238. }
  239. }
  240. closedir(DirSt);
  241. if (LeaveDir(CurDir) == false)
  242. return false;
  243. return true;
  244. }
  245. /*}}}*/
  246. // GenFileList::DirTree - Breadth/Tree directory ordering /*{{{*/
  247. // ---------------------------------------------------------------------
  248. /* Breadth ordering does all of the dirs at each depth before proceeding
  249. to the next depth. We just treat the list as a queue to get this
  250. effect. Tree ordering does things in a more normal recursive fashion,
  251. we treat the queue as a stack to get that effect. */
  252. bool dsGenFileList::DirTree()
  253. {
  254. string Dir;
  255. if (Queue.empty() == false)
  256. {
  257. Dir = Queue.front();
  258. Queue.pop_front();
  259. }
  260. else
  261. {
  262. Dir = DelayQueue.front();
  263. DelayQueue.pop_front();
  264. }
  265. struct stat St;
  266. if (Dir.empty() == false && chdir(Dir.c_str()) != 0 || stat(".",&St) != 0)
  267. return _error->Errno("chdir","Could not change to %s",Dir.c_str());
  268. if (EnterDir(Dir.c_str(),St) == false)
  269. return false;
  270. // Scan the directory
  271. DIR *DirSt = opendir(".");
  272. if (DirSt == 0)
  273. return _error->Errno("opendir","Unable to open direcotry %s",Dir.c_str());
  274. struct dirent *Ent;
  275. while ((Ent = readdir(DirSt)) != 0)
  276. {
  277. // Skip . and ..
  278. if (strcmp(Ent->d_name,".") == 0 ||
  279. strcmp(Ent->d_name,"..") == 0)
  280. continue;
  281. if (lstat(Ent->d_name,&St) != 0)
  282. {
  283. closedir(DirSt);
  284. return _error->Errno("stat","Could not stat %s%s",Dir.c_str(),Ent->d_name);
  285. }
  286. // It is a directory
  287. if (S_ISDIR(St.st_mode) != 0)
  288. {
  289. char S[1024];
  290. snprintf(S,sizeof(S),"%s/",Ent->d_name);
  291. // Check the Filter
  292. if (Filter.Test(Dir.c_str(),S) == false)
  293. continue;
  294. // Check the delay filter
  295. if (PreferFilter.Test(Dir.c_str(),S) == false)
  296. {
  297. snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
  298. if (Type == Tree)
  299. DelayQueue.push_front(S);
  300. else
  301. DelayQueue.push_back(S);
  302. continue;
  303. }
  304. snprintf(S,sizeof(S),"%s%s/",Dir.c_str(),Ent->d_name);
  305. if (Type == Tree)
  306. Queue.push_front(S);
  307. else
  308. Queue.push_back(S);
  309. }
  310. else
  311. {
  312. // Check the Filter
  313. if (Filter.Test(Dir.c_str(),Ent->d_name) == false)
  314. continue;
  315. }
  316. if (DoFile(Dir.c_str(),Ent->d_name,St) == false)
  317. {
  318. closedir(DirSt);
  319. return false;
  320. }
  321. }
  322. closedir(DirSt);
  323. if (LeaveDir(Dir.c_str()) == false)
  324. return false;
  325. return true;
  326. }
  327. /*}}}*/
  328. // GenFileList::EnterDir - Called when a directory is entered /*{{{*/
  329. // ---------------------------------------------------------------------
  330. /* This is called to start a directory block the current working dir
  331. should be set to the directory entered. This emits the directory start
  332. record */
  333. bool dsGenFileList::EnterDir(const char *Dir,struct stat const &St)
  334. {
  335. if (Visit(Dir,0,St) != 0)
  336. return false;
  337. dsFList::Directory D;
  338. D.Tag = dsFList::tDirStart;
  339. D.ModTime = St.st_mtime - IO->Header.Epoch;
  340. D.Permissions = St.st_mode & ~S_IFMT;
  341. D.Name = Dir;
  342. return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) &&
  343. D.Write(*IO);
  344. }
  345. /*}}}*/
  346. // GenFileList::LeaveDir - Called when a directory is left /*{{{*/
  347. // ---------------------------------------------------------------------
  348. /* Don't do anything for now */
  349. bool dsGenFileList::LeaveDir(const char *Dir)
  350. {
  351. return true;
  352. }
  353. /*}}}*/
  354. // GenFileList::DirectoryMarker - Called when a dir is skipped /*{{{*/
  355. // ---------------------------------------------------------------------
  356. /* This is used by the depth first ordering, when a dir is temporarily
  357. skipped over this function is called to emit a marker */
  358. bool dsGenFileList::DirectoryMarker(const char *Dir,
  359. struct stat const &St)
  360. {
  361. dsFList::Directory D;
  362. D.Tag = dsFList::tDirMarker;
  363. D.ModTime = St.st_mtime - IO->Header.Epoch;
  364. D.Permissions = St.st_mode & ~S_IFMT;
  365. D.Name = Dir;
  366. return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) &&
  367. D.Write(*IO);
  368. }
  369. /*}}}*/
  370. // GenFileList::DoFile - This does all other items in a directory /*{{{*/
  371. // ---------------------------------------------------------------------
  372. /* The different file types are emitted as perscribed by the file list
  373. document */
  374. bool dsGenFileList::DoFile(const char *Dir,const char *File,
  375. struct stat const &St)
  376. {
  377. int Res = Visit(Dir,File,St);
  378. if (Res < 0)
  379. return false;
  380. if (Res > 0)
  381. return true;
  382. // Regular file
  383. if (S_ISREG(St.st_mode) != 0)
  384. {
  385. dsFList::NormalFile F;
  386. F.Tag = dsFList::tNormalFile;
  387. F.ModTime = St.st_mtime - IO->Header.Epoch;
  388. F.Permissions = St.st_mode & ~S_IFMT;
  389. F.Name = File;
  390. F.Size = St.st_size;
  391. if (EmitOwner(St,F.User,F.Group,F.Tag,dsFList::NormalFile::FlOwner) == false)
  392. return false;
  393. // See if we need to emit rsync checksums
  394. if (NeedsRSync(Dir,File,F) == true)
  395. {
  396. dsFList::RSyncChecksum Ck;
  397. if (EmitRSync(Dir,File,St,F,Ck) == false)
  398. return false;
  399. // Write out the file record, the checksums and the end marker
  400. return F.Write(*IO) && Ck.Write(*IO);
  401. }
  402. else
  403. {
  404. if (EmitMD5(Dir,File,St,F.MD5,F.Tag,
  405. dsFList::NormalFile::FlMD5) == false)
  406. return false;
  407. return F.Write(*IO);
  408. }
  409. }
  410. // Directory
  411. if (S_ISDIR(St.st_mode) != 0)
  412. {
  413. dsFList::Directory D;
  414. D.Tag = dsFList::tDirectory;
  415. D.ModTime = St.st_mtime - IO->Header.Epoch;
  416. D.Permissions = St.st_mode & ~S_IFMT;
  417. D.Name = File;
  418. return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::Directory::FlOwner) &&
  419. D.Write(*IO);
  420. }
  421. // Link
  422. if (S_ISLNK(St.st_mode) != 0)
  423. {
  424. dsFList::Symlink L;
  425. L.Tag = dsFList::tSymlink;
  426. L.ModTime = St.st_mtime - IO->Header.Epoch;
  427. L.Name = File;
  428. char Buf[1024];
  429. int Res = readlink(File,Buf,sizeof(Buf));
  430. if (Res <= 0)
  431. return _error->Errno("readlink","Unable to read symbolic link");
  432. Buf[Res] = 0;
  433. L.To = Buf;
  434. return EmitOwner(St,L.User,L.Group,L.Tag,dsFList::Symlink::FlOwner) &&
  435. L.Write(*IO);
  436. }
  437. // Block special file
  438. if (S_ISCHR(St.st_mode) != 0 || S_ISBLK(St.st_mode) != 0 ||
  439. S_ISFIFO(St.st_mode) != 0)
  440. {
  441. dsFList::DeviceSpecial D;
  442. D.Tag = dsFList::tDeviceSpecial;
  443. D.ModTime = St.st_mtime - IO->Header.Epoch;
  444. D.Permissions = St.st_mode & ~S_IFMT;
  445. D.Dev = St.st_dev;
  446. D.Name = File;
  447. return EmitOwner(St,D.User,D.Group,D.Tag,dsFList::DeviceSpecial::FlOwner) &&
  448. D.Write(*IO);
  449. }
  450. return _error->Error("File %s%s is not a known type",Dir,File);
  451. }
  452. /*}}}*/
  453. // GenFileList::EmitOwner - Set the entitiy ownership /*{{{*/
  454. // ---------------------------------------------------------------------
  455. /* This emits the necessary UID/GID mapping records and sets the feilds
  456. in */
  457. bool dsGenFileList::EmitOwner(struct stat const &St,unsigned long &UID,
  458. unsigned long &GID,unsigned int Tag,
  459. unsigned int Flag)
  460. {
  461. if ((IO->Header.Flags[Tag] & Flag) != Flag)
  462. return true;
  463. return _error->Error("UID/GID storage is not supported yet");
  464. }
  465. /*}}}*/
  466. // GenFileList::EmitMd5 - Generate the md5 hash for the file /*{{{*/
  467. // ---------------------------------------------------------------------
  468. /* This uses the MD5 class to generate the md5 hash for the entry. */
  469. bool dsGenFileList::EmitMD5(const char *Dir,const char *File,
  470. struct stat const &St,unsigned char MD5[16],
  471. unsigned int Tag,unsigned int Flag)
  472. {
  473. if ((IO->Header.Flags[Tag] & Flag) != Flag)
  474. return true;
  475. // Open the file
  476. MD5Summation Sum;
  477. FileFd Fd(File,FileFd::ReadOnly);
  478. if (_error->PendingError() == true)
  479. return _error->Error("MD5 generation failed for %s%s",Dir,File);
  480. if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
  481. return _error->Error("MD5 generation failed for %s%s",Dir,File);
  482. Sum.Result().Value(MD5);
  483. return true;
  484. }
  485. /*}}}*/
  486. // GenFileList::EmitRSync - Emit a RSync checksum record /*{{{*/
  487. // ---------------------------------------------------------------------
  488. /* This just generates the checksum into the memory structure. */
  489. bool dsGenFileList::EmitRSync(const char *Dir,const char *File,
  490. struct stat const &St,dsFList::NormalFile &F,
  491. dsFList::RSyncChecksum &Ck)
  492. {
  493. FileFd Fd(File,FileFd::ReadOnly);
  494. if (_error->PendingError() == true)
  495. return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
  496. if (GenerateRSync(Fd,Ck,F.MD5) == false)
  497. return _error->Error("RSync Checksum generation failed for %s%s",Dir,File);
  498. return true;
  499. }
  500. /*}}}*/