compare.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. // -*- mode: cpp; mode: fold -*-
  2. // Description /*{{{*/
  3. // $Id: compare.cc,v 1.6 1999/12/26 06:59:00 jgg Exp $
  4. /* ######################################################################
  5. Compare a file list with a local directory
  6. The first step in the compare is to read the names of each entry
  7. in the local directory into ram. This list is the first step to
  8. creating a delete list. Next we begin scanning the file list, checking
  9. each entry against the dir contents, if a match is found it is removed
  10. from the dir list and then stat'd to verify against the file list
  11. contents. If no match is found then the entry is marked for download.
  12. When done the local directory in ram will only contain entries that
  13. need to be erased.
  14. ##################################################################### */
  15. /*}}}*/
  16. // Include files /*{{{*/
  17. #ifdef __GNUG__
  18. #pragma implementation "dsync/compare.h"
  19. #endif
  20. #include <dsync/compare.h>
  21. #include <dsync/error.h>
  22. #include <dsync/fileutl.h>
  23. #include <dsync/md5.h>
  24. #include <sys/types.h>
  25. #include <sys/stat.h>
  26. #include <unistd.h>
  27. #include <dirent.h>
  28. #include <utime.h>
  29. #include <stdio.h>
  30. /*}}}*/
  31. // DirCompre::dsDirCompare - Constructor /*{{{*/
  32. // ---------------------------------------------------------------------
  33. /* */
  34. dsDirCompare::dsDirCompare() : IndexSize(0), IndexAlloc(0), Indexes(0),
  35. NameAlloc(0), Names(0), Verify(true), HashLevel(Md5Date)
  36. {
  37. IndexAlloc = 1000;
  38. Indexes = (unsigned int *)malloc(sizeof(*Indexes)*IndexAlloc);
  39. NameAlloc = 4096*5;
  40. Names = (char *)malloc(sizeof(*Names)*NameAlloc);
  41. if (Names == 0 || Indexes == 0)
  42. _error->Error("Cannot allocate memory");
  43. }
  44. /*}}}*/
  45. // DirCompare::~dsDirCompare - Destructor /*{{{*/
  46. // ---------------------------------------------------------------------
  47. /* */
  48. dsDirCompare::~dsDirCompare()
  49. {
  50. free(Names);
  51. free(Indexes);
  52. }
  53. /*}}}*/
  54. // DirCompare::LoadDir - Load all the names in the directory /*{{{*/
  55. // ---------------------------------------------------------------------
  56. /* Such in every name in the directory, we store them as a packed, indexed
  57. array of strings */
  58. bool dsDirCompare::LoadDir()
  59. {
  60. // Scan the directory
  61. DIR *DirSt = opendir(".");
  62. if (DirSt == 0)
  63. return _error->Errno("opendir","Unable to open directory %s",SafeGetCWD().c_str());
  64. struct dirent *Ent;
  65. IndexSize = 0;
  66. char *End = Names + 1;
  67. while ((Ent = readdir(DirSt)) != 0)
  68. {
  69. // Skip . and ..
  70. if (strcmp(Ent->d_name,".") == 0 ||
  71. strcmp(Ent->d_name,"..") == 0)
  72. continue;
  73. // Grab some more bytes in the name allocation
  74. if ((unsigned)(NameAlloc - (End - Names)) <= strlen(Ent->d_name)+1)
  75. {
  76. unsigned long OldEnd = End - Names;
  77. char *New = (char *)realloc(Names,sizeof(*Names)*NameAlloc + 4*4096);
  78. if (New == 0)
  79. {
  80. closedir(DirSt);
  81. return _error->Error("Cannot allocate memory");
  82. }
  83. Names = New;
  84. NameAlloc += 4*4096;
  85. End = Names + OldEnd;
  86. }
  87. // Grab some more bytes in the index allocation
  88. if (IndexSize >= IndexAlloc)
  89. {
  90. unsigned int *New = (unsigned int *)realloc(Indexes,
  91. sizeof(*Indexes)*IndexAlloc + 1000);
  92. if (New == 0)
  93. {
  94. closedir(DirSt);
  95. return _error->Error("Cannot allocate memory");
  96. }
  97. Indexes = New;
  98. IndexAlloc += 4*4096;
  99. }
  100. // Store it
  101. Indexes[IndexSize] = End - Names;
  102. IndexSize++;
  103. strcpy(End,Ent->d_name);
  104. End += strlen(End) + 1;
  105. }
  106. closedir(DirSt);
  107. return true;
  108. }
  109. /*}}}*/
  110. // DirCompare::Process - Process the file list stream /*{{{*/
  111. // ---------------------------------------------------------------------
  112. /* This scans over the dirs from the IO and decides what to do with them */
  113. bool dsDirCompare::Process(string Base,dsFList::IO &IO)
  114. {
  115. // Setup the queues and store the current directory
  116. string StartDir = SafeGetCWD();
  117. // Change to the base directory
  118. if (chdir(Base.c_str()) != 0)
  119. return _error->Errno("chdir","Could not change to %s",Base.c_str());
  120. Base = SafeGetCWD();
  121. this->Base = Base;
  122. string CurDir;
  123. dsFList List;
  124. bool Missing = false;
  125. while (List.Step(IO) == true)
  126. {
  127. if (Visit(List,CurDir) == false)
  128. return false;
  129. switch (List.Tag)
  130. {
  131. // Handle a forward directory reference
  132. case dsFList::tDirMarker:
  133. {
  134. // Ingore the root directory
  135. if (List.Entity->Name.empty() == true)
  136. continue;
  137. char S[1024];
  138. snprintf(S,sizeof(S),"%s%s",Base.c_str(),List.Entity->Name.c_str());
  139. /* We change the path to be absolute for the benifit of the
  140. routines below */
  141. List.Entity->Name = S;
  142. // Stat the marker dir
  143. struct stat St;
  144. bool Res;
  145. if (lstat(S,&St) != 0)
  146. Res = Fetch(List,string(),0);
  147. else
  148. Res = Fetch(List,string(),&St);
  149. if (Res == false)
  150. return false;
  151. break;
  152. }
  153. // Start a directory
  154. case dsFList::tDirStart:
  155. {
  156. if (DoDelete(CurDir) == false)
  157. return false;
  158. if (chdir(Base.c_str()) != 0)
  159. return _error->Errno("chdir","Could not change to %s",Base.c_str());
  160. CurDir = List.Dir.Name;
  161. Missing = false;
  162. IndexSize = 0;
  163. if (List.Dir.Name.empty() == false)
  164. {
  165. /* Instead of erroring out we just mark them as missing and
  166. do not re-stat. This is to support the verify mode, the
  167. actual downloader should never get this. */
  168. if (chdir(List.Dir.Name.c_str()) != 0)
  169. {
  170. if (Verify == false)
  171. return _error->Errno("chdir","Unable to cd to %s%s.",Base.c_str(),List.Dir.Name.c_str());
  172. Missing = true;
  173. }
  174. }
  175. if (Missing == false)
  176. LoadDir();
  177. break;
  178. }
  179. // Finalize the directory
  180. case dsFList::tDirEnd:
  181. {
  182. if (DoDelete(CurDir) == false)
  183. return false;
  184. IndexSize = 0;
  185. if (chdir(Base.c_str()) != 0)
  186. return _error->Errno("chdir","Could not change to %s",Base.c_str());
  187. break;
  188. }
  189. }
  190. // We have some sort of normal entity
  191. if (List.Entity != 0 && List.Tag != dsFList::tDirMarker &&
  192. List.Tag != dsFList::tDirStart)
  193. {
  194. // See if it exists, if it does then stat it
  195. bool Res = true;
  196. if (Missing == true || DirExists(List.Entity->Name) == false)
  197. Res = Fetch(List,CurDir,0);
  198. else
  199. {
  200. struct stat St;
  201. if (lstat(List.Entity->Name.c_str(),&St) != 0)
  202. Res = Fetch(List,CurDir,0);
  203. else
  204. Res = Fetch(List,CurDir,&St);
  205. }
  206. if (Res == false)
  207. return false;
  208. }
  209. // Fini
  210. if (List.Tag == dsFList::tTrailer)
  211. {
  212. if (DoDelete(CurDir) == false)
  213. return false;
  214. return true;
  215. }
  216. }
  217. return false;
  218. }
  219. /*}}}*/
  220. // DirCompare::DoDelete - Delete files in the delete list /*{{{*/
  221. // ---------------------------------------------------------------------
  222. /* The delete list is created by removing names that were found till only
  223. extra names remain */
  224. bool dsDirCompare::DoDelete(string Dir)
  225. {
  226. for (unsigned int I = 0; I != IndexSize; I++)
  227. {
  228. if (Indexes[I] == 0)
  229. continue;
  230. if (Delete(Dir,Names + Indexes[I]) == false)
  231. return false;
  232. }
  233. return true;
  234. }
  235. /*}}}*/
  236. // DirCompare::Fetch - Fetch an entity /*{{{*/
  237. // ---------------------------------------------------------------------
  238. /* This examins an entry to see what sort of fetch should be done. There
  239. are three sorts,
  240. New - There is no existing data
  241. Changed - There is existing data
  242. Meta - The data is fine but the timestamp/owner/perms might not be */
  243. bool dsDirCompare::Fetch(dsFList &List,string Dir,struct stat *St)
  244. {
  245. if (List.Tag != dsFList::tNormalFile && List.Tag != dsFList::tDirectory &&
  246. List.Tag != dsFList::tSymlink && List.Tag != dsFList::tDeviceSpecial &&
  247. List.Tag != dsFList::tDirMarker)
  248. return _error->Error("dsDirCompare::Fetch called for an entity "
  249. "that it does not understand");
  250. // This is a new entitiy
  251. if (St == 0)
  252. return GetNew(List,Dir);
  253. /* Check the types for a mis-match, if they do not match then
  254. we have to erase the entity and get a new one */
  255. if ((S_ISREG(St->st_mode) != 0 && List.Tag != dsFList::tNormalFile) ||
  256. (S_ISDIR(St->st_mode) != 0 && (List.Tag != dsFList::tDirectory &&
  257. List.Tag != dsFList::tDirMarker)) ||
  258. (S_ISLNK(St->st_mode) != 0 && List.Tag != dsFList::tSymlink) ||
  259. ((S_ISCHR(St->st_mode) != 0 || S_ISBLK(St->st_mode) != 0 ||
  260. S_ISFIFO(St->st_mode) != 0) && List.Tag != dsFList::tDeviceSpecial))
  261. {
  262. return Delete(Dir,List.Entity->Name.c_str(),true) && GetNew(List,Dir);
  263. }
  264. // First we check permissions and mod time
  265. bool ModTime = (signed)(List.Entity->ModTime + List.Head.Epoch) == St->st_mtime;
  266. bool Perm = true;
  267. if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
  268. Perm = List.Entity->Permissions == (unsigned)(St->st_mode & ~S_IFMT);
  269. // Normal file
  270. if (List.Tag == dsFList::tNormalFile)
  271. {
  272. // Size mismatch is an immedate fail
  273. if (List.NFile.Size != (unsigned)St->st_size)
  274. return GetChanged(List,Dir);
  275. // Try to check the stored MD5
  276. if (HashLevel == Md5Always ||
  277. (HashLevel == Md5Date && ModTime == false))
  278. {
  279. if ((List.Head.Flags[List.Tag] & dsFList::NormalFile::FlMD5) != 0)
  280. {
  281. if (CheckHash(List,Dir,List.NFile.MD5) == true)
  282. return FixMeta(List,Dir,*St);
  283. else
  284. return GetChanged(List,Dir);
  285. }
  286. }
  287. // Look at the modification time
  288. if (ModTime == true)
  289. return FixMeta(List,Dir,*St);
  290. return GetChanged(List,Dir);
  291. }
  292. // Check symlinks
  293. if (List.Tag == dsFList::tSymlink)
  294. {
  295. char Buf[1024];
  296. int Res = readlink(List.Entity->Name.c_str(),Buf,sizeof(Buf));
  297. if (Res > 0)
  298. Buf[Res] = 0;
  299. // Link is invalid
  300. if (Res < 0 || List.SLink.To != Buf)
  301. return GetNew(List,Dir);
  302. return FixMeta(List,Dir,*St);
  303. }
  304. // Check directories and dev special files
  305. if (List.Tag == dsFList::tDirectory || List.Tag == dsFList::tDeviceSpecial ||
  306. List.Tag == dsFList::tDirMarker)
  307. return FixMeta(List,Dir,*St);
  308. return true;
  309. }
  310. /*}}}*/
  311. // DirCompare::DirExists - See if the entry exists in our dir table /*{{{*/
  312. // ---------------------------------------------------------------------
  313. /* We look at the dir table for one that exists */
  314. bool dsDirCompare::DirExists(string Name)
  315. {
  316. for (unsigned int I = 0; I != IndexSize; I++)
  317. {
  318. if (Indexes[I] == 0)
  319. continue;
  320. if (Name == Names + Indexes[I])
  321. {
  322. Indexes[I] = 0;
  323. return true;
  324. }
  325. }
  326. return false;
  327. }
  328. /*}}}*/
  329. // DirCompare::CheckHash - Check the MD5 of a entity /*{{{*/
  330. // ---------------------------------------------------------------------
  331. /* This is invoked to see of the local file we have is the file the remote
  332. says we should have. */
  333. bool dsDirCompare::CheckHash(dsFList &List,string Dir,unsigned char MD5[16])
  334. {
  335. // Open the file
  336. MD5Summation Sum;
  337. FileFd Fd(List.Entity->Name,FileFd::ReadOnly);
  338. if (_error->PendingError() == true)
  339. return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
  340. List.Entity->Name.c_str());
  341. if (Sum.AddFD(Fd.Fd(),Fd.Size()) == false)
  342. return _error->Error("MD5 generation failed for %s%s",Dir.c_str(),
  343. List.Entity->Name.c_str());
  344. unsigned char MyMD5[16];
  345. Sum.Result().Value(MyMD5);
  346. return memcmp(MD5,MyMD5,sizeof(MyMD5)) == 0;
  347. }
  348. /*}}}*/
  349. // DirCompare::FixMeta - Fix timestamps, ownership and permissions /*{{{*/
  350. // ---------------------------------------------------------------------
  351. /* This checks if it is necessary to correct the timestamps, ownership and
  352. permissions of an entity */
  353. bool dsDirCompare::FixMeta(dsFList &List,string Dir,struct stat &St)
  354. {
  355. // Check the mod time
  356. if (List.Tag != dsFList::tSymlink)
  357. {
  358. if ((signed)(List.Entity->ModTime + List.Head.Epoch) != St.st_mtime)
  359. if (SetTime(List,Dir) == false)
  360. return false;
  361. // Check the permissions
  362. if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
  363. {
  364. if (List.Entity->Permissions != (St.st_mode & ~S_IFMT))
  365. if (SetPerm(List,Dir) == false)
  366. return false;
  367. }
  368. }
  369. return true;
  370. }
  371. /*}}}*/
  372. // DirCorrect::GetNew - Create a new entry /*{{{*/
  373. // ---------------------------------------------------------------------
  374. /* We cannot create files but we do generate everything else. */
  375. bool dsDirCorrect::GetNew(dsFList &List,string Dir)
  376. {
  377. if (List.Tag == dsFList::tDirectory)
  378. {
  379. unsigned long PermDir = 0666;
  380. if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
  381. PermDir = List.Entity->Permissions;
  382. if (mkdir(List.Entity->Name.c_str(),PermDir) != 0)
  383. return _error->Errno("mkdir","Unable to create directory, %s%s",
  384. Dir.c_str(),List.Entity->Name.c_str());
  385. // Stat the newly created file for FixMeta's benifit
  386. struct stat St;
  387. if (lstat(List.Entity->Name.c_str(),&St) != 0)
  388. return _error->Errno("stat","Unable to stat directory, %s%s",
  389. Dir.c_str(),List.Entity->Name.c_str());
  390. return FixMeta(List,Dir,St);
  391. }
  392. if (List.Tag == dsFList::tSymlink)
  393. {
  394. if (symlink(List.SLink.To.c_str(),List.Entity->Name.c_str()) != 0)
  395. return _error->Errno("symlink","Unable to create symlink, %s%s",
  396. Dir.c_str(),List.Entity->Name.c_str());
  397. // Stat the newly created file for FixMeta's benifit
  398. struct stat St;
  399. if (lstat(List.Entity->Name.c_str(),&St) != 0)
  400. return _error->Errno("stat","Unable to stat directory, %s%s",
  401. Dir.c_str(),List.Entity->Name.c_str());
  402. return FixMeta(List,Dir,St);
  403. }
  404. if (List.Tag == dsFList::tDeviceSpecial)
  405. {
  406. unsigned long PermDev;
  407. if ((List.Head.Flags[List.Tag] & dsFList::DirEntity::FlPerm) != 0)
  408. PermDev = List.Entity->Permissions;
  409. else
  410. return _error->Error("Corrupted file list");
  411. if (mknod(List.Entity->Name.c_str(),PermDev,List.DevSpecial.Dev) != 0)
  412. return _error->Errno("mkdir","Unable to create directory, %s%s",
  413. Dir.c_str(),List.Entity->Name.c_str());
  414. // Stat the newly created file for FixMeta's benifit
  415. struct stat St;
  416. if (lstat(List.Entity->Name.c_str(),&St) != 0)
  417. return _error->Errno("stat","Unable to stat directory, %s%s",
  418. Dir.c_str(),List.Entity->Name.c_str());
  419. return FixMeta(List,Dir,St);
  420. }
  421. }
  422. /*}}}*/
  423. // DirCorrect::DirUnlink - Unlink a directory /*{{{*/
  424. // ---------------------------------------------------------------------
  425. /* This just recursively unlinks stuff */
  426. bool dsDirCorrect::DirUnlink(const char *Path)
  427. {
  428. // Record what dir we were in
  429. struct stat Dir;
  430. if (lstat(".",&Dir) != 0)
  431. return _error->Errno("lstat","Unable to stat .!");
  432. if (chdir(Path) != 0)
  433. return _error->Errno("chdir","Unable to change to %s",Path);
  434. // Scan the directory
  435. DIR *DirSt = opendir(".");
  436. if (DirSt == 0)
  437. {
  438. chdir("..");
  439. return _error->Errno("opendir","Unable to open directory %s",Path);
  440. }
  441. // Erase this directory
  442. struct dirent *Ent;
  443. while ((Ent = readdir(DirSt)) != 0)
  444. {
  445. // Skip . and ..
  446. if (strcmp(Ent->d_name,".") == 0 ||
  447. strcmp(Ent->d_name,"..") == 0)
  448. continue;
  449. struct stat St;
  450. if (lstat(Ent->d_name,&St) != 0)
  451. return _error->Errno("stat","Unable to stat %s",Ent->d_name);
  452. if (S_ISDIR(St.st_mode) == 0)
  453. {
  454. // Try to unlink the file
  455. if (unlink(Ent->d_name) != 0)
  456. {
  457. chdir("..");
  458. return _error->Errno("unlink","Unable to remove file %s",Ent->d_name);
  459. }
  460. }
  461. else
  462. {
  463. if (DirUnlink(Ent->d_name) == false)
  464. {
  465. chdir("..");
  466. closedir(DirSt);
  467. return false;
  468. }
  469. }
  470. }
  471. closedir(DirSt);
  472. chdir("..");
  473. /* Make sure someone didn't screw with the directory layout while we
  474. were erasing */
  475. struct stat Dir2;
  476. if (lstat(".",&Dir2) != 0)
  477. return _error->Errno("lstat","Unable to stat .!");
  478. if (Dir2.st_ino != Dir.st_ino || Dir2.st_dev != Dir.st_dev)
  479. return _error->Error("Hey! Someone is fiddling with the dir tree as I erase it!");
  480. if (rmdir(Path) != 0)
  481. return _error->Errno("rmdir","Unable to remove directory %s",Ent->d_name);
  482. return true;
  483. }
  484. /*}}}*/
  485. // DirCorrect::Delete - Delete an entry /*{{{*/
  486. // ---------------------------------------------------------------------
  487. /* This obliterates an entity - recursively, use with caution. */
  488. bool dsDirCorrect::Delete(string Dir,const char *Name,bool Now)
  489. {
  490. struct stat St;
  491. if (lstat(Name,&St) != 0)
  492. return _error->Errno("stat","Unable to stat %s%s",Dir.c_str(),Name);
  493. if (S_ISDIR(St.st_mode) == 0)
  494. {
  495. if (unlink(Name) != 0)
  496. return _error->Errno("unlink","Unable to remove %s%s",Dir.c_str(),Name);
  497. }
  498. else
  499. {
  500. if (DirUnlink(Name) == false)
  501. return _error->Error("Unable to erase directory %s%s",Dir.c_str(),Name);
  502. }
  503. return true;
  504. }
  505. /*}}}*/
  506. // DirCorrect::GetChanged - Get a changed entry /*{{{*/
  507. // ---------------------------------------------------------------------
  508. /* This is only called for normal files, we cannot do anything here. */
  509. bool dsDirCorrect::GetChanged(dsFList &List,string Dir)
  510. {
  511. return true;
  512. }
  513. /*}}}*/
  514. // DirCorrect::SetTime - Change the timestamp /*{{{*/
  515. // ---------------------------------------------------------------------
  516. /* This fixes the mod time of the file */
  517. bool dsDirCorrect::SetTime(dsFList &List,string Dir)
  518. {
  519. struct utimbuf Time;
  520. Time.actime = Time.modtime = List.Entity->ModTime + List.Head.Epoch;
  521. if (utime(List.Entity->Name.c_str(),&Time) != 0)
  522. return _error->Errno("utimes","Unable to change mod time for %s%s",
  523. Dir.c_str(),List.Entity->Name.c_str());
  524. return true;
  525. }
  526. /*}}}*/
  527. // DirCorrect::SetPerm - Change the permissions /*{{{*/
  528. // ---------------------------------------------------------------------
  529. /* This fixes the permissions */
  530. bool dsDirCorrect::SetPerm(dsFList &List,string Dir)
  531. {
  532. if (chmod(List.Entity->Name.c_str(),List.Entity->Permissions) != 0)
  533. return _error->Errno("chmod","Unable to change permissions for %s%s",
  534. Dir.c_str(),List.Entity->Name.c_str());
  535. return true;
  536. }
  537. /*}}}*/
  538. // Dircorrect::SetOwner - Change ownership /*{{{*/
  539. // ---------------------------------------------------------------------
  540. /* This fixes the file ownership */
  541. bool dsDirCorrect::SetOwners(dsFList &List,string Dir)
  542. {
  543. return _error->Error("Ownership is not yet supported");
  544. }
  545. /*}}}*/