bep_extensions.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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. "runtime"
  10. "time"
  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 (m 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}",
  50. f.Name, f.Sequence, f.Permissions, f.ModTime(), f.Version, f.VersionHash, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions)
  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}",
  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)
  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}",
  56. f.Name, f.Type, f.Sequence, f.Version, f.VersionHash, f.Deleted, f.RawInvalid, f.LocalFlags, f.NoPermissions, f.SymlinkTarget)
  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 == 0 {
  107. return MinBlockSize
  108. }
  109. return int(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. func (f FileInfo) IsEquivalent(other FileInfo, modTimeWindow time.Duration) bool {
  162. return f.isEquivalent(other, modTimeWindow, false, false, 0)
  163. }
  164. func (f FileInfo) IsEquivalentOptional(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
  165. return f.isEquivalent(other, modTimeWindow, ignorePerms, ignoreBlocks, ignoreFlags)
  166. }
  167. // isEquivalent checks that the two file infos represent the same actual file content,
  168. // i.e. it does purposely not check only selected (see below) struct members.
  169. // Permissions (config) and blocks (scanning) can be excluded from the comparison.
  170. // Any file info is not "equivalent", if it has different
  171. // - type
  172. // - deleted flag
  173. // - invalid flag
  174. // - permissions, unless they are ignored
  175. // A file is not "equivalent", if it has different
  176. // - modification time (difference bigger than modTimeWindow)
  177. // - size
  178. // - blocks, unless there are no blocks to compare (scanning)
  179. // A symlink is not "equivalent", if it has different
  180. // - target
  181. // A directory does not have anything specific to check.
  182. func (f FileInfo) isEquivalent(other FileInfo, modTimeWindow time.Duration, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
  183. if f.MustRescan() || other.MustRescan() {
  184. // These are per definition not equivalent because they don't
  185. // represent a valid state, even if both happen to have the
  186. // MustRescan bit set.
  187. return false
  188. }
  189. // Mask out the ignored local flags before checking IsInvalid() below
  190. f.LocalFlags &^= ignoreFlags
  191. other.LocalFlags &^= ignoreFlags
  192. if f.Name != other.Name || f.Type != other.Type || f.Deleted != other.Deleted || f.IsInvalid() != other.IsInvalid() {
  193. return false
  194. }
  195. if !ignorePerms && !f.NoPermissions && !other.NoPermissions && !PermsEqual(f.Permissions, other.Permissions) {
  196. return false
  197. }
  198. switch f.Type {
  199. case FileInfoTypeFile:
  200. return f.Size == other.Size && ModTimeEqual(f.ModTime(), other.ModTime(), modTimeWindow) && (ignoreBlocks || f.BlocksEqual(other))
  201. case FileInfoTypeSymlink:
  202. return f.SymlinkTarget == other.SymlinkTarget
  203. case FileInfoTypeDirectory:
  204. return true
  205. }
  206. return false
  207. }
  208. func ModTimeEqual(a, b time.Time, modTimeWindow time.Duration) bool {
  209. if a.Equal(b) {
  210. return true
  211. }
  212. diff := a.Sub(b)
  213. if diff < 0 {
  214. diff *= -1
  215. }
  216. return diff < modTimeWindow
  217. }
  218. func PermsEqual(a, b uint32) bool {
  219. switch runtime.GOOS {
  220. case "windows":
  221. // There is only writeable and read only, represented for user, group
  222. // and other equally. We only compare against user.
  223. return a&0600 == b&0600
  224. default:
  225. // All bits count
  226. return a&0777 == b&0777
  227. }
  228. }
  229. // BlocksEqual returns true when the two files have identical block lists.
  230. func (f FileInfo) BlocksEqual(other FileInfo) bool {
  231. // If both sides have blocks hashes and they match, we are good. If they
  232. // don't match still check individual block hashes to catch differences
  233. // in weak hashes only (e.g. after switching weak hash algo).
  234. if len(f.BlocksHash) > 0 && len(other.BlocksHash) > 0 && bytes.Equal(f.BlocksHash, other.BlocksHash) {
  235. return true
  236. }
  237. // Actually compare the block lists in full.
  238. return blocksEqual(f.Blocks, other.Blocks)
  239. }
  240. // blocksEqual returns whether two slices of blocks are exactly the same hash
  241. // and index pair wise.
  242. func blocksEqual(a, b []BlockInfo) bool {
  243. if len(b) != len(a) {
  244. return false
  245. }
  246. for i, sblk := range a {
  247. if !bytes.Equal(sblk.Hash, b[i].Hash) {
  248. return false
  249. }
  250. }
  251. return true
  252. }
  253. func (f *FileInfo) SetMustRescan() {
  254. f.setLocalFlags(FlagLocalMustRescan)
  255. }
  256. func (f *FileInfo) SetIgnored() {
  257. f.setLocalFlags(FlagLocalIgnored)
  258. }
  259. func (f *FileInfo) SetUnsupported() {
  260. f.setLocalFlags(FlagLocalUnsupported)
  261. }
  262. func (f *FileInfo) SetDeleted(by ShortID) {
  263. f.ModifiedBy = by
  264. f.Deleted = true
  265. f.Version = f.Version.Update(by)
  266. f.ModifiedS = time.Now().Unix()
  267. f.setNoContent()
  268. }
  269. func (f *FileInfo) setLocalFlags(flags uint32) {
  270. f.RawInvalid = false
  271. f.LocalFlags = flags
  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. }
  335. func (x *FileInfoType) MarshalJSON() ([]byte, error) {
  336. return json.Marshal(x.String())
  337. }
  338. func (x *FileInfoType) UnmarshalJSON(data []byte) error {
  339. var s string
  340. if err := json.Unmarshal(data, &s); err != nil {
  341. return err
  342. }
  343. n, ok := FileInfoType_value[s]
  344. if !ok {
  345. return errors.New("invalid value: " + s)
  346. }
  347. *x = FileInfoType(n)
  348. return nil
  349. }