You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

604 lines
14 KiB

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