webdav_server.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. package weed_server
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io"
  7. "math"
  8. "os"
  9. "path"
  10. "strings"
  11. "time"
  12. "github.com/chrislusf/seaweedfs/weed/util/buffered_writer"
  13. "golang.org/x/net/webdav"
  14. "google.golang.org/grpc"
  15. "github.com/chrislusf/seaweedfs/weed/operation"
  16. "github.com/chrislusf/seaweedfs/weed/pb"
  17. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  18. "github.com/chrislusf/seaweedfs/weed/util"
  19. "github.com/chrislusf/seaweedfs/weed/util/chunk_cache"
  20. "github.com/chrislusf/seaweedfs/weed/filer"
  21. "github.com/chrislusf/seaweedfs/weed/glog"
  22. "github.com/chrislusf/seaweedfs/weed/security"
  23. )
  24. type WebDavOption struct {
  25. Filer string
  26. FilerGrpcAddress string
  27. DomainName string
  28. BucketsPath string
  29. GrpcDialOption grpc.DialOption
  30. Collection string
  31. Replication string
  32. DiskType string
  33. Uid uint32
  34. Gid uint32
  35. Cipher bool
  36. CacheDir string
  37. CacheSizeMB int64
  38. }
  39. type WebDavServer struct {
  40. option *WebDavOption
  41. secret security.SigningKey
  42. filer *filer.Filer
  43. grpcDialOption grpc.DialOption
  44. Handler *webdav.Handler
  45. }
  46. func NewWebDavServer(option *WebDavOption) (ws *WebDavServer, err error) {
  47. fs, _ := NewWebDavFileSystem(option)
  48. ws = &WebDavServer{
  49. option: option,
  50. grpcDialOption: security.LoadClientTLS(util.GetViper(), "grpc.filer"),
  51. Handler: &webdav.Handler{
  52. FileSystem: fs,
  53. LockSystem: webdav.NewMemLS(),
  54. },
  55. }
  56. return ws, nil
  57. }
  58. // adapted from https://github.com/mattn/davfs/blob/master/plugin/mysql/mysql.go
  59. type WebDavFileSystem struct {
  60. option *WebDavOption
  61. secret security.SigningKey
  62. filer *filer.Filer
  63. grpcDialOption grpc.DialOption
  64. chunkCache *chunk_cache.TieredChunkCache
  65. signature int32
  66. }
  67. type FileInfo struct {
  68. name string
  69. size int64
  70. mode os.FileMode
  71. modifiledTime time.Time
  72. isDirectory bool
  73. }
  74. func (fi *FileInfo) Name() string { return fi.name }
  75. func (fi *FileInfo) Size() int64 { return fi.size }
  76. func (fi *FileInfo) Mode() os.FileMode { return fi.mode }
  77. func (fi *FileInfo) ModTime() time.Time { return fi.modifiledTime }
  78. func (fi *FileInfo) IsDir() bool { return fi.isDirectory }
  79. func (fi *FileInfo) Sys() interface{} { return nil }
  80. type WebDavFile struct {
  81. fs *WebDavFileSystem
  82. name string
  83. isDirectory bool
  84. off int64
  85. entry *filer_pb.Entry
  86. entryViewCache []filer.VisibleInterval
  87. reader io.ReaderAt
  88. bufWriter *buffered_writer.BufferedWriteCloser
  89. collection string
  90. replication string
  91. }
  92. func NewWebDavFileSystem(option *WebDavOption) (webdav.FileSystem, error) {
  93. cacheUniqueId := util.Md5String([]byte("webdav" + option.FilerGrpcAddress + util.Version()))[0:8]
  94. cacheDir := path.Join(option.CacheDir, cacheUniqueId)
  95. os.MkdirAll(cacheDir, os.FileMode(0755))
  96. chunkCache := chunk_cache.NewTieredChunkCache(256, cacheDir, option.CacheSizeMB, 1024*1024)
  97. return &WebDavFileSystem{
  98. option: option,
  99. chunkCache: chunkCache,
  100. signature: util.RandomInt32(),
  101. }, nil
  102. }
  103. var _ = filer_pb.FilerClient(&WebDavFileSystem{})
  104. func (fs *WebDavFileSystem) WithFilerClient(fn func(filer_pb.SeaweedFilerClient) error) error {
  105. return pb.WithCachedGrpcClient(func(grpcConnection *grpc.ClientConn) error {
  106. client := filer_pb.NewSeaweedFilerClient(grpcConnection)
  107. return fn(client)
  108. }, fs.option.FilerGrpcAddress, fs.option.GrpcDialOption)
  109. }
  110. func (fs *WebDavFileSystem) AdjustedUrl(location *filer_pb.Location) string {
  111. return location.Url
  112. }
  113. func clearName(name string) (string, error) {
  114. slashed := strings.HasSuffix(name, "/")
  115. name = path.Clean(name)
  116. if !strings.HasSuffix(name, "/") && slashed {
  117. name += "/"
  118. }
  119. if !strings.HasPrefix(name, "/") {
  120. return "", os.ErrInvalid
  121. }
  122. return name, nil
  123. }
  124. func (fs *WebDavFileSystem) Mkdir(ctx context.Context, fullDirPath string, perm os.FileMode) error {
  125. glog.V(2).Infof("WebDavFileSystem.Mkdir %v", fullDirPath)
  126. if !strings.HasSuffix(fullDirPath, "/") {
  127. fullDirPath += "/"
  128. }
  129. var err error
  130. if fullDirPath, err = clearName(fullDirPath); err != nil {
  131. return err
  132. }
  133. _, err = fs.stat(ctx, fullDirPath)
  134. if err == nil {
  135. return os.ErrExist
  136. }
  137. return fs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  138. dir, name := util.FullPath(fullDirPath).DirAndName()
  139. request := &filer_pb.CreateEntryRequest{
  140. Directory: dir,
  141. Entry: &filer_pb.Entry{
  142. Name: name,
  143. IsDirectory: true,
  144. Attributes: &filer_pb.FuseAttributes{
  145. Mtime: time.Now().Unix(),
  146. Crtime: time.Now().Unix(),
  147. FileMode: uint32(perm | os.ModeDir),
  148. Uid: fs.option.Uid,
  149. Gid: fs.option.Gid,
  150. },
  151. },
  152. Signatures: []int32{fs.signature},
  153. }
  154. glog.V(1).Infof("mkdir: %v", request)
  155. if err := filer_pb.CreateEntry(client, request); err != nil {
  156. return fmt.Errorf("mkdir %s/%s: %v", dir, name, err)
  157. }
  158. return nil
  159. })
  160. }
  161. func (fs *WebDavFileSystem) OpenFile(ctx context.Context, fullFilePath string, flag int, perm os.FileMode) (webdav.File, error) {
  162. glog.V(2).Infof("WebDavFileSystem.OpenFile %v %x", fullFilePath, flag)
  163. var err error
  164. if fullFilePath, err = clearName(fullFilePath); err != nil {
  165. return nil, err
  166. }
  167. if flag&os.O_CREATE != 0 {
  168. // file should not have / suffix.
  169. if strings.HasSuffix(fullFilePath, "/") {
  170. return nil, os.ErrInvalid
  171. }
  172. _, err = fs.stat(ctx, fullFilePath)
  173. if err == nil {
  174. if flag&os.O_EXCL != 0 {
  175. return nil, os.ErrExist
  176. }
  177. fs.removeAll(ctx, fullFilePath)
  178. }
  179. dir, name := util.FullPath(fullFilePath).DirAndName()
  180. err = fs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  181. if err := filer_pb.CreateEntry(client, &filer_pb.CreateEntryRequest{
  182. Directory: dir,
  183. Entry: &filer_pb.Entry{
  184. Name: name,
  185. IsDirectory: perm&os.ModeDir > 0,
  186. Attributes: &filer_pb.FuseAttributes{
  187. Mtime: time.Now().Unix(),
  188. Crtime: time.Now().Unix(),
  189. FileMode: uint32(perm),
  190. Uid: fs.option.Uid,
  191. Gid: fs.option.Gid,
  192. Collection: fs.option.Collection,
  193. Replication: fs.option.Replication,
  194. TtlSec: 0,
  195. },
  196. },
  197. Signatures: []int32{fs.signature},
  198. }); err != nil {
  199. return fmt.Errorf("create %s: %v", fullFilePath, err)
  200. }
  201. return nil
  202. })
  203. if err != nil {
  204. return nil, err
  205. }
  206. return &WebDavFile{
  207. fs: fs,
  208. name: fullFilePath,
  209. isDirectory: false,
  210. bufWriter: buffered_writer.NewBufferedWriteCloser(4 * 1024 * 1024),
  211. }, nil
  212. }
  213. fi, err := fs.stat(ctx, fullFilePath)
  214. if err != nil {
  215. return nil, os.ErrNotExist
  216. }
  217. if !strings.HasSuffix(fullFilePath, "/") && fi.IsDir() {
  218. fullFilePath += "/"
  219. }
  220. return &WebDavFile{
  221. fs: fs,
  222. name: fullFilePath,
  223. isDirectory: false,
  224. bufWriter: buffered_writer.NewBufferedWriteCloser(4 * 1024 * 1024),
  225. }, nil
  226. }
  227. func (fs *WebDavFileSystem) removeAll(ctx context.Context, fullFilePath string) error {
  228. var err error
  229. if fullFilePath, err = clearName(fullFilePath); err != nil {
  230. return err
  231. }
  232. dir, name := util.FullPath(fullFilePath).DirAndName()
  233. return filer_pb.Remove(fs, dir, name, true, false, false, false, []int32{fs.signature})
  234. }
  235. func (fs *WebDavFileSystem) RemoveAll(ctx context.Context, name string) error {
  236. glog.V(2).Infof("WebDavFileSystem.RemoveAll %v", name)
  237. return fs.removeAll(ctx, name)
  238. }
  239. func (fs *WebDavFileSystem) Rename(ctx context.Context, oldName, newName string) error {
  240. glog.V(2).Infof("WebDavFileSystem.Rename %v to %v", oldName, newName)
  241. var err error
  242. if oldName, err = clearName(oldName); err != nil {
  243. return err
  244. }
  245. if newName, err = clearName(newName); err != nil {
  246. return err
  247. }
  248. of, err := fs.stat(ctx, oldName)
  249. if err != nil {
  250. return os.ErrExist
  251. }
  252. if of.IsDir() {
  253. if strings.HasSuffix(oldName, "/") {
  254. oldName = strings.TrimRight(oldName, "/")
  255. }
  256. if strings.HasSuffix(newName, "/") {
  257. newName = strings.TrimRight(newName, "/")
  258. }
  259. }
  260. _, err = fs.stat(ctx, newName)
  261. if err == nil {
  262. return os.ErrExist
  263. }
  264. oldDir, oldBaseName := util.FullPath(oldName).DirAndName()
  265. newDir, newBaseName := util.FullPath(newName).DirAndName()
  266. return fs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  267. request := &filer_pb.AtomicRenameEntryRequest{
  268. OldDirectory: oldDir,
  269. OldName: oldBaseName,
  270. NewDirectory: newDir,
  271. NewName: newBaseName,
  272. }
  273. _, err := client.AtomicRenameEntry(ctx, request)
  274. if err != nil {
  275. return fmt.Errorf("renaming %s/%s => %s/%s: %v", oldDir, oldBaseName, newDir, newBaseName, err)
  276. }
  277. return nil
  278. })
  279. }
  280. func (fs *WebDavFileSystem) stat(ctx context.Context, fullFilePath string) (os.FileInfo, error) {
  281. var err error
  282. if fullFilePath, err = clearName(fullFilePath); err != nil {
  283. return nil, err
  284. }
  285. fullpath := util.FullPath(fullFilePath)
  286. var fi FileInfo
  287. entry, err := filer_pb.GetEntry(fs, fullpath)
  288. if entry == nil {
  289. return nil, os.ErrNotExist
  290. }
  291. if err != nil {
  292. return nil, err
  293. }
  294. fi.size = int64(filer.FileSize(entry))
  295. fi.name = string(fullpath)
  296. fi.mode = os.FileMode(entry.Attributes.FileMode)
  297. fi.modifiledTime = time.Unix(entry.Attributes.Mtime, 0)
  298. fi.isDirectory = entry.IsDirectory
  299. if fi.name == "/" {
  300. fi.modifiledTime = time.Now()
  301. fi.isDirectory = true
  302. }
  303. return &fi, nil
  304. }
  305. func (fs *WebDavFileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) {
  306. glog.V(2).Infof("WebDavFileSystem.Stat %v", name)
  307. return fs.stat(ctx, name)
  308. }
  309. func (f *WebDavFile) saveDataAsChunk(reader io.Reader, name string, offset int64) (chunk *filer_pb.FileChunk, collection, replication string, err error) {
  310. var fileId, host string
  311. var auth security.EncodedJwt
  312. if flushErr := f.fs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  313. ctx := context.Background()
  314. request := &filer_pb.AssignVolumeRequest{
  315. Count: 1,
  316. Replication: f.fs.option.Replication,
  317. Collection: f.fs.option.Collection,
  318. DiskType: f.fs.option.DiskType,
  319. Path: name,
  320. }
  321. resp, err := client.AssignVolume(ctx, request)
  322. if err != nil {
  323. glog.V(0).Infof("assign volume failure %v: %v", request, err)
  324. return err
  325. }
  326. if resp.Error != "" {
  327. return fmt.Errorf("assign volume failure %v: %v", request, resp.Error)
  328. }
  329. fileId, host, auth = resp.FileId, resp.Url, security.EncodedJwt(resp.Auth)
  330. f.collection, f.replication = resp.Collection, resp.Replication
  331. return nil
  332. }); flushErr != nil {
  333. return nil, f.collection, f.replication, fmt.Errorf("filerGrpcAddress assign volume: %v", flushErr)
  334. }
  335. fileUrl := fmt.Sprintf("http://%s/%s", host, fileId)
  336. uploadResult, flushErr, _ := operation.Upload(fileUrl, f.name, f.fs.option.Cipher, reader, false, "", nil, auth)
  337. if flushErr != nil {
  338. glog.V(0).Infof("upload data %v to %s: %v", f.name, fileUrl, flushErr)
  339. return nil, f.collection, f.replication, fmt.Errorf("upload data: %v", flushErr)
  340. }
  341. if uploadResult.Error != "" {
  342. glog.V(0).Infof("upload failure %v to %s: %v", f.name, fileUrl, flushErr)
  343. return nil, f.collection, f.replication, fmt.Errorf("upload result: %v", uploadResult.Error)
  344. }
  345. return uploadResult.ToPbFileChunk(fileId, offset), f.collection, f.replication, nil
  346. }
  347. func (f *WebDavFile) Write(buf []byte) (int, error) {
  348. glog.V(2).Infof("WebDavFileSystem.Write %v", f.name)
  349. dir, _ := util.FullPath(f.name).DirAndName()
  350. var getErr error
  351. ctx := context.Background()
  352. if f.entry == nil {
  353. f.entry, getErr = filer_pb.GetEntry(f.fs, util.FullPath(f.name))
  354. }
  355. if f.entry == nil {
  356. return 0, getErr
  357. }
  358. if getErr != nil {
  359. return 0, getErr
  360. }
  361. if f.bufWriter.FlushFunc == nil {
  362. f.bufWriter.FlushFunc = func(data []byte, offset int64) (flushErr error) {
  363. var chunk *filer_pb.FileChunk
  364. chunk, f.collection, f.replication, flushErr = f.saveDataAsChunk(bytes.NewReader(data), f.name, offset)
  365. if flushErr != nil {
  366. return fmt.Errorf("%s upload result: %v", f.name, flushErr)
  367. }
  368. f.entry.Content = nil
  369. f.entry.Chunks = append(f.entry.Chunks, chunk)
  370. return flushErr
  371. }
  372. f.bufWriter.CloseFunc = func() error {
  373. manifestedChunks, manifestErr := filer.MaybeManifestize(f.saveDataAsChunk, f.entry.Chunks)
  374. if manifestErr != nil {
  375. // not good, but should be ok
  376. glog.V(0).Infof("file %s close MaybeManifestize: %v", f.name, manifestErr)
  377. } else {
  378. f.entry.Chunks = manifestedChunks
  379. }
  380. flushErr := f.fs.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
  381. f.entry.Attributes.Mtime = time.Now().Unix()
  382. f.entry.Attributes.Collection = f.collection
  383. f.entry.Attributes.Replication = f.replication
  384. request := &filer_pb.UpdateEntryRequest{
  385. Directory: dir,
  386. Entry: f.entry,
  387. Signatures: []int32{f.fs.signature},
  388. }
  389. if _, err := client.UpdateEntry(ctx, request); err != nil {
  390. return fmt.Errorf("update %s: %v", f.name, err)
  391. }
  392. return nil
  393. })
  394. return flushErr
  395. }
  396. }
  397. written, err := f.bufWriter.Write(buf)
  398. if err == nil {
  399. glog.V(3).Infof("WebDavFileSystem.Write %v: written [%d,%d)", f.name, f.off, f.off+int64(len(buf)))
  400. f.off += int64(written)
  401. }
  402. return written, err
  403. }
  404. func (f *WebDavFile) Close() error {
  405. glog.V(2).Infof("WebDavFileSystem.Close %v", f.name)
  406. err := f.bufWriter.Close()
  407. if f.entry != nil {
  408. f.entry = nil
  409. f.entryViewCache = nil
  410. }
  411. return err
  412. }
  413. func (f *WebDavFile) Read(p []byte) (readSize int, err error) {
  414. glog.V(2).Infof("WebDavFileSystem.Read %v", f.name)
  415. if f.entry == nil {
  416. f.entry, err = filer_pb.GetEntry(f.fs, util.FullPath(f.name))
  417. }
  418. if f.entry == nil {
  419. return 0, err
  420. }
  421. if err != nil {
  422. return 0, err
  423. }
  424. fileSize := int64(filer.FileSize(f.entry))
  425. if fileSize == 0 {
  426. return 0, io.EOF
  427. }
  428. if f.entryViewCache == nil {
  429. f.entryViewCache, _ = filer.NonOverlappingVisibleIntervals(filer.LookupFn(f.fs), f.entry.Chunks)
  430. f.reader = nil
  431. }
  432. if f.reader == nil {
  433. chunkViews := filer.ViewFromVisibleIntervals(f.entryViewCache, 0, math.MaxInt64)
  434. f.reader = filer.NewChunkReaderAtFromClient(filer.LookupFn(f.fs), chunkViews, f.fs.chunkCache, fileSize)
  435. }
  436. readSize, err = f.reader.ReadAt(p, f.off)
  437. glog.V(3).Infof("WebDavFileSystem.Read %v: [%d,%d)", f.name, f.off, f.off+int64(readSize))
  438. f.off += int64(readSize)
  439. if err != nil && err != io.EOF {
  440. glog.Errorf("file read %s: %v", f.name, err)
  441. }
  442. return
  443. }
  444. func (f *WebDavFile) Readdir(count int) (ret []os.FileInfo, err error) {
  445. glog.V(2).Infof("WebDavFileSystem.Readdir %v count %d", f.name, count)
  446. dir, _ := util.FullPath(f.name).DirAndName()
  447. err = filer_pb.ReadDirAllEntries(f.fs, util.FullPath(dir), "", func(entry *filer_pb.Entry, isLast bool) error {
  448. fi := FileInfo{
  449. size: int64(filer.FileSize(entry)),
  450. name: entry.Name,
  451. mode: os.FileMode(entry.Attributes.FileMode),
  452. modifiledTime: time.Unix(entry.Attributes.Mtime, 0),
  453. isDirectory: entry.IsDirectory,
  454. }
  455. if !strings.HasSuffix(fi.name, "/") && fi.IsDir() {
  456. fi.name += "/"
  457. }
  458. glog.V(4).Infof("entry: %v", fi.name)
  459. ret = append(ret, &fi)
  460. return nil
  461. })
  462. old := f.off
  463. if old >= int64(len(ret)) {
  464. if count > 0 {
  465. return nil, io.EOF
  466. }
  467. return nil, nil
  468. }
  469. if count > 0 {
  470. f.off += int64(count)
  471. if f.off > int64(len(ret)) {
  472. f.off = int64(len(ret))
  473. }
  474. } else {
  475. f.off = int64(len(ret))
  476. old = 0
  477. }
  478. return ret[old:f.off], nil
  479. }
  480. func (f *WebDavFile) Seek(offset int64, whence int) (int64, error) {
  481. glog.V(2).Infof("WebDavFile.Seek %v %v %v", f.name, offset, whence)
  482. ctx := context.Background()
  483. var err error
  484. switch whence {
  485. case io.SeekStart:
  486. f.off = 0
  487. case io.SeekEnd:
  488. if fi, err := f.fs.stat(ctx, f.name); err != nil {
  489. return 0, err
  490. } else {
  491. f.off = fi.Size()
  492. }
  493. }
  494. f.off += offset
  495. return f.off, err
  496. }
  497. func (f *WebDavFile) Stat() (os.FileInfo, error) {
  498. glog.V(2).Infof("WebDavFile.Stat %v", f.name)
  499. ctx := context.Background()
  500. return f.fs.stat(ctx, f.name)
  501. }