versionBackup.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. package hybridBackup
  2. import (
  3. "errors"
  4. "io/ioutil"
  5. "log"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "time"
  10. )
  11. /*
  12. VersionBackup.go
  13. This scirpt file backup the data in the system nightly and create a restore point
  14. for the day just like BRTFS
  15. */
  16. func executeVersionBackup(backupConfig *BackupTask) (string, error) {
  17. //Check if the backup parent root is identical / within backup disk
  18. parentRootAbs, err := filepath.Abs(backupConfig.ParentPath)
  19. if err != nil {
  20. return "", errors.New("Unable to resolve parent disk path")
  21. }
  22. backupRootAbs, err := filepath.Abs(filepath.Join(backupConfig.DiskPath, "/version/"))
  23. if err != nil {
  24. return "", errors.New("Unable to resolve backup disk path")
  25. }
  26. if len(parentRootAbs) >= len(backupRootAbs) {
  27. if parentRootAbs[:len(backupRootAbs)] == backupRootAbs {
  28. //parent root is within backup root. Raise configuration error
  29. log.Println("*HyperBackup* Invalid backup cycle: Parent drive is located inside backup drive")
  30. return "", errors.New("Configuration Error. Skipping backup cycle.")
  31. }
  32. }
  33. todayFolderName := time.Now().Format("2006-01-02")
  34. previousSnapshotName, _ := getPreviousSnapshotName(backupConfig, todayFolderName)
  35. snapshotLocation := filepath.Join(backupConfig.DiskPath, "/version/", todayFolderName)
  36. previousSnapshotLocation := filepath.Join(backupConfig.DiskPath, "/version/", previousSnapshotName)
  37. if !fileExists(snapshotLocation) {
  38. //Create today folder if not exist
  39. os.MkdirAll(snapshotLocation, 0755)
  40. }
  41. /*
  42. Run a three pass compare logic between
  43. 1. source disk and new backup disk to check any new / modified files (created today)
  44. 2. yesterday backup and today backup to check any deleted files (created before, deleted today)
  45. 3. file in today backup disk no longer in the current source disk (created today, deleted today)
  46. */
  47. copiedFileList := []string{}
  48. //First pass: Check if there are any updated file from source and backup it to backup drive
  49. fastWalk(parentRootAbs, func(filename string) error {
  50. if filepath.Base(filename) == "aofs.db" || filepath.Base(filename) == "aofs.db.lock" {
  51. //Reserved filename, skipping
  52. return nil
  53. }
  54. //Get the target paste location
  55. rootAbs, _ := filepath.Abs(backupConfig.ParentPath)
  56. fileAbs, _ := filepath.Abs(filename)
  57. rootAbs = filepath.ToSlash(filepath.Clean(rootAbs))
  58. fileAbs = filepath.ToSlash(filepath.Clean(fileAbs))
  59. relPath := strings.ReplaceAll(fileAbs, rootAbs, "")
  60. fileBackupLocation := filepath.Join(backupConfig.DiskPath, "/version/", todayFolderName, relPath)
  61. yesterdayBackupLocation := filepath.Join(previousSnapshotLocation, relPath)
  62. //Check if the file exists
  63. if !fileExists(fileBackupLocation) && !fileExists(yesterdayBackupLocation) {
  64. //File not exists in both current source and yesterday one. Copy it to the target location
  65. if !isDir(fileBackupLocation) && fileExists(fileBackupLocation+".deleted") {
  66. os.Remove(fileBackupLocation + ".deleted")
  67. }
  68. if !fileExists(filepath.Dir(fileBackupLocation)) {
  69. os.MkdirAll(filepath.Dir(fileBackupLocation), 0755)
  70. }
  71. err = BufferedLargeFileCopy(filename, fileBackupLocation, 4096)
  72. if err != nil {
  73. log.Println("[HybridBackup] Failed to copy file: ", filepath.Base(filename)+". "+err.Error())
  74. }
  75. copiedFileList = append(copiedFileList, fileBackupLocation)
  76. } else if fileExists(yesterdayBackupLocation) {
  77. //The file exists in the last snapshot
  78. //Check if their hash is the same. If no, update it
  79. srcHash, err := getFileHash(fileAbs)
  80. if err != nil {
  81. log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(fileAbs), err.Error(), " Skipping.")
  82. return nil
  83. }
  84. targetHash, err := getFileHash(yesterdayBackupLocation)
  85. if err != nil {
  86. log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(fileBackupLocation), err.Error(), " Skipping.")
  87. return nil
  88. }
  89. if srcHash != targetHash {
  90. //Hash mismatch. Overwrite the file
  91. err = BufferedLargeFileCopy(filename, fileBackupLocation, 4096)
  92. if err != nil {
  93. log.Println("[HybridBackup] Copy Failed for file "+filepath.Base(fileAbs), err.Error(), " Skipping.")
  94. } else {
  95. //No problem. Add this filepath into the list
  96. copiedFileList = append(copiedFileList, fileBackupLocation)
  97. }
  98. }
  99. } else {
  100. //Default case
  101. lastModTime := lastModTime(fileAbs)
  102. if lastModTime > backupConfig.LastCycleTime {
  103. //Check if hash the same
  104. srcHash, err := getFileHash(fileAbs)
  105. if err != nil {
  106. log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(fileAbs), err.Error(), " Skipping.")
  107. return nil
  108. }
  109. targetHash, err := getFileHash(fileBackupLocation)
  110. if err != nil {
  111. log.Println("[HybridBackup] Hash calculation failed for file "+filepath.Base(fileBackupLocation), err.Error(), " Skipping.")
  112. return nil
  113. }
  114. if srcHash != targetHash {
  115. //Hash mismatch. Overwrite the file
  116. err = BufferedLargeFileCopy(filename, fileBackupLocation, 4096)
  117. if err != nil {
  118. log.Println("[HybridBackup] Copy Failed for file "+filepath.Base(fileAbs), err.Error(), " Skipping.")
  119. } else {
  120. //No problem. Add this filepath into the list
  121. copiedFileList = append(copiedFileList, fileBackupLocation)
  122. }
  123. }
  124. }
  125. }
  126. return nil
  127. })
  128. //2nd pass: Check if there are anything exists in the previous backup but no longer exists in the source now
  129. //For case where the file is backed up in previous snapshot but now the file has been removed
  130. if fileExists(previousSnapshotLocation) {
  131. fastWalk(previousSnapshotLocation, func(filename string) error {
  132. //Get the target paste location
  133. rootAbs, _ := filepath.Abs(previousSnapshotLocation)
  134. fileAbs, _ := filepath.Abs(filename)
  135. rootAbs = filepath.ToSlash(filepath.Clean(rootAbs))
  136. fileAbs = filepath.ToSlash(filepath.Clean(fileAbs))
  137. relPath := strings.ReplaceAll(fileAbs, rootAbs, "")
  138. sourcAssumeLocation := filepath.Join(parentRootAbs, relPath)
  139. todaySnapshotLocation := filepath.Join(snapshotLocation, relPath)
  140. if !fileExists(sourcAssumeLocation) {
  141. //File exists in yesterday snapshot but not in the current source
  142. //Assume it has been deleted, create a dummy indicator file
  143. ioutil.WriteFile(todaySnapshotLocation+".deleted", []byte(""), 0755)
  144. }
  145. return nil
  146. })
  147. }
  148. //3rd pass: Check if there are anything (except file with .deleted) in today backup drive that didn't exists in the source drive
  149. //For cases where the backup is applied to overwrite an eariler backup of the same day
  150. fastWalk(snapshotLocation, func(filename string) error {
  151. if filepath.Base(filename) == "aofs.db" || filepath.Base(filename) == "aofs.db.lock" {
  152. //Reserved filename, skipping
  153. return nil
  154. }
  155. if filepath.Ext(filename) == ".deleted" {
  156. //Deleted file marker. Skip this
  157. return nil
  158. }
  159. //Get the target paste location
  160. rootAbs, _ := filepath.Abs(snapshotLocation)
  161. fileAbs, _ := filepath.Abs(filename)
  162. rootAbs = filepath.ToSlash(filepath.Clean(rootAbs))
  163. fileAbs = filepath.ToSlash(filepath.Clean(fileAbs))
  164. relPath := strings.ReplaceAll(fileAbs, rootAbs, "")
  165. sourceAssumedLocation := filepath.Join(parentRootAbs, relPath)
  166. if !fileExists(sourceAssumedLocation) {
  167. //File removed from the source. Delete it from backup as well
  168. os.Remove(filename)
  169. }
  170. return nil
  171. })
  172. return "", nil
  173. }
  174. //Return the previous snapshot for the currentSnspashot
  175. func getPreviousSnapshotName(backupConfig *BackupTask, currentSnapshotName string) (string, error) {
  176. //Resolve the backup root folder
  177. backupRootAbs, err := filepath.Abs(filepath.Join(backupConfig.DiskPath, "/version/"))
  178. if err != nil {
  179. return "", errors.New("Unable to get the previous snapshot directory")
  180. }
  181. //Get the snapshot list and extract the snapshot date from foldername
  182. existingSnapshots := []string{}
  183. files, _ := filepath.Glob(filepath.ToSlash(filepath.Clean(backupRootAbs)) + "/*")
  184. for _, file := range files {
  185. if isDir(file) {
  186. existingSnapshots = append(existingSnapshots, filepath.Base(file))
  187. }
  188. }
  189. if len(existingSnapshots) == 0 {
  190. return "", errors.New("No snapshot found")
  191. }
  192. //Check if the current snapshot exists, if not, return the latest one
  193. previousSnapshotName := ""
  194. if fileExists(filepath.Join(backupRootAbs, currentSnapshotName)) {
  195. //Current snapshot exists. Find the one just above it
  196. lastSnapshotName := existingSnapshots[0]
  197. for _, snapshotName := range existingSnapshots {
  198. if snapshotName == currentSnapshotName {
  199. //This is the correct snapshot name. Get the last one as previous snapshot
  200. previousSnapshotName = lastSnapshotName
  201. } else {
  202. lastSnapshotName = snapshotName
  203. }
  204. }
  205. } else {
  206. //Current snapshot not exists. Use the last item in snapshots list
  207. previousSnapshotName = existingSnapshots[len(existingSnapshots)-1]
  208. }
  209. return previousSnapshotName, nil
  210. }