bep_extensions.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. // Copyright (C) 2014 The Protocol Authors.
  2. package protocol
  3. import (
  4. "bytes"
  5. "encoding/binary"
  6. "errors"
  7. "fmt"
  8. "runtime"
  9. "time"
  10. "github.com/syncthing/syncthing/lib/rand"
  11. "github.com/syncthing/syncthing/lib/sha256"
  12. )
  13. const (
  14. SyntheticDirectorySize = 128
  15. HelloMessageMagic uint32 = 0x2EA7D90B
  16. Version13HelloMagic uint32 = 0x9F79BC40 // old
  17. )
  18. // FileIntf is the set of methods implemented by both FileInfo and
  19. // db.FileInfoTruncated.
  20. type FileIntf interface {
  21. FileSize() int64
  22. FileName() string
  23. FileLocalFlags() uint32
  24. IsDeleted() bool
  25. IsInvalid() bool
  26. IsIgnored() bool
  27. IsUnsupported() bool
  28. MustRescan() bool
  29. IsReceiveOnlyChanged() bool
  30. IsDirectory() bool
  31. IsSymlink() bool
  32. ShouldConflict() bool
  33. HasPermissionBits() bool
  34. SequenceNo() int64
  35. BlockSize() int
  36. FileVersion() Vector
  37. FileType() FileInfoType
  38. FilePermissions() uint32
  39. FileModifiedBy() ShortID
  40. ModTime() time.Time
  41. }
  42. func (m Hello) Magic() uint32 {
  43. return HelloMessageMagic
  44. }
  45. func (f FileInfo) String() string {
  46. switch f.Type {
  47. case FileInfoTypeDirectory:
  48. return fmt.Sprintf("Directory{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, VersionHash:%x, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v}",
  49. f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.VersionHash, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions)
  50. case FileInfoTypeFile:
  51. return fmt.Sprintf("File{Name:%q, Sequence:%d, Permissions:0%o, ModTime:%v, Version:%v, VersionHash:%x, Length:%d, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, BlockSize:%d, Blocks:%v, BlocksHash:%x}",
  52. f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.VersionHash, f.Size, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.RawBlockSize, f.Blocks, f.BlocksHash)
  53. case FileInfoTypeSymlink, FileInfoTypeSymlinkDirectory, FileInfoTypeSymlinkFile:
  54. return fmt.Sprintf("Symlink{Name:%q, Type:%v, Sequence:%d, Version:%v, VersionHash:%x, Deleted:%v, Invalid:%v, LocalFlags:0x%x, NoPermissions:%v, SymlinkTarget:%q}",
  55. f.Name, f.Type, f.Sequence, f.Version, f.VersionHash, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.SymlinkTarget)
  56. default:
  57. panic("mystery file type detected")
  58. }
  59. }
  60. func (f FileInfo) IsDeleted() bool {
  61. return f.Deleted
  62. }
  63. func (f FileInfo) IsInvalid() bool {
  64. return f.RawInvalid || f.LocalFlags&LocalInvalidFlags != 0
  65. }
  66. func (f FileInfo) IsUnsupported() bool {
  67. return f.LocalFlags&FlagLocalUnsupported != 0
  68. }
  69. func (f FileInfo) IsIgnored() bool {
  70. return f.LocalFlags&FlagLocalIgnored != 0
  71. }
  72. func (f FileInfo) MustRescan() bool {
  73. return f.LocalFlags&FlagLocalMustRescan != 0
  74. }
  75. func (f FileInfo) IsReceiveOnlyChanged() bool {
  76. return f.LocalFlags&FlagLocalReceiveOnly != 0
  77. }
  78. func (f FileInfo) IsDirectory() bool {
  79. return f.Type == FileInfoTypeDirectory
  80. }
  81. func (f FileInfo) ShouldConflict() bool {
  82. return f.LocalFlags&LocalConflictFlags != 0
  83. }
  84. func (f FileInfo) IsSymlink() bool {
  85. switch f.Type {
  86. case FileInfoTypeSymlink, FileInfoTypeSymlinkDirectory, FileInfoTypeSymlinkFile:
  87. return true
  88. default:
  89. return false
  90. }
  91. }
  92. func (f FileInfo) HasPermissionBits() bool {
  93. return !f.NoPermissions
  94. }
  95. func (f FileInfo) FileSize() int64 {
  96. if f.Deleted {
  97. return 0
  98. }
  99. if f.IsDirectory() || f.IsSymlink() {
  100. return SyntheticDirectorySize
  101. }
  102. return f.Size
  103. }
  104. func (f FileInfo) BlockSize() int {
  105. if f.RawBlockSize == 0 {
  106. return MinBlockSize
  107. }
  108. return int(f.RawBlockSize)
  109. }
  110. func (f FileInfo) FileName() string {
  111. return f.Name
  112. }
  113. func (f FileInfo) FileLocalFlags() uint32 {
  114. return f.LocalFlags
  115. }
  116. func (f FileInfo) ModTime() time.Time {
  117. return time.Unix(f.ModifiedS, int64(f.ModifiedNs))
  118. }
  119. func (f FileInfo) SequenceNo() int64 {
  120. return f.Sequence
  121. }
  122. func (f FileInfo) FileVersion() Vector {
  123. return f.Version
  124. }
  125. func (f FileInfo) FileType() FileInfoType {
  126. return f.Type
  127. }
  128. func (f FileInfo) FilePermissions() uint32 {
  129. return f.Permissions
  130. }
  131. func (f FileInfo) FileModifiedBy() ShortID {
  132. return f.ModifiedBy
  133. }
  134. // WinsConflict returns true if "f" is the one to choose when it is in
  135. // conflict with "other".
  136. func WinsConflict(f, other FileIntf) bool {
  137. // If only one of the files is invalid, that one loses.
  138. if f.IsInvalid() != other.IsInvalid() {
  139. return !f.IsInvalid()
  140. }
  141. // If a modification is in conflict with a delete, we pick the
  142. // modification.
  143. if !f.IsDeleted() && other.IsDeleted() {
  144. return true
  145. }
  146. if f.IsDeleted() && !other.IsDeleted() {
  147. return false
  148. }
  149. // The one with the newer modification time wins.
  150. if f.ModTime().After(other.ModTime()) {
  151. return true
  152. }
  153. if f.ModTime().Before(other.ModTime()) {
  154. return false
  155. }
  156. // The modification times were equal. Use the device ID in the version
  157. // vector as tie breaker.
  158. return f.FileVersion().Compare(other.FileVersion()) == ConcurrentGreater
  159. }
  160. func (f FileInfo) IsEquivalent(other FileInfo, modTimeWindow time.Duration) bool {
  161. return f.isEquivalent(other, modTimeWindow, false, false, 0)
  162. }
  163. func (f FileInfo) IsEquivalentOptional(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
  164. return f.isEquivalent(other, modTimeWindow, ignorePerms, ignoreBlocks, ignoreFlags)
  165. }
  166. // isEquivalent checks that the two file infos represent the same actual file content,
  167. // i.e. it does purposely not check only selected (see below) struct members.
  168. // Permissions (config) and blocks (scanning) can be excluded from the comparison.
  169. // Any file info is not "equivalent", if it has different
  170. // - type
  171. // - deleted flag
  172. // - invalid flag
  173. // - permissions, unless they are ignored
  174. // A file is not "equivalent", if it has different
  175. // - modification time (difference bigger than modTimeWindow)
  176. // - size
  177. // - blocks, unless there are no blocks to compare (scanning)
  178. // A symlink is not "equivalent", if it has different
  179. // - target
  180. // A directory does not have anything specific to check.
  181. func (f FileInfo) isEquivalent(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
  182. if f.MustRescan() || other.MustRescan() {
  183. // These are per definition not equivalent because they don't
  184. // represent a valid state, even if both happen to have the
  185. // MustRescan bit set.
  186. return false
  187. }
  188. // Mask out the ignored local flags before checking IsInvalid() below
  189. f.LocalFlags &^= ignoreFlags
  190. other.LocalFlags &^= ignoreFlags
  191. if f.Name != other.Name || f.Type != other.Type || f.Deleted != other.Deleted || f.IsInvalid() != other.IsInvalid() {
  192. return false
  193. }
  194. if !ignorePerms && !f.NoPermissions && !other.NoPermissions && !PermsEqual(f.Permissions, other.Permissions) {
  195. return false
  196. }
  197. switch f.Type {
  198. case FileInfoTypeFile:
  199. return f.Size == other.Size && ModTimeEqual(f.ModTime(), other.ModTime(), modTimeWindow) && (ignoreBlocks || f.BlocksEqual(other))
  200. case FileInfoTypeSymlink:
  201. return f.SymlinkTarget == other.SymlinkTarget
  202. case FileInfoTypeDirectory:
  203. return true
  204. }
  205. return false
  206. }
  207. func ModTimeEqual(a, b time.Time, modTimeWindow time.Duration) bool {
  208. if a.Equal(b) {
  209. return true
  210. }
  211. diff := a.Sub(b)
  212. if diff < 0 {
  213. diff *= -1
  214. }
  215. return diff < modTimeWindow
  216. }
  217. func PermsEqual(a, b uint32) bool {
  218. switch runtime.GOOS {
  219. case "windows":
  220. // There is only writeable and read only, represented for user, group
  221. // and other equally. We only compare against user.
  222. return a&0600 == b&0600
  223. default:
  224. // All bits count
  225. return a&0777 == b&0777
  226. }
  227. }
  228. // BlocksEqual returns true when the two files have identical block lists.
  229. func (f FileInfo) BlocksEqual(other FileInfo) bool {
  230. // If both sides have blocks hashes and they match, we are good. If they
  231. // don't match still check individual block hashes to catch differences
  232. // in weak hashes only (e.g. after switching weak hash algo).
  233. if len(f.BlocksHash) > 0 && len(other.BlocksHash) > 0 && bytes.Equal(f.BlocksHash, other.BlocksHash) {
  234. return true
  235. }
  236. // Actually compare the block lists in full.
  237. return blocksEqual(f.Blocks, other.Blocks)
  238. }
  239. // blocksEqual returns whether two slices of blocks are exactly the same hash
  240. // and index pair wise.
  241. func blocksEqual(a, b []BlockInfo) bool {
  242. if len(b) != len(a) {
  243. return false
  244. }
  245. for i, sblk := range a {
  246. if !bytes.Equal(sblk.Hash, b[i].Hash) {
  247. return false
  248. }
  249. }
  250. return true
  251. }
  252. func (f *FileInfo) SetMustRescan(by ShortID) {
  253. f.setLocalFlags(by, FlagLocalMustRescan)
  254. }
  255. func (f *FileInfo) SetIgnored(by ShortID) {
  256. f.setLocalFlags(by, FlagLocalIgnored)
  257. }
  258. func (f *FileInfo) SetUnsupported(by ShortID) {
  259. f.setLocalFlags(by, FlagLocalUnsupported)
  260. }
  261. func (f *FileInfo) SetDeleted(by ShortID) {
  262. f.ModifiedBy = by
  263. f.Deleted = true
  264. f.Version = f.Version.Update(by)
  265. f.ModifiedS = time.Now().Unix()
  266. f.setNoContent()
  267. }
  268. func (f *FileInfo) setLocalFlags(by ShortID, flags uint32) {
  269. f.RawInvalid = false
  270. f.LocalFlags = flags
  271. f.ModifiedBy = by
  272. f.setNoContent()
  273. }
  274. func (f *FileInfo) setNoContent() {
  275. f.Blocks = nil
  276. f.BlocksHash = nil
  277. f.Size = 0
  278. }
  279. func (b BlockInfo) String() string {
  280. return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash)
  281. }
  282. // IsEmpty returns true if the block is a full block of zeroes.
  283. func (b BlockInfo) IsEmpty() bool {
  284. if v, ok := sha256OfEmptyBlock[int(b.Size)]; ok {
  285. return bytes.Equal(b.Hash, v[:])
  286. }
  287. return false
  288. }
  289. type IndexID uint64
  290. func (i IndexID) String() string {
  291. return fmt.Sprintf("0x%016X", uint64(i))
  292. }
  293. func (i IndexID) Marshal() ([]byte, error) {
  294. bs := make([]byte, 8)
  295. binary.BigEndian.PutUint64(bs, uint64(i))
  296. return bs, nil
  297. }
  298. func (i *IndexID) Unmarshal(bs []byte) error {
  299. if len(bs) != 8 {
  300. return errors.New("incorrect IndexID length")
  301. }
  302. *i = IndexID(binary.BigEndian.Uint64(bs))
  303. return nil
  304. }
  305. func NewIndexID() IndexID {
  306. return IndexID(rand.Uint64())
  307. }
  308. func (f Folder) Description() string {
  309. // used by logging stuff
  310. if f.Label == "" {
  311. return f.ID
  312. }
  313. return fmt.Sprintf("%q (%s)", f.Label, f.ID)
  314. }
  315. func BlocksHash(bs []BlockInfo) []byte {
  316. h := sha256.New()
  317. for _, b := range bs {
  318. _, _ = h.Write(b.Hash)
  319. _ = binary.Write(h, binary.BigEndian, b.WeakHash)
  320. }
  321. return h.Sum(nil)
  322. }
  323. func VectorHash(v Vector) []byte {
  324. h := sha256.New()
  325. for _, c := range v.Counters {
  326. if err := binary.Write(h, binary.BigEndian, c.ID); err != nil {
  327. panic("impossible: failed to write c.ID to hash function: " + err.Error())
  328. }
  329. if err := binary.Write(h, binary.BigEndian, c.Value); err != nil {
  330. panic("impossible: failed to write c.Value to hash function: " + err.Error())
  331. }
  332. }
  333. return h.Sum(nil)
  334. }