gdrivefs.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. // gdrivemount implements a google drive fuse driver
  2. package gdrivefs
  3. import (
  4. "io"
  5. "os"
  6. "sync"
  7. "syscall"
  8. "time"
  9. "dev.hexasoftware.com/hxs/cloudmount/internal/core"
  10. "dev.hexasoftware.com/hxs/prettylog"
  11. "golang.org/x/net/context"
  12. drive "google.golang.org/api/drive/v3"
  13. "google.golang.org/api/googleapi"
  14. "github.com/jacobsa/fuse"
  15. "github.com/jacobsa/fuse/fuseops"
  16. "github.com/jacobsa/fuse/fuseutil"
  17. )
  18. var (
  19. log = prettylog.New("gdrivefs")
  20. )
  21. type Handle struct {
  22. ID fuseops.HandleID
  23. entry *FileEntry
  24. uploadOnDone bool
  25. // Handling for dir
  26. entries []fuseutil.Dirent
  27. }
  28. // GDriveFS
  29. type GDriveFS struct {
  30. fuseutil.NotImplementedFileSystem // Defaults
  31. config *core.Config //core *core.Core // Core Config instead?
  32. client *drive.Service
  33. //root *FileEntry // hiearchy reference
  34. root *FileContainer
  35. fileHandles map[fuseops.HandleID]*Handle
  36. fileEntries map[fuseops.InodeID]*FileEntry
  37. nextRefresh time.Time
  38. handleMU *sync.Mutex
  39. //fileMap map[string]
  40. // Map IDS with FileEntries
  41. }
  42. func New(core *core.Core) core.Driver {
  43. fs := &GDriveFS{
  44. config: &core.Config,
  45. fileHandles: map[fuseops.HandleID]*Handle{},
  46. handleMU: &sync.Mutex{},
  47. }
  48. fs.initClient() // Init Oauth2 client
  49. fs.root = NewFileContainer(fs)
  50. fs.root.uid = core.Config.UID
  51. fs.root.gid = core.Config.GID
  52. //fs.root = rootEntry
  53. // Temporary entry
  54. entry := fs.root.tree.AppendGFile(&drive.File{Id: "0", Name: "Loading..."}, 999999)
  55. entry.Attr.Mode = os.FileMode(0)
  56. return fs
  57. }
  58. func (fs *GDriveFS) Start() {
  59. fs.timedRefresh() // start synching
  60. }
  61. ////////////////////////////////////////////////////////
  62. // TOOLS & HELPERS
  63. ////////////////////////////////////////////////////////
  64. func (fs *GDriveFS) createHandle() *Handle {
  65. // Lock here instead
  66. fs.handleMU.Lock()
  67. defer fs.handleMU.Unlock()
  68. var handleID fuseops.HandleID
  69. for handleID = 1; handleID < 99999; handleID++ {
  70. _, ok := fs.fileHandles[handleID]
  71. if !ok {
  72. break
  73. }
  74. }
  75. handle := &Handle{ID: handleID}
  76. fs.fileHandles[handleID] = handle
  77. return handle
  78. }
  79. func (fs *GDriveFS) timedRefresh() {
  80. go func() {
  81. for {
  82. if time.Now().After(fs.nextRefresh) {
  83. fs.Refresh()
  84. }
  85. time.Sleep(fs.config.RefreshTime) // 2 minutes
  86. }
  87. }()
  88. }
  89. // Refresh service files
  90. func (fs *GDriveFS) Refresh() {
  91. fs.nextRefresh = time.Now().Add(1 * time.Minute)
  92. fileList := []*drive.File{}
  93. fileMap := map[string]*drive.File{} // Temporary map by google drive fileID
  94. gdFields := googleapi.Field("nextPageToken, files(id,name,size,quotaBytesUsed, mimeType,parents,createdTime,modifiedTime)")
  95. log.Println("Loading file entries from gdrive")
  96. r, err := fs.client.Files.List().
  97. OrderBy("createdTime").
  98. PageSize(1000).
  99. SupportsTeamDrives(true).
  100. IncludeTeamDriveItems(true).
  101. Fields(gdFields).
  102. Do()
  103. if err != nil {
  104. log.Println("GDrive ERR:", err)
  105. return
  106. }
  107. fileList = append(fileList, r.Files...)
  108. // Rest of the pages
  109. for r.NextPageToken != "" {
  110. r, err = fs.client.Files.List().
  111. OrderBy("createdTime").
  112. PageToken(r.NextPageToken).
  113. Fields(gdFields).
  114. Do()
  115. if err != nil {
  116. log.Println("GDrive ERR:", err)
  117. return
  118. }
  119. fileList = append(fileList, r.Files...)
  120. }
  121. log.Println("Total entries:", len(fileList))
  122. // TimeSort
  123. /*log.Println("Sort by time")
  124. sort.Slice(fileList, func(i, j int) bool {
  125. createdTimeI, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
  126. createdTimeJ, _ := time.Parse(time.RFC3339, fileList[i].CreatedTime)
  127. if createdTimeI.Before(createdTimeJ) {
  128. return true
  129. }
  130. return false
  131. })*/
  132. // Cache ID for faster retrieval, might not be necessary
  133. for _, f := range fileList {
  134. fileMap[f.Id] = f
  135. }
  136. if err != nil || r == nil {
  137. log.Println("Unable to retrieve files", err)
  138. return
  139. }
  140. // Create clean fileList
  141. root := NewFileContainer(fs)
  142. // Helper func to recurse
  143. // Everything loaded we add to our entries
  144. // Add file and its parents priorizing it parent
  145. var appendFile func(df *drive.File)
  146. appendFile = func(df *drive.File) {
  147. for _, pID := range df.Parents {
  148. parentFile, ok := fileMap[pID]
  149. if !ok {
  150. parentFile, err = fs.client.Files.Get(pID).Do()
  151. if err != nil {
  152. panic(err)
  153. }
  154. fileMap[parentFile.Id] = parentFile
  155. }
  156. appendFile(parentFile) // Recurse
  157. }
  158. // Find existing entry
  159. var inode fuseops.InodeID
  160. entry := fs.root.FindByGID(df.Id)
  161. // Store for later add
  162. if entry == nil {
  163. inode = fs.root.FileEntry().Inode // Register new inode on Same Root fs
  164. //inode = root.FileEntry().Inode // This can be a problem if next time a existing inode comes? Allocate new file entry with new Inode
  165. } else {
  166. inode = entry.Inode // Reuse existing root fs inode
  167. }
  168. newEntry := root.tree.solveAppendGFile(df, inode) // Find right parent
  169. if entry != nil && entry.GFile.Name == df.Name { // Copy name from old entry
  170. newEntry.Name = entry.Name
  171. }
  172. // add File
  173. }
  174. for _, f := range fileList { // Ordered
  175. appendFile(f) // Check parent first
  176. }
  177. log.Println("Refresh done, update root")
  178. fs.root = root
  179. //fs.root.children = root.children
  180. log.Println("File count:", len(fs.root.fileEntries))
  181. }
  182. ///////////////////////////////
  183. // Fuse operations
  184. ////////////
  185. // OpenDir return nil error allows open dir
  186. func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
  187. entry := fs.root.FindByInode(op.Inode)
  188. if entry == nil {
  189. return fuse.ENOENT
  190. }
  191. handle := fs.createHandle()
  192. handle.entry = entry
  193. op.Handle = handle.ID
  194. return // No error allow, dir open
  195. }
  196. // ReadDir lists files into readdirop
  197. func (fs *GDriveFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) (err error) {
  198. fh, ok := fs.fileHandles[op.Handle]
  199. if !ok {
  200. log.Fatal("Handle does not exists")
  201. }
  202. if op.Offset == 0 { // Rebuild/rewind dir list
  203. fh.entries = []fuseutil.Dirent{}
  204. for i, v := range fh.entry.children {
  205. fusetype := fuseutil.DT_File
  206. if v.IsDir() {
  207. fusetype = fuseutil.DT_Directory
  208. }
  209. dirEnt := fuseutil.Dirent{
  210. Inode: v.Inode,
  211. Name: v.Name,
  212. Type: fusetype,
  213. Offset: fuseops.DirOffset(i) + 1,
  214. }
  215. // written += fuseutil.WriteDirent(fh.buf[written:], dirEnt)
  216. fh.entries = append(fh.entries, dirEnt)
  217. }
  218. }
  219. index := int(op.Offset)
  220. if index > len(fh.entries) {
  221. return fuse.EINVAL
  222. }
  223. if index > 0 {
  224. index++
  225. }
  226. for i := index; i < len(fh.entries); i++ {
  227. n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], fh.entries[i])
  228. //log.Println("Written:", n)
  229. if n == 0 {
  230. break
  231. }
  232. op.BytesRead += n
  233. }
  234. return
  235. }
  236. // SetInodeAttributes Not sure what attributes gdrive support we just leave this blank for now
  237. func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
  238. // Hack to truncate file?
  239. if op.Size != nil {
  240. f := fs.root.FindByInode(op.Inode)
  241. if *op.Size != 0 { // We only allow truncate to 0
  242. return fuse.ENOSYS
  243. }
  244. // Delete and create another on truncate 0
  245. err = fs.client.Files.Delete(f.GFile.Id).Do() // XXX: Careful on this
  246. createdFile, err := fs.client.Files.Create(&drive.File{Parents: f.GFile.Parents, Name: f.GFile.Name}).Do()
  247. if err != nil {
  248. return fuse.EINVAL
  249. }
  250. f.SetGFile(createdFile) // Set new file
  251. }
  252. return
  253. }
  254. //GetInodeAttributes return attributes
  255. func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
  256. f := fs.root.FindByInode(op.Inode)
  257. if f == nil {
  258. return fuse.ENOENT
  259. }
  260. op.Attributes = f.Attr
  261. op.AttributesExpiration = time.Now().Add(time.Minute)
  262. return
  263. }
  264. // ReleaseDirHandle deletes file handle entry
  265. func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) (err error) {
  266. delete(fs.fileHandles, op.Handle)
  267. return
  268. }
  269. // LookUpInode based on Parent and Name we return a self cached inode
  270. func (fs *GDriveFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
  271. parentFile := fs.root.FindByInode(op.Parent) // true means transverse all
  272. if parentFile == nil {
  273. return fuse.ENOENT
  274. }
  275. now := time.Now()
  276. // Transverse only local
  277. f := parentFile.FindByName(op.Name, false)
  278. if f == nil {
  279. return fuse.ENOENT
  280. }
  281. op.Entry = fuseops.ChildInodeEntry{
  282. Attributes: f.Attr,
  283. Child: f.Inode,
  284. AttributesExpiration: now.Add(time.Second),
  285. EntryExpiration: now.Add(time.Second),
  286. }
  287. return
  288. }
  289. // StatFS basically allows StatFS to run
  290. /*func (fs *GDriveFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) (err error) {
  291. return
  292. }*/
  293. // ForgetInode allows to forgetInode
  294. func (fs *GDriveFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) (err error) {
  295. return
  296. }
  297. // GetXAttr special attributes
  298. func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err error) {
  299. return
  300. }
  301. //////////////////////////////////////////////////////////////////////////
  302. // File OPS
  303. //////////////////////////////////////////////////////////////////////////
  304. // OpenFile creates a temporary handle to be handled on read or write
  305. func (fs *GDriveFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
  306. f := fs.root.FindByInode(op.Inode) // might not exists
  307. // Generate new handle
  308. handle := fs.createHandle()
  309. handle.entry = f
  310. op.Handle = handle.ID
  311. op.UseDirectIO = true
  312. return
  313. }
  314. // ReadFile if the first time we download the google drive file into a local temporary file
  315. func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
  316. handle := fs.fileHandles[op.Handle]
  317. localFile := handle.entry.Cache()
  318. op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
  319. if err == io.EOF { // fuse does not expect a EOF
  320. err = nil
  321. }
  322. return
  323. }
  324. // CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
  325. func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
  326. parentFile := fs.root.FindByInode(op.Parent)
  327. if parentFile == nil {
  328. return fuse.ENOENT
  329. }
  330. // Only write on child folders
  331. if parentFile.Inode == fuseops.RootInodeID {
  332. return syscall.EPERM
  333. }
  334. existsFile := parentFile.FindByName(op.Name, false)
  335. if existsFile != nil {
  336. return fuse.EEXIST
  337. }
  338. newFile := &drive.File{
  339. Parents: []string{parentFile.GFile.Id},
  340. Name: op.Name,
  341. }
  342. createdFile, err := fs.client.Files.Create(newFile).Do()
  343. if err != nil {
  344. err = fuse.EINVAL
  345. return
  346. }
  347. // Temp
  348. entry := fs.root.FileEntry()
  349. entry = parentFile.AppendGFile(createdFile, entry.Inode) // Add new created file
  350. if entry == nil {
  351. err = fuse.EINVAL
  352. return
  353. }
  354. // Associate a temp file to a new handle
  355. // Local copy
  356. // Lock
  357. handle := fs.createHandle()
  358. handle.entry = entry
  359. handle.uploadOnDone = true
  360. //
  361. op.Handle = handle.ID
  362. op.Entry = fuseops.ChildInodeEntry{
  363. Attributes: entry.Attr,
  364. Child: entry.Inode,
  365. AttributesExpiration: time.Now().Add(time.Minute),
  366. EntryExpiration: time.Now().Add(time.Minute),
  367. }
  368. op.Mode = entry.Attr.Mode
  369. return
  370. }
  371. // WriteFile as ReadFile it creates a temporary file on first read
  372. // Maybe the ReadFile should be called here aswell to cache current contents since we are using writeAt
  373. func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
  374. handle, ok := fs.fileHandles[op.Handle]
  375. if !ok {
  376. return fuse.EIO
  377. }
  378. localFile := handle.entry.Cache()
  379. if localFile == nil {
  380. return fuse.EINVAL
  381. }
  382. _, err = localFile.WriteAt(op.Data, op.Offset)
  383. if err != nil {
  384. err = fuse.EIO
  385. return
  386. }
  387. handle.uploadOnDone = true
  388. return
  389. }
  390. // FlushFile just returns no error, maybe upload should be handled here
  391. func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
  392. handle, ok := fs.fileHandles[op.Handle]
  393. if !ok {
  394. return fuse.EIO
  395. }
  396. if handle.entry.tempFile == nil {
  397. return
  398. }
  399. if handle.uploadOnDone { // or if content changed basically
  400. err = handle.entry.Sync()
  401. if err != nil {
  402. return fuse.EINVAL
  403. }
  404. }
  405. return
  406. }
  407. // ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
  408. func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
  409. handle := fs.fileHandles[op.Handle]
  410. handle.entry.ClearCache()
  411. delete(fs.fileHandles, op.Handle)
  412. return
  413. }
  414. // Unlink remove file and remove from local cache entry
  415. func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
  416. parentEntry := fs.root.FindByInode(op.Parent)
  417. if parentEntry == nil {
  418. return fuse.ENOENT
  419. }
  420. if parentEntry.Inode == fuseops.RootInodeID {
  421. return syscall.EPERM
  422. }
  423. fileEntry := parentEntry.FindByName(op.Name, false)
  424. if fileEntry == nil {
  425. return fuse.ENOATTR
  426. }
  427. err = fs.client.Files.Delete(fileEntry.GFile.Id).Do()
  428. if err != nil {
  429. return fuse.EIO
  430. }
  431. fs.root.RemoveEntry(fileEntry)
  432. parentEntry.RemoveChild(fileEntry)
  433. return
  434. }
  435. // MkDir creates a directory on a parent dir
  436. func (fs *GDriveFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
  437. parentFile := fs.root.FindByInode(op.Parent)
  438. if parentFile == nil {
  439. return fuse.ENOENT
  440. }
  441. if parentFile.Inode == fuseops.RootInodeID {
  442. return syscall.EPERM
  443. }
  444. // Should check existent first too
  445. fi, err := fs.client.Files.Create(&drive.File{
  446. Parents: []string{parentFile.GFile.Id},
  447. MimeType: "application/vnd.google-apps.folder",
  448. Name: op.Name,
  449. }).Do()
  450. if err != nil {
  451. return fuse.ENOATTR
  452. }
  453. entry := fs.root.FileEntry()
  454. entry = parentFile.AppendGFile(fi, entry.Inode)
  455. if entry == nil {
  456. return fuse.EINVAL
  457. }
  458. op.Entry = fuseops.ChildInodeEntry{
  459. Attributes: entry.Attr,
  460. Child: entry.Inode,
  461. AttributesExpiration: time.Now().Add(time.Minute),
  462. EntryExpiration: time.Now().Add(time.Microsecond),
  463. }
  464. return
  465. }
  466. // RmDir fuse implementation
  467. func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
  468. parentFile := fs.root.FindByInode(op.Parent)
  469. if parentFile == nil {
  470. return fuse.ENOENT
  471. }
  472. if parentFile.Inode == fuseops.RootInodeID {
  473. return syscall.EPERM
  474. }
  475. theFile := parentFile.FindByName(op.Name, false)
  476. err = fs.client.Files.Delete(theFile.GFile.Id).Do()
  477. if err != nil {
  478. return fuse.ENOTEMPTY
  479. }
  480. parentFile.RemoveChild(theFile)
  481. // Remove from entry somehow
  482. return
  483. }
  484. // Rename fuse implementation
  485. func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
  486. oldParentFile := fs.root.FindByInode(op.OldParent)
  487. if oldParentFile == nil {
  488. return fuse.ENOENT
  489. }
  490. newParentFile := fs.root.FindByInode(op.NewParent)
  491. if newParentFile == nil {
  492. return fuse.ENOENT
  493. }
  494. if oldParentFile.Inode == fuseops.RootInodeID || newParentFile.Inode == fuseops.RootInodeID {
  495. return syscall.EPERM
  496. }
  497. oldFile := oldParentFile.FindByName(op.OldName, false)
  498. // Although GDrive allows duplicate names, there is some issue with inode caching
  499. // So we prevent a rename to a file with same name
  500. existsFile := newParentFile.FindByName(op.NewName, false)
  501. if existsFile != nil {
  502. return fuse.EEXIST
  503. }
  504. ngFile := &drive.File{
  505. Name: op.NewName,
  506. }
  507. updateCall := fs.client.Files.Update(oldFile.GFile.Id, ngFile)
  508. if oldParentFile != newParentFile {
  509. updateCall.RemoveParents(oldParentFile.GFile.Id)
  510. updateCall.AddParents(newParentFile.GFile.Id)
  511. }
  512. updatedFile, err := updateCall.Do()
  513. oldParentFile.RemoveChild(oldFile)
  514. newParentFile.AppendGFile(updatedFile, oldFile.Inode)
  515. return
  516. }