├── README.md ├── cmd └── davfs │ └── davfs.go ├── davfs.go └── plugin ├── file └── file.go ├── memory └── memory.go ├── mysql └── mysql.go ├── postgres └── postgres.go └── sqlite3 └── sqlite3.go /README.md: -------------------------------------------------------------------------------- 1 | # davfs 2 | 3 | WebDAV filesystem 4 | 5 | ## Usage 6 | 7 | ``` 8 | $ davfs 9 | ``` 10 | 11 | ## Supported Drivers 12 | 13 | |Driver |Options to be specified | 14 | |----------|----------------------------------| 15 | |file |-driver=file -source=/path/to/root| 16 | |memory |-driver=memory | 17 | |sqlite3 |-driver=sqlite3 -source=fs.db | 18 | |mysql |-driver=mysql -source=blah... | 19 | |postgresql|-driver=postgres -source=blah... | 20 | 21 | 22 | ## Installation 23 | 24 | ``` 25 | $ go get github.com/mattn/davfs/cmd/davfs 26 | ``` 27 | 28 | At the first time, you need to create filesystem for database drivers you specified like below. 29 | 30 | ``` 31 | $ davfs -driver=sqlite3 -source=fs.db -create 32 | ``` 33 | 34 | ## License 35 | 36 | MIT 37 | 38 | ## Author 39 | 40 | Yasuhiro Matsumoto (a.k.a mattn) 41 | -------------------------------------------------------------------------------- /cmd/davfs/davfs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "net/url" 8 | "os" 9 | "strings" 10 | 11 | "github.com/fatih/color" 12 | "github.com/mattn/go-colorable" 13 | 14 | "github.com/mattn/davfs" 15 | _ "github.com/mattn/davfs/plugin/file" 16 | _ "github.com/mattn/davfs/plugin/memory" 17 | _ "github.com/mattn/davfs/plugin/mysql" 18 | _ "github.com/mattn/davfs/plugin/postgres" 19 | _ "github.com/mattn/davfs/plugin/sqlite3" 20 | "golang.org/x/net/webdav" 21 | ) 22 | 23 | var ( 24 | addr = flag.String("addr", ":9999", "server address") 25 | driver = flag.String("driver", "file", "database driver") 26 | source = flag.String("source", ".", "database connection string") 27 | cred = flag.String("cred", "", "credential for basic auth") 28 | create = flag.Bool("create", false, "create filesystem") 29 | ) 30 | 31 | func errorString(err error) string { 32 | if err != nil { 33 | return err.Error() 34 | } 35 | return "" 36 | } 37 | 38 | func main() { 39 | flag.Parse() 40 | 41 | log.SetOutput(colorable.NewColorableStderr()) 42 | 43 | if *create { 44 | err := davfs.CreateFS(*driver, *source) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | os.Exit(0) 49 | } 50 | fs, err := davfs.NewFS(*driver, *source) 51 | if err != nil { 52 | log.Fatal(err) 53 | } 54 | 55 | dav := &webdav.Handler{ 56 | FileSystem: fs, 57 | LockSystem: webdav.NewMemLS(), 58 | Logger: func(r *http.Request, err error) { 59 | litmus := r.Header.Get("X-Litmus") 60 | if len(litmus) > 19 { 61 | litmus = litmus[:16] + "..." 62 | } 63 | 64 | switch r.Method { 65 | case "COPY", "MOVE": 66 | dst := "" 67 | if u, err := url.Parse(r.Header.Get("Destination")); err == nil { 68 | dst = u.Path 69 | } 70 | log.Printf("%-18s %s %s %s", 71 | color.GreenString(r.Method), 72 | r.URL.Path, 73 | dst, 74 | color.RedString(errorString(err))) 75 | default: 76 | log.Printf("%-18s %s %s", 77 | color.GreenString(r.Method), 78 | r.URL.Path, 79 | color.RedString(errorString(err))) 80 | } 81 | }, 82 | } 83 | 84 | var handler http.Handler 85 | if *cred != "" { 86 | token := strings.SplitN(*cred, ":", 2) 87 | if len(token) != 2 { 88 | flag.Usage() 89 | return 90 | } 91 | user, pass := token[0], token[1] 92 | handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 93 | username, password, ok := r.BasicAuth() 94 | if !ok || username != user || password != pass { 95 | w.Header().Set("WWW-Authenticate", `Basic realm="davfs"`) 96 | http.Error(w, "authorization failed", http.StatusUnauthorized) 97 | return 98 | } 99 | dav.ServeHTTP(w, r) 100 | }) 101 | } else { 102 | handler = dav 103 | } 104 | 105 | log.Print(color.CyanString("Server started %v", *addr)) 106 | http.Handle("/", handler) 107 | log.Fatal(http.ListenAndServe(*addr, nil)) 108 | } 109 | -------------------------------------------------------------------------------- /davfs.go: -------------------------------------------------------------------------------- 1 | package davfs 2 | 3 | import ( 4 | "os" 5 | 6 | "golang.org/x/net/webdav" 7 | ) 8 | 9 | type Driver interface { 10 | Mount(source string) (webdav.FileSystem, error) 11 | CreateFS(source string) error 12 | } 13 | 14 | var drivers = map[string]Driver{} 15 | 16 | func Register(name string, driver Driver) { 17 | drivers[name] = driver 18 | } 19 | 20 | func NewFS(driver, source string) (webdav.FileSystem, error) { 21 | if d, ok := drivers[driver]; ok { 22 | return d.Mount(source) 23 | } 24 | return nil, os.ErrNotExist 25 | } 26 | 27 | func CreateFS(driver, source string) error { 28 | if d, ok := drivers[driver]; ok { 29 | return d.CreateFS(source) 30 | } 31 | return os.ErrNotExist 32 | } 33 | -------------------------------------------------------------------------------- /plugin/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "github.com/mattn/davfs" 5 | "golang.org/x/net/webdav" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func init() { 11 | davfs.Register("file", &Driver{}) 12 | } 13 | 14 | type Driver struct { 15 | } 16 | 17 | func (d *Driver) Mount(source string) (webdav.FileSystem, error) { 18 | if source == "" { 19 | source = "." 20 | } 21 | if s, err := filepath.Abs(source); err == nil { 22 | source = s 23 | } 24 | return webdav.Dir(source), nil 25 | } 26 | 27 | func (d *Driver) CreateFS(source string) error { 28 | if source == "" { 29 | source = "." 30 | } 31 | if s, err := filepath.Abs(source); err == nil { 32 | source = s 33 | } 34 | return os.Mkdir(source, 0755) 35 | } 36 | -------------------------------------------------------------------------------- /plugin/memory/memory.go: -------------------------------------------------------------------------------- 1 | package memory 2 | 3 | import ( 4 | "github.com/mattn/davfs" 5 | "golang.org/x/net/webdav" 6 | ) 7 | 8 | func init() { 9 | davfs.Register("memory", &Driver{}) 10 | } 11 | 12 | type Driver struct { 13 | } 14 | 15 | func (d *Driver) Mount(source string) (webdav.FileSystem, error) { 16 | return webdav.NewMemFS(), nil 17 | } 18 | 19 | func (d *Driver) CreateFS(source string) error { 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /plugin/mysql/mysql.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/hex" 6 | "io" 7 | "log" 8 | "os" 9 | "path" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | _ "github.com/go-sql-driver/mysql" 15 | "github.com/mattn/davfs" 16 | "golang.org/x/net/context" 17 | "golang.org/x/net/webdav" 18 | ) 19 | 20 | const createSQL = ` 21 | create table filesystem( 22 | name text(255) not null, 23 | content longtext not null, 24 | mode bigint not null, 25 | mod_time datetime not null, 26 | primary key (name(255)) 27 | ) default charset=utf8; 28 | ` 29 | 30 | const insertSQL = ` 31 | insert into filesystem(name, content, mode, mod_time) values('/', '', 2147484159, now()); 32 | ` 33 | 34 | func init() { 35 | davfs.Register("mysql", &Driver{}) 36 | } 37 | 38 | type Driver struct { 39 | } 40 | 41 | type FileSystem struct { 42 | db *sql.DB 43 | mu sync.Mutex 44 | Debug bool 45 | } 46 | 47 | type FileInfo struct { 48 | name string 49 | size int64 50 | mode os.FileMode 51 | mod_time time.Time 52 | } 53 | 54 | func (fi *FileInfo) Name() string { return fi.name } 55 | func (fi *FileInfo) Size() int64 { return fi.size } 56 | func (fi *FileInfo) Mode() os.FileMode { return fi.mode } 57 | func (fi *FileInfo) ModTime() time.Time { return fi.mod_time } 58 | func (fi *FileInfo) IsDir() bool { return fi.mode.IsDir() } 59 | func (fi *FileInfo) Sys() interface{} { return nil } 60 | 61 | type File struct { 62 | fs *FileSystem 63 | name string 64 | off int64 65 | children []os.FileInfo 66 | } 67 | 68 | func (d *Driver) Mount(source string) (webdav.FileSystem, error) { 69 | db, err := sql.Open("mysql", source) 70 | if err != nil { 71 | return nil, err 72 | } 73 | return &FileSystem{db: db, Debug: true}, nil 74 | } 75 | 76 | func (d *Driver) CreateFS(source string) error { 77 | db, err := sql.Open("mysql", source) 78 | if err != nil { 79 | return err 80 | } 81 | defer db.Close() 82 | _, err = db.Exec(createSQL) 83 | if err != nil { 84 | return err 85 | } 86 | _, err = db.Exec(insertSQL) 87 | if err != nil { 88 | return err 89 | } 90 | return nil 91 | } 92 | 93 | func clearName(name string) (string, error) { 94 | slashed := strings.HasSuffix(name, "/") 95 | name = path.Clean(name) 96 | if !strings.HasSuffix(name, "/") && slashed { 97 | name += "/" 98 | } 99 | if !strings.HasPrefix(name, "/") { 100 | return "", os.ErrInvalid 101 | } 102 | return name, nil 103 | } 104 | 105 | func (fs *FileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error { 106 | fs.mu.Lock() 107 | defer fs.mu.Unlock() 108 | 109 | if fs.Debug { 110 | log.Printf("FileSystem.Mkdir %v", name) 111 | } 112 | 113 | if !strings.HasSuffix(name, "/") { 114 | name += "/" 115 | } 116 | 117 | var err error 118 | if name, err = clearName(name); err != nil { 119 | return err 120 | } 121 | 122 | _, err = fs.stat(name) 123 | if err == nil { 124 | return os.ErrExist 125 | } 126 | 127 | base := "/" 128 | for _, elem := range strings.Split(strings.Trim(name, "/"), "/") { 129 | base += elem + "/" 130 | _, err = fs.stat(base) 131 | if err != os.ErrNotExist { 132 | return err 133 | } 134 | _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values(?, '', ?, now())`, base, perm.Perm()|os.ModeDir) 135 | if err != nil { 136 | return err 137 | } 138 | } 139 | return nil 140 | } 141 | 142 | func (fs *FileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { 143 | fs.mu.Lock() 144 | defer fs.mu.Unlock() 145 | 146 | if fs.Debug { 147 | log.Printf("FileSystem.OpenFile %v", name) 148 | } 149 | 150 | var err error 151 | if name, err = clearName(name); err != nil { 152 | return nil, err 153 | } 154 | 155 | if flag&os.O_CREATE != 0 { 156 | // file should not have / suffix. 157 | if strings.HasSuffix(name, "/") { 158 | return nil, os.ErrInvalid 159 | } 160 | // based directory should be exists. 161 | dir, _ := path.Split(name) 162 | _, err := fs.stat(dir) 163 | if err != nil { 164 | return nil, os.ErrInvalid 165 | } 166 | _, err = fs.stat(name) 167 | if err == nil { 168 | if flag&os.O_EXCL != 0 { 169 | return nil, os.ErrExist 170 | } 171 | fs.removeAll(name) 172 | } 173 | _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values(?, '', ?, now())`, name, perm.Perm()) 174 | if err != nil { 175 | return nil, err 176 | } 177 | return &File{fs, name, 0, nil}, nil 178 | } 179 | 180 | fi, err := fs.stat(name) 181 | if err != nil { 182 | return nil, os.ErrNotExist 183 | } 184 | if !strings.HasSuffix(name, "/") && fi.IsDir() { 185 | name += "/" 186 | } 187 | return &File{fs, name, 0, nil}, nil 188 | } 189 | 190 | func (fs *FileSystem) removeAll(name string) error { 191 | var err error 192 | if name, err = clearName(name); err != nil { 193 | return err 194 | } 195 | 196 | fi, err := fs.stat(name) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | if fi.IsDir() { 202 | _, err = fs.db.Exec(`delete from filesystem where name like $1 escape '\'`, strings.Replace(name, `%`, `\%`, -1)+`%`) 203 | } else { 204 | _, err = fs.db.Exec(`delete from filesystem where name = ?`, name) 205 | } 206 | return err 207 | } 208 | 209 | func (fs *FileSystem) RemoveAll(ctx context.Context, name string) error { 210 | fs.mu.Lock() 211 | defer fs.mu.Unlock() 212 | 213 | if fs.Debug { 214 | log.Printf("FileSystem.RemoveAll %v", name) 215 | } 216 | 217 | return fs.removeAll(name) 218 | } 219 | 220 | func (fs *FileSystem) Rename(ctx context.Context, oldName, newName string) error { 221 | fs.mu.Lock() 222 | defer fs.mu.Unlock() 223 | 224 | if fs.Debug { 225 | log.Printf("FileSystem.Rename %v %v", oldName, newName) 226 | } 227 | 228 | var err error 229 | if oldName, err = clearName(oldName); err != nil { 230 | return err 231 | } 232 | if newName, err = clearName(newName); err != nil { 233 | return err 234 | } 235 | 236 | of, err := fs.stat(oldName) 237 | if err != nil { 238 | return os.ErrExist 239 | } 240 | if of.IsDir() && !strings.HasSuffix(oldName, "/") { 241 | oldName += "/" 242 | newName += "/" 243 | } 244 | 245 | _, err = fs.stat(newName) 246 | if err == nil { 247 | return os.ErrExist 248 | } 249 | 250 | _, err = fs.db.Exec(`update filesystem set name = ? where name = ?`, newName, oldName) 251 | return err 252 | } 253 | 254 | func (fs *FileSystem) stat(name string) (os.FileInfo, error) { 255 | var err error 256 | if name, err = clearName(name); err != nil { 257 | return nil, err 258 | } 259 | 260 | rows, err := fs.db.Query(`select name, format(length(content)/2, 0), mode, mod_time from filesystem where name = ?`, name) 261 | if err != nil { 262 | return nil, err 263 | } 264 | if !rows.Next() { 265 | rows.Close() 266 | if strings.HasSuffix(name, "/") { 267 | return nil, os.ErrNotExist 268 | } 269 | rows, err = fs.db.Query(`select name, format(length(content)/2, 0), mode, mod_time from filesystem where name = ?`, name+"/") 270 | if err != nil { 271 | return nil, err 272 | } 273 | if !rows.Next() { 274 | rows.Close() 275 | return nil, os.ErrNotExist 276 | } 277 | } 278 | defer rows.Close() 279 | var fi FileInfo 280 | err = rows.Scan(&fi.name, &fi.size, &fi.mode, &fi.mod_time) 281 | if err != nil { 282 | return nil, err 283 | } 284 | _, fi.name = path.Split(path.Clean(fi.name)) 285 | if fi.name == "" { 286 | fi.name = "/" 287 | fi.mod_time = time.Now() 288 | } 289 | return &fi, nil 290 | } 291 | 292 | func (fs *FileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) { 293 | fs.mu.Lock() 294 | defer fs.mu.Unlock() 295 | 296 | if fs.Debug { 297 | log.Printf("FileSystem.Stat %v", name) 298 | } 299 | 300 | return fs.stat(name) 301 | } 302 | 303 | func (f *File) Write(p []byte) (int, error) { 304 | f.fs.mu.Lock() 305 | defer f.fs.mu.Unlock() 306 | 307 | if f.fs.Debug { 308 | log.Printf("File.Write %v", f.name) 309 | } 310 | _, err := f.fs.db.Exec(`update filesystem set content = substr(content, 1, ?) || ? where name = ?`, f.off*2, hex.EncodeToString(p), f.name) 311 | if err != nil { 312 | return 0, err 313 | } 314 | f.off += int64(len(p)) 315 | return len(p), err 316 | } 317 | 318 | func (f *File) Close() error { 319 | if f.fs.Debug { 320 | log.Printf("File.Close %v", f.name) 321 | } 322 | 323 | return nil 324 | } 325 | 326 | func (f *File) Read(p []byte) (int, error) { 327 | f.fs.mu.Lock() 328 | defer f.fs.mu.Unlock() 329 | 330 | if f.fs.Debug { 331 | log.Printf("File.Read %v", f.name) 332 | } 333 | 334 | rows, err := f.fs.db.Query(`select mode, substr(content, ?, ?) from filesystem where name = ?`, 1+f.off*2, len(p)*2, f.name) 335 | if err != nil { 336 | return 0, err 337 | } 338 | defer rows.Close() 339 | 340 | if !rows.Next() { 341 | return 0, os.ErrInvalid 342 | } 343 | var content string 344 | var mode os.FileMode 345 | err = rows.Scan(&mode, &content) 346 | if err != nil { 347 | return 0, err 348 | } 349 | if mode.IsDir() { 350 | return 0, os.ErrInvalid 351 | } 352 | b, err := hex.DecodeString(content) 353 | if err != nil { 354 | return 0, err 355 | } 356 | copy(p, b) 357 | bl := len(b) 358 | f.off += int64(bl) 359 | if bl == 0 { 360 | return 0, io.EOF 361 | } 362 | return bl, nil 363 | } 364 | 365 | func (f *File) Readdir(count int) ([]os.FileInfo, error) { 366 | f.fs.mu.Lock() 367 | defer f.fs.mu.Unlock() 368 | 369 | if f.fs.Debug { 370 | log.Printf("File.Readdir %v", f.name) 371 | } 372 | 373 | if f.children == nil { 374 | rows, err := f.fs.db.Query(`select name from filesystem where name <> ? and name like ? escape '\'`, f.name, strings.Replace(f.name, `%`, `\%`, -1)+"%") 375 | if err != nil { 376 | return nil, err 377 | } 378 | defer rows.Close() 379 | 380 | f.children = []os.FileInfo{} 381 | for rows.Next() { 382 | var name string 383 | err = rows.Scan(&name) 384 | if err != nil { 385 | return nil, err 386 | } 387 | part := strings.TrimRight(name[len(f.name):], "/") 388 | if strings.IndexRune(part, '/') != -1 { 389 | continue 390 | } 391 | fi, err := f.fs.stat(name) 392 | if err != nil { 393 | return nil, err 394 | } 395 | f.children = append(f.children, fi) 396 | } 397 | } 398 | 399 | old := f.off 400 | if old >= int64(len(f.children)) { 401 | if count > 0 { 402 | return nil, io.EOF 403 | } 404 | return nil, nil 405 | } 406 | if count > 0 { 407 | f.off += int64(count) 408 | if f.off > int64(len(f.children)) { 409 | f.off = int64(len(f.children)) 410 | } 411 | } else { 412 | f.off = int64(len(f.children)) 413 | old = 0 414 | } 415 | return f.children[old:f.off], nil 416 | } 417 | 418 | func (f *File) Seek(offset int64, whence int) (int64, error) { 419 | f.fs.mu.Lock() 420 | defer f.fs.mu.Unlock() 421 | 422 | if f.fs.Debug { 423 | log.Printf("File.Seek %v %v %v", f.name, offset, whence) 424 | } 425 | 426 | var err error 427 | switch whence { 428 | case 0: 429 | f.off = 0 430 | case 2: 431 | if fi, err := f.fs.stat(f.name); err != nil { 432 | return 0, err 433 | } else { 434 | f.off = fi.Size() 435 | } 436 | } 437 | f.off += offset 438 | return f.off, err 439 | } 440 | 441 | func (f *File) Stat() (os.FileInfo, error) { 442 | f.fs.mu.Lock() 443 | defer f.fs.mu.Unlock() 444 | 445 | if f.fs.Debug { 446 | log.Printf("File.Stat %v", f.name) 447 | } 448 | 449 | return f.fs.stat(f.name) 450 | } 451 | -------------------------------------------------------------------------------- /plugin/postgres/postgres.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/hex" 6 | "io" 7 | "log" 8 | "os" 9 | "path" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | _ "github.com/lib/pq" 15 | "github.com/mattn/davfs" 16 | "golang.org/x/net/context" 17 | "golang.org/x/net/webdav" 18 | ) 19 | 20 | const createSQL = ` 21 | create table filesystem( 22 | name text not null, 23 | content text not null, 24 | mode bigint not null, 25 | mod_time timestamp not null, 26 | primary key (name) 27 | ); 28 | 29 | insert into filesystem(name, content, mode, mod_time) values('/', '', 2147484159, current_timestamp); 30 | ` 31 | 32 | func init() { 33 | davfs.Register("postgres", &Driver{}) 34 | } 35 | 36 | type Driver struct { 37 | } 38 | 39 | type FileSystem struct { 40 | db *sql.DB 41 | mu sync.Mutex 42 | Debug bool 43 | } 44 | 45 | type FileInfo struct { 46 | name string 47 | size int64 48 | mode os.FileMode 49 | mod_time time.Time 50 | } 51 | 52 | type File struct { 53 | fs *FileSystem 54 | name string 55 | off int64 56 | children []os.FileInfo 57 | } 58 | 59 | func (d *Driver) Mount(source string) (webdav.FileSystem, error) { 60 | db, err := sql.Open("postgres", source) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return &FileSystem{db: db}, nil 65 | } 66 | 67 | func (d *Driver) CreateFS(source string) error { 68 | db, err := sql.Open("postgres", source) 69 | if err != nil { 70 | return err 71 | } 72 | defer db.Close() 73 | _, err = db.Exec(createSQL) 74 | if err != nil { 75 | return err 76 | } 77 | return nil 78 | } 79 | 80 | func clearName(name string) (string, error) { 81 | slashed := strings.HasSuffix(name, "/") 82 | name = path.Clean(name) 83 | if !strings.HasSuffix(name, "/") && slashed { 84 | name += "/" 85 | } 86 | if !strings.HasPrefix(name, "/") { 87 | return "", os.ErrInvalid 88 | } 89 | return name, nil 90 | } 91 | 92 | func (fs *FileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error { 93 | fs.mu.Lock() 94 | defer fs.mu.Unlock() 95 | 96 | if fs.Debug { 97 | log.Printf("FileSystem.Mkdir %v", name) 98 | } 99 | 100 | if !strings.HasSuffix(name, "/") { 101 | name += "/" 102 | } 103 | 104 | var err error 105 | if name, err = clearName(name); err != nil { 106 | return err 107 | } 108 | 109 | _, err = fs.stat(name) 110 | if err == nil { 111 | return os.ErrExist 112 | } 113 | 114 | base := "/" 115 | for _, elem := range strings.Split(strings.Trim(name, "/"), "/") { 116 | base += elem + "/" 117 | _, err = fs.stat(base) 118 | if err != os.ErrNotExist { 119 | return err 120 | } 121 | _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values($1, '', $2, current_timestamp)`, base, perm.Perm()|os.ModeDir) 122 | if err != nil { 123 | return err 124 | } 125 | } 126 | return nil 127 | } 128 | 129 | func (fs *FileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { 130 | fs.mu.Lock() 131 | defer fs.mu.Unlock() 132 | 133 | if fs.Debug { 134 | log.Printf("FileSystem.OpenFile %v", name) 135 | } 136 | 137 | var err error 138 | if name, err = clearName(name); err != nil { 139 | return nil, err 140 | } 141 | 142 | if flag&os.O_CREATE != 0 { 143 | // file should not have / suffix. 144 | if strings.HasSuffix(name, "/") { 145 | return nil, os.ErrInvalid 146 | } 147 | // based directory should be exists. 148 | dir, _ := path.Split(name) 149 | _, err := fs.stat(dir) 150 | if err != nil { 151 | return nil, os.ErrInvalid 152 | } 153 | _, err = fs.stat(name) 154 | if err == nil { 155 | if flag&os.O_EXCL != 0 { 156 | return nil, os.ErrExist 157 | } 158 | fs.removeAll(name) 159 | } 160 | _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values($1, '', $2, current_timestamp)`, name, perm.Perm()) 161 | if err != nil { 162 | return nil, err 163 | } 164 | return &File{fs, name, 0, nil}, nil 165 | } 166 | 167 | fi, err := fs.stat(name) 168 | if err != nil { 169 | return nil, os.ErrNotExist 170 | } 171 | if !strings.HasSuffix(name, "/") && fi.IsDir() { 172 | name += "/" 173 | } 174 | return &File{fs, name, 0, nil}, nil 175 | } 176 | 177 | func (fs *FileSystem) removeAll(name string) error { 178 | var err error 179 | if name, err = clearName(name); err != nil { 180 | return err 181 | } 182 | 183 | fi, err := fs.stat(name) 184 | if err != nil { 185 | return err 186 | } 187 | 188 | if fi.IsDir() { 189 | _, err = fs.db.Exec(`delete from filesystem where name like $1 escape '\'`, strings.Replace(name, `%`, `\%`, -1)+`%`) 190 | } else { 191 | _, err = fs.db.Exec(`delete from filesystem where name = $1`, name) 192 | } 193 | return err 194 | } 195 | 196 | func (fs *FileSystem) RemoveAll(ctx context.Context, name string) error { 197 | fs.mu.Lock() 198 | defer fs.mu.Unlock() 199 | 200 | if fs.Debug { 201 | log.Printf("FileSystem.RemoveAll %v", name) 202 | } 203 | 204 | return fs.removeAll(name) 205 | } 206 | 207 | func (fs *FileSystem) Rename(ctx context.Context, oldName, newName string) error { 208 | fs.mu.Lock() 209 | defer fs.mu.Unlock() 210 | 211 | if fs.Debug { 212 | log.Printf("FileSystem.Rename %v %v", oldName, newName) 213 | } 214 | 215 | var err error 216 | if oldName, err = clearName(oldName); err != nil { 217 | return err 218 | } 219 | if newName, err = clearName(newName); err != nil { 220 | return err 221 | } 222 | 223 | of, err := fs.stat(oldName) 224 | if err != nil { 225 | return os.ErrExist 226 | } 227 | if of.IsDir() && !strings.HasSuffix(oldName, "/") { 228 | oldName += "/" 229 | newName += "/" 230 | } 231 | 232 | _, err = fs.stat(newName) 233 | if err == nil { 234 | return os.ErrExist 235 | } 236 | 237 | _, err = fs.db.Exec(`update filesystem set name = $1 where name = $2`, newName, oldName) 238 | return err 239 | } 240 | 241 | func (fs *FileSystem) stat(name string) (os.FileInfo, error) { 242 | var err error 243 | if name, err = clearName(name); err != nil { 244 | return nil, err 245 | } 246 | 247 | rows, err := fs.db.Query(`select name, length(content)/2, mode, mod_time from filesystem where name = $1`, name) 248 | if err != nil { 249 | return nil, err 250 | } 251 | if !rows.Next() { 252 | rows.Close() 253 | if strings.HasSuffix(name, "/") { 254 | return nil, os.ErrNotExist 255 | } 256 | rows, err = fs.db.Query(`select name, length(content)/2, mode, mod_time from filesystem where name = $1`, name+"/") 257 | if err != nil { 258 | return nil, err 259 | } 260 | if !rows.Next() { 261 | rows.Close() 262 | return nil, os.ErrNotExist 263 | } 264 | } 265 | defer rows.Close() 266 | var fi FileInfo 267 | err = rows.Scan(&fi.name, &fi.size, &fi.mode, &fi.mod_time) 268 | if err != nil { 269 | return nil, err 270 | } 271 | _, fi.name = path.Split(path.Clean(fi.name)) 272 | if fi.name == "" { 273 | fi.name = "/" 274 | fi.mod_time = time.Now() 275 | } 276 | return &fi, nil 277 | } 278 | 279 | func (fs *FileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) { 280 | fs.mu.Lock() 281 | defer fs.mu.Unlock() 282 | 283 | if fs.Debug { 284 | log.Printf("FileSystem.Stat %v", name) 285 | } 286 | 287 | return fs.stat(name) 288 | } 289 | 290 | func (fi *FileInfo) Name() string { return fi.name } 291 | func (fi *FileInfo) Size() int64 { return fi.size } 292 | func (fi *FileInfo) Mode() os.FileMode { return fi.mode } 293 | func (fi *FileInfo) ModTime() time.Time { return fi.mod_time } 294 | func (fi *FileInfo) IsDir() bool { return fi.mode.IsDir() } 295 | func (fi *FileInfo) Sys() interface{} { return nil } 296 | 297 | func (f *File) Write(p []byte) (int, error) { 298 | f.fs.mu.Lock() 299 | defer f.fs.mu.Unlock() 300 | 301 | if f.fs.Debug { 302 | log.Printf("File.Write %v", f.name) 303 | } 304 | _, err := f.fs.db.Exec(`update filesystem set content = substr(content, 1, $1) || $2 where name = $3`, f.off*2, hex.EncodeToString(p), f.name) 305 | if err != nil { 306 | return 0, err 307 | } 308 | f.off += int64(len(p)) 309 | return len(p), err 310 | } 311 | 312 | func (f *File) Close() error { 313 | if f.fs.Debug { 314 | log.Printf("File.Close %v", f.name) 315 | } 316 | 317 | return nil 318 | } 319 | 320 | func (f *File) Read(p []byte) (int, error) { 321 | f.fs.mu.Lock() 322 | defer f.fs.mu.Unlock() 323 | 324 | if f.fs.Debug { 325 | log.Printf("File.Read %v", f.name) 326 | } 327 | 328 | rows, err := f.fs.db.Query(`select mode, substr(content, $1, $2) from filesystem where name = $3`, 1+f.off*2, len(p)*2, f.name) 329 | if err != nil { 330 | return 0, err 331 | } 332 | defer rows.Close() 333 | 334 | if !rows.Next() { 335 | return 0, os.ErrInvalid 336 | } 337 | var content string 338 | var mode os.FileMode 339 | err = rows.Scan(&mode, &content) 340 | if err != nil { 341 | return 0, err 342 | } 343 | if mode.IsDir() { 344 | return 0, os.ErrInvalid 345 | } 346 | b, err := hex.DecodeString(content) 347 | if err != nil { 348 | return 0, err 349 | } 350 | copy(p, b) 351 | bl := len(b) 352 | f.off += int64(bl) 353 | if bl == 0 { 354 | return 0, io.EOF 355 | } 356 | return bl, nil 357 | } 358 | 359 | func (f *File) Readdir(count int) ([]os.FileInfo, error) { 360 | f.fs.mu.Lock() 361 | defer f.fs.mu.Unlock() 362 | 363 | if f.fs.Debug { 364 | log.Printf("File.Readdir %v", f.name) 365 | } 366 | 367 | if f.children == nil { 368 | rows, err := f.fs.db.Query(`select name from filesystem where name <> $1 and name like $2 escape '\'`, f.name, strings.Replace(f.name, `%`, `\%`, -1)+"%") 369 | if err != nil { 370 | return nil, err 371 | } 372 | defer rows.Close() 373 | 374 | f.children = []os.FileInfo{} 375 | for rows.Next() { 376 | var name string 377 | err = rows.Scan(&name) 378 | if err != nil { 379 | return nil, err 380 | } 381 | part := strings.TrimRight(name[len(f.name):], "/") 382 | if strings.IndexRune(part, '/') != -1 { 383 | continue 384 | } 385 | fi, err := f.fs.stat(name) 386 | if err != nil { 387 | return nil, err 388 | } 389 | f.children = append(f.children, fi) 390 | } 391 | } 392 | 393 | old := f.off 394 | if old >= int64(len(f.children)) { 395 | if count > 0 { 396 | return nil, io.EOF 397 | } 398 | return nil, nil 399 | } 400 | if count > 0 { 401 | f.off += int64(count) 402 | if f.off > int64(len(f.children)) { 403 | f.off = int64(len(f.children)) 404 | } 405 | } else { 406 | f.off = int64(len(f.children)) 407 | old = 0 408 | } 409 | return f.children[old:f.off], nil 410 | } 411 | 412 | func (f *File) Seek(offset int64, whence int) (int64, error) { 413 | f.fs.mu.Lock() 414 | defer f.fs.mu.Unlock() 415 | 416 | if f.fs.Debug { 417 | log.Printf("File.Seek %v %v %v", f.name, offset, whence) 418 | } 419 | 420 | var err error 421 | switch whence { 422 | case 0: 423 | f.off = 0 424 | case 2: 425 | if fi, err := f.fs.stat(f.name); err != nil { 426 | return 0, err 427 | } else { 428 | f.off = fi.Size() 429 | } 430 | } 431 | f.off += offset 432 | return f.off, err 433 | } 434 | 435 | func (f *File) Stat() (os.FileInfo, error) { 436 | f.fs.mu.Lock() 437 | defer f.fs.mu.Unlock() 438 | 439 | if f.fs.Debug { 440 | log.Printf("File.Stat %v", f.name) 441 | } 442 | 443 | return f.fs.stat(f.name) 444 | } 445 | -------------------------------------------------------------------------------- /plugin/sqlite3/sqlite3.go: -------------------------------------------------------------------------------- 1 | package sqlite3 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/hex" 6 | "io" 7 | "log" 8 | "os" 9 | "path" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/mattn/davfs" 15 | _ "github.com/mattn/go-sqlite3" 16 | "golang.org/x/net/context" 17 | "golang.org/x/net/webdav" 18 | ) 19 | 20 | const createSQL = ` 21 | create table filesystem( 22 | name text not null, 23 | content text not null, 24 | mode bigint not null, 25 | mod_time timestamp not null, 26 | primary key (name) 27 | ); 28 | 29 | insert into filesystem(name, content, mode, mod_time) values('/', '', 2147484159, current_timestamp); 30 | ` 31 | 32 | func init() { 33 | davfs.Register("sqlite3", &Driver{}) 34 | } 35 | 36 | type Driver struct { 37 | } 38 | 39 | type FileSystem struct { 40 | db *sql.DB 41 | mu sync.Mutex 42 | Debug bool 43 | } 44 | 45 | type FileInfo struct { 46 | name string 47 | size int64 48 | mode os.FileMode 49 | mod_time time.Time 50 | } 51 | 52 | type File struct { 53 | fs *FileSystem 54 | name string 55 | off int64 56 | children []os.FileInfo 57 | } 58 | 59 | func (d *Driver) Mount(source string) (webdav.FileSystem, error) { 60 | db, err := sql.Open("sqlite3", source) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return &FileSystem{db: db}, nil 65 | } 66 | 67 | func (d *Driver) CreateFS(source string) error { 68 | db, err := sql.Open("sqlite3", source) 69 | if err != nil { 70 | return err 71 | } 72 | defer db.Close() 73 | _, err = db.Exec(createSQL) 74 | if err != nil { 75 | return err 76 | } 77 | return nil 78 | } 79 | 80 | func clearName(name string) (string, error) { 81 | slashed := strings.HasSuffix(name, "/") 82 | name = path.Clean(name) 83 | if !strings.HasSuffix(name, "/") && slashed { 84 | name += "/" 85 | } 86 | if !strings.HasPrefix(name, "/") { 87 | return "", os.ErrInvalid 88 | } 89 | return name, nil 90 | } 91 | 92 | func (fs *FileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error { 93 | fs.mu.Lock() 94 | defer fs.mu.Unlock() 95 | 96 | if fs.Debug { 97 | log.Printf("FileSystem.Mkdir %v", name) 98 | } 99 | 100 | if !strings.HasSuffix(name, "/") { 101 | name += "/" 102 | } 103 | 104 | var err error 105 | if name, err = clearName(name); err != nil { 106 | return err 107 | } 108 | 109 | _, err = fs.stat(name) 110 | if err == nil { 111 | return os.ErrExist 112 | } 113 | 114 | base := "/" 115 | for _, elem := range strings.Split(strings.Trim(name, "/"), "/") { 116 | base += elem + "/" 117 | _, err = fs.stat(base) 118 | if err != os.ErrNotExist { 119 | return err 120 | } 121 | _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values($1, '', $2, current_timestamp)`, base, perm.Perm()|os.ModeDir) 122 | if err != nil { 123 | return err 124 | } 125 | } 126 | return nil 127 | } 128 | 129 | func (fs *FileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { 130 | fs.mu.Lock() 131 | defer fs.mu.Unlock() 132 | 133 | if fs.Debug { 134 | log.Printf("FileSystem.OpenFile %v", name) 135 | } 136 | 137 | var err error 138 | if name, err = clearName(name); err != nil { 139 | return nil, err 140 | } 141 | 142 | if flag&os.O_CREATE != 0 { 143 | // file should not have / suffix. 144 | if strings.HasSuffix(name, "/") { 145 | return nil, os.ErrInvalid 146 | } 147 | // based directory should be exists. 148 | dir, _ := path.Split(name) 149 | _, err := fs.stat(dir) 150 | if err != nil { 151 | return nil, os.ErrInvalid 152 | } 153 | _, err = fs.stat(name) 154 | if err == nil { 155 | if flag&os.O_EXCL != 0 { 156 | return nil, os.ErrExist 157 | } 158 | fs.removeAll(name) 159 | } 160 | _, err = fs.db.Exec(`insert into filesystem(name, content, mode, mod_time) values($1, '', $2, current_timestamp)`, name, perm.Perm()) 161 | if err != nil { 162 | return nil, err 163 | } 164 | return &File{fs, name, 0, nil}, nil 165 | } 166 | 167 | fi, err := fs.stat(name) 168 | if err != nil { 169 | return nil, os.ErrNotExist 170 | } 171 | if !strings.HasSuffix(name, "/") && fi.IsDir() { 172 | name += "/" 173 | } 174 | return &File{fs, name, 0, nil}, nil 175 | } 176 | 177 | func (fs *FileSystem) removeAll(name string) error { 178 | var err error 179 | if name, err = clearName(name); err != nil { 180 | return err 181 | } 182 | 183 | fi, err := fs.stat(name) 184 | if err != nil { 185 | return err 186 | } 187 | 188 | if fi.IsDir() { 189 | _, err = fs.db.Exec(`delete from filesystem where name like $1 escape '\'`, strings.Replace(name, `%`, `\%`, -1)+`%`) 190 | } else { 191 | _, err = fs.db.Exec(`delete from filesystem where name = $1`, name) 192 | } 193 | return err 194 | } 195 | 196 | func (fs *FileSystem) RemoveAll(ctx context.Context, name string) error { 197 | fs.mu.Lock() 198 | defer fs.mu.Unlock() 199 | 200 | if fs.Debug { 201 | log.Printf("FileSystem.RemoveAll %v", name) 202 | } 203 | 204 | return fs.removeAll(name) 205 | } 206 | 207 | func (fs *FileSystem) Rename(ctx context.Context, oldName, newName string) error { 208 | fs.mu.Lock() 209 | defer fs.mu.Unlock() 210 | 211 | if fs.Debug { 212 | log.Printf("FileSystem.Rename %v %v", oldName, newName) 213 | } 214 | 215 | var err error 216 | if oldName, err = clearName(oldName); err != nil { 217 | return err 218 | } 219 | if newName, err = clearName(newName); err != nil { 220 | return err 221 | } 222 | 223 | of, err := fs.stat(oldName) 224 | if err != nil { 225 | return os.ErrExist 226 | } 227 | if of.IsDir() && !strings.HasSuffix(oldName, "/") { 228 | oldName += "/" 229 | newName += "/" 230 | } 231 | 232 | _, err = fs.stat(newName) 233 | if err == nil { 234 | return os.ErrExist 235 | } 236 | 237 | _, err = fs.db.Exec(`update filesystem set name = $1 where name = $2`, newName, oldName) 238 | return err 239 | } 240 | 241 | func (fs *FileSystem) stat(name string) (os.FileInfo, error) { 242 | var err error 243 | if name, err = clearName(name); err != nil { 244 | return nil, err 245 | } 246 | 247 | rows, err := fs.db.Query(`select name, length(content)/2, mode, mod_time from filesystem where name = $1`, name) 248 | if err != nil { 249 | return nil, err 250 | } 251 | if !rows.Next() { 252 | rows.Close() 253 | if strings.HasSuffix(name, "/") { 254 | return nil, os.ErrNotExist 255 | } 256 | rows, err = fs.db.Query(`select name, length(content)/2, mode, mod_time from filesystem where name = $1`, name+"/") 257 | if err != nil { 258 | return nil, err 259 | } 260 | if !rows.Next() { 261 | rows.Close() 262 | return nil, os.ErrNotExist 263 | } 264 | } 265 | defer rows.Close() 266 | var fi FileInfo 267 | err = rows.Scan(&fi.name, &fi.size, &fi.mode, &fi.mod_time) 268 | if err != nil { 269 | return nil, err 270 | } 271 | _, fi.name = path.Split(path.Clean(fi.name)) 272 | if fi.name == "" { 273 | fi.name = "/" 274 | } 275 | return &fi, nil 276 | } 277 | 278 | func (fs *FileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) { 279 | fs.mu.Lock() 280 | defer fs.mu.Unlock() 281 | 282 | if fs.Debug { 283 | log.Printf("FileSystem.Stat %v", name) 284 | } 285 | 286 | return fs.stat(name) 287 | } 288 | 289 | func (fi *FileInfo) Name() string { return fi.name } 290 | func (fi *FileInfo) Size() int64 { return fi.size } 291 | func (fi *FileInfo) Mode() os.FileMode { return fi.mode } 292 | func (fi *FileInfo) ModTime() time.Time { return fi.mod_time } 293 | func (fi *FileInfo) IsDir() bool { return fi.mode.IsDir() } 294 | func (fi *FileInfo) Sys() interface{} { return nil } 295 | 296 | func (f *File) Write(p []byte) (int, error) { 297 | f.fs.mu.Lock() 298 | defer f.fs.mu.Unlock() 299 | 300 | if f.fs.Debug { 301 | log.Printf("File.Write %v", f.name) 302 | } 303 | _, err := f.fs.db.Exec(`update filesystem set content = substr(content, 1, $1) || $2 where name = $3`, f.off*2, hex.EncodeToString(p), f.name) 304 | if err != nil { 305 | return 0, err 306 | } 307 | f.off += int64(len(p)) 308 | return len(p), err 309 | } 310 | 311 | func (f *File) Close() error { 312 | if f.fs.Debug { 313 | log.Printf("File.Close %v", f.name) 314 | } 315 | 316 | return nil 317 | } 318 | 319 | func (f *File) Read(p []byte) (int, error) { 320 | f.fs.mu.Lock() 321 | defer f.fs.mu.Unlock() 322 | 323 | if f.fs.Debug { 324 | log.Printf("File.Read %v", f.name) 325 | } 326 | 327 | rows, err := f.fs.db.Query(`select mode, substr(content, $1, $2) from filesystem where name = $3`, 1+f.off*2, len(p)*2, f.name) 328 | if err != nil { 329 | return 0, err 330 | } 331 | defer rows.Close() 332 | 333 | if !rows.Next() { 334 | return 0, os.ErrInvalid 335 | } 336 | var content string 337 | var mode os.FileMode 338 | err = rows.Scan(&mode, &content) 339 | if err != nil { 340 | return 0, err 341 | } 342 | if mode.IsDir() { 343 | return 0, os.ErrInvalid 344 | } 345 | b, err := hex.DecodeString(content) 346 | if err != nil { 347 | return 0, err 348 | } 349 | copy(p, b) 350 | bl := len(b) 351 | f.off += int64(bl) 352 | if bl == 0 { 353 | return 0, io.EOF 354 | } 355 | return bl, nil 356 | } 357 | 358 | func (f *File) Readdir(count int) ([]os.FileInfo, error) { 359 | f.fs.mu.Lock() 360 | defer f.fs.mu.Unlock() 361 | 362 | if f.fs.Debug { 363 | log.Printf("File.Readdir %v", f.name) 364 | } 365 | 366 | if f.children == nil { 367 | rows, err := f.fs.db.Query(`select name from filesystem where name <> $1 and name like $2 escape '\'`, f.name, strings.Replace(f.name, `%`, `\%`, -1)+"%") 368 | if err != nil { 369 | return nil, err 370 | } 371 | defer rows.Close() 372 | 373 | f.children = []os.FileInfo{} 374 | for rows.Next() { 375 | var name string 376 | err = rows.Scan(&name) 377 | if err != nil { 378 | return nil, err 379 | } 380 | part := strings.TrimRight(name[len(f.name):], "/") 381 | if strings.IndexRune(part, '/') != -1 { 382 | continue 383 | } 384 | fi, err := f.fs.stat(name) 385 | if err != nil { 386 | return nil, err 387 | } 388 | f.children = append(f.children, fi) 389 | } 390 | } 391 | 392 | old := f.off 393 | if old >= int64(len(f.children)) { 394 | if count > 0 { 395 | return nil, io.EOF 396 | } 397 | return nil, nil 398 | } 399 | if count > 0 { 400 | f.off += int64(count) 401 | if f.off > int64(len(f.children)) { 402 | f.off = int64(len(f.children)) 403 | } 404 | } else { 405 | f.off = int64(len(f.children)) 406 | old = 0 407 | } 408 | return f.children[old:f.off], nil 409 | } 410 | 411 | func (f *File) Seek(offset int64, whence int) (int64, error) { 412 | f.fs.mu.Lock() 413 | defer f.fs.mu.Unlock() 414 | 415 | if f.fs.Debug { 416 | log.Printf("File.Seek %v %v %v", f.name, offset, whence) 417 | } 418 | 419 | var err error 420 | switch whence { 421 | case 0: 422 | f.off = 0 423 | case 2: 424 | if fi, err := f.fs.stat(f.name); err != nil { 425 | return 0, err 426 | } else { 427 | f.off = fi.Size() 428 | } 429 | } 430 | f.off += offset 431 | return f.off, err 432 | } 433 | 434 | func (f *File) Stat() (os.FileInfo, error) { 435 | f.fs.mu.Lock() 436 | defer f.fs.mu.Unlock() 437 | 438 | if f.fs.Debug { 439 | log.Printf("File.Stat %v", f.name) 440 | } 441 | 442 | return f.fs.stat(f.name) 443 | } 444 | --------------------------------------------------------------------------------