bep_extensions.go 11 KB

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