├── .gitignore ├── 9upspinfs_test.go ├── LICENSE ├── README.md ├── doc.go ├── file.go ├── fs.go ├── go.mod ├── go.sum ├── main.go ├── service_plan9.go └── service_posix.go /.gitignore: -------------------------------------------------------------------------------- 1 | 9upspinfs 2 | -------------------------------------------------------------------------------- /9upspinfs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Upspin Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | 6 | // +build !windows 7 | 8 | package main 9 | 10 | import ( 11 | "crypto/rand" 12 | "fmt" 13 | "io" 14 | "os" 15 | "os/user" 16 | "path/filepath" 17 | rtdebug "runtime/debug" 18 | "testing" 19 | 20 | go9p "github.com/lionkov/go9p/p" 21 | "github.com/lionkov/go9p/p/clnt" 22 | "upspin.io/bind" 23 | "upspin.io/config" 24 | "upspin.io/errors" 25 | "upspin.io/factotum" 26 | "upspin.io/test/testutil" 27 | "upspin.io/upspin" 28 | 29 | dirserver "upspin.io/dir/inprocess" 30 | keyserver "upspin.io/key/inprocess" 31 | storeserver "upspin.io/store/inprocess" 32 | ) 33 | 34 | type TestUser upspin.UserName 35 | 36 | var _ go9p.User = TestUser("") 37 | 38 | func (tu TestUser) Name() string { return string(tu) } 39 | func (tu TestUser) Id() int { return -1 } 40 | func (tu TestUser) Groups() []go9p.Group { return nil } 41 | func (tu TestUser) IsMember(g go9p.Group) bool { return false } 42 | 43 | var testConfig struct { 44 | root string 45 | cfg upspin.Config 46 | clnt *clnt.Clnt 47 | } 48 | 49 | const ( 50 | perm = 0777 51 | maxBytes int64 = 1e8 52 | serverAddr = "127.0.0.1:7777" 53 | ) 54 | 55 | func checkTransport(s upspin.Service) { 56 | if s == nil { 57 | panic(fmt.Sprintf("nil service")) 58 | } 59 | if t := s.Endpoint().Transport; t != upspin.InProcess { 60 | panic(fmt.Sprintf("bad transport %v, want inprocess", t)) 61 | } 62 | } 63 | 64 | func testSetup(userName upspin.UserName) upspin.Config { 65 | inProcess := upspin.Endpoint{ 66 | Transport: upspin.InProcess, 67 | NetAddr: "", // ignored 68 | } 69 | 70 | // Create baseCfg with user1's keys. 71 | f, err := factotum.NewFromDir(testutil.Repo("key", "testdata", "user1")) // Always use user1's keys. 72 | if err != nil { 73 | panic("cannot initialize factotum: " + err.Error()) 74 | } 75 | 76 | cfg := config.New() 77 | cfg = config.SetPacking(cfg, upspin.EEPack) 78 | cfg = config.SetKeyEndpoint(cfg, inProcess) 79 | cfg = config.SetStoreEndpoint(cfg, inProcess) 80 | cfg = config.SetDirEndpoint(cfg, inProcess) 81 | cfg = config.SetFactotum(cfg, f) 82 | 83 | bind.RegisterKeyServer(upspin.InProcess, keyserver.New()) 84 | bind.RegisterStoreServer(upspin.InProcess, storeserver.New()) 85 | bind.RegisterDirServer(upspin.InProcess, dirserver.New(cfg)) 86 | 87 | cfg = config.SetUserName(cfg, userName) 88 | key, _ := bind.KeyServer(cfg, cfg.KeyEndpoint()) 89 | checkTransport(key) 90 | dir, _ := bind.DirServer(cfg, cfg.DirEndpoint()) 91 | checkTransport(dir) 92 | if cfg.Factotum().PublicKey() == "" { 93 | panic("empty public key") 94 | } 95 | user := &upspin.User{ 96 | Name: upspin.UserName(userName), 97 | Dirs: []upspin.Endpoint{cfg.DirEndpoint()}, 98 | Stores: []upspin.Endpoint{cfg.StoreEndpoint()}, 99 | PublicKey: cfg.Factotum().PublicKey(), 100 | } 101 | if err := key.Put(user); err != nil { 102 | panic(err) 103 | } 104 | name := upspin.PathName(userName) + "/" 105 | entry := &upspin.DirEntry{ 106 | Name: name, 107 | SignedName: name, 108 | Attr: upspin.AttrDirectory, 109 | Writer: userName, 110 | } 111 | _, err = dir.Put(entry) 112 | if err != nil && !errors.Is(errors.Exist, err) { 113 | panic(err) 114 | } 115 | return cfg 116 | } 117 | 118 | func mount() error { 119 | // Set up a user config. 120 | uname := upspin.UserName("user1@google.com") 121 | cfg := testSetup(uname) 122 | testConfig.cfg = cfg 123 | 124 | // start server 125 | go do(cfg, "tcp", serverAddr, *debug) 126 | 127 | // The server may take some time to start up 128 | var client *clnt.Clnt 129 | var err error 130 | cur, err := user.Current() 131 | if err != nil { 132 | return err 133 | } 134 | user9p := TestUser(cur.Username) 135 | for i := 0; i < 16; i++ { 136 | if client, err = clnt.Mount("tcp", serverAddr, "", 8192, user9p); err == nil { 137 | break 138 | } 139 | } 140 | if err != nil { 141 | return fmt.Errorf("Connect failed after many tries: %v", err) 142 | } 143 | testConfig.clnt = client 144 | testConfig.root = string(uname) + "/" 145 | return nil 146 | } 147 | 148 | func cleanup() { 149 | testConfig.clnt.Unmount() 150 | } 151 | 152 | func TestMain(m *testing.M) { 153 | if err := mount(); err != nil { 154 | fmt.Fprintf(os.Stderr, "startServer failed: %s\n", err) 155 | os.Exit(1) 156 | } 157 | rv := m.Run() 158 | cleanup() 159 | os.Exit(rv) 160 | } 161 | 162 | func mkTestDir(t *testing.T, name string) string { 163 | testDir := filepath.Join(testConfig.root, name) 164 | if _, err := testConfig.clnt.FCreate(testDir, perm|go9p.DMDIR, 0); err != nil { 165 | fatal(t, err) 166 | } 167 | return testDir 168 | } 169 | 170 | func randomBytes(t *testing.T, len int) []byte { 171 | buf := make([]byte, len) 172 | if _, err := rand.Read(buf); err != nil { 173 | fatal(t, err) 174 | } 175 | return buf 176 | } 177 | 178 | func writeFile(t *testing.T, fn string, buf []byte) *clnt.File { 179 | f, err := testConfig.clnt.FCreate(fn, 0600, go9p.OWRITE) 180 | if err != nil { 181 | // file already exists, so try to open it 182 | f, err = testConfig.clnt.FOpen(fn, go9p.OWRITE) 183 | } 184 | if err != nil { 185 | fatal(t, err) 186 | } 187 | n, err := f.Writen(buf, 0) 188 | if err != nil { 189 | f.Close() 190 | fatal(t, err) 191 | } 192 | if n != len(buf) { 193 | f.Close() 194 | fatalf(t, "%s: wrote %d bytes, expected %d", fn, n, len(buf)) 195 | } 196 | return f 197 | } 198 | 199 | func readAndCheckContentsOrDie(t *testing.T, fn string, buf []byte) { 200 | err := readAndCheckContents(t, fn, buf) 201 | if err != nil { 202 | fatal(t, err) 203 | } 204 | } 205 | 206 | func readAndCheckContents(t *testing.T, fn string, buf []byte) error { 207 | f, err := testConfig.clnt.FOpen(fn, go9p.OREAD) 208 | if err != nil { 209 | return err 210 | } 211 | defer f.Close() 212 | rbuf := make([]byte, len(buf)+10) 213 | n, err := io.ReadFull(f, rbuf) 214 | if err != nil && err != io.ErrUnexpectedEOF { 215 | return err 216 | } 217 | if n != len(buf) { 218 | return fmt.Errorf("%s: read %d bytes, expected %d", fn, n, len(buf)) 219 | } 220 | for i := range buf { 221 | if buf[i] != rbuf[i] { 222 | return fmt.Errorf("%s: error at byte %d: %.2x should be %.2x", fn, i, rbuf[i], buf[i]) 223 | } 224 | } 225 | return nil 226 | } 227 | 228 | func mkFile(t *testing.T, fn string, buf []byte) { 229 | f := writeFile(t, fn, buf) 230 | if err := f.Close(); err != nil { 231 | fatal(t, err) 232 | } 233 | } 234 | 235 | func mkDir(t *testing.T, fn string) { 236 | if _, err := testConfig.clnt.FCreate(fn, perm|go9p.DMDIR, 0); err != nil { 237 | fatal(t, err) 238 | } 239 | } 240 | 241 | func remove(t *testing.T, fn string) { 242 | if err := testConfig.clnt.FRemove(fn); err != nil { 243 | fatal(t, err) 244 | } 245 | notExist(t, fn, "removal") 246 | } 247 | 248 | func notExist(t *testing.T, fn, event string) { 249 | if _, err := testConfig.clnt.FStat(fn); err == nil { 250 | fatalf(t, "%s: should not exist after %s", fn, event) 251 | } 252 | } 253 | 254 | // TestFile tests creating, writing, reading, and removing a file. 255 | func TestFile(t *testing.T) { 256 | testDir := mkTestDir(t, "testfile") 257 | buf := randomBytes(t, 16*1024) 258 | 259 | // Create and write a file. 260 | fn := filepath.Join(testDir, "file") 261 | wf := writeFile(t, fn, buf) 262 | 263 | // Read before close. 264 | // TODO: uncomment after caching is implemented 265 | //readAndCheckContentsOrDie(t, fn, buf) 266 | 267 | // Read after close. 268 | if err := wf.Close(); err != nil { 269 | t.Fatal(err) 270 | } 271 | readAndCheckContentsOrDie(t, fn, buf) 272 | 273 | // Test Rewriting part of the file. 274 | for i := 0; i < len(buf)/2; i++ { 275 | buf[i] = buf[i] ^ 0xff 276 | } 277 | wf = writeFile(t, fn, buf[:len(buf)/2]) 278 | if err := wf.Close(); err != nil { 279 | t.Fatal(err) 280 | } 281 | readAndCheckContentsOrDie(t, fn, buf) 282 | remove(t, fn) 283 | remove(t, testDir) 284 | } 285 | 286 | func TestWalkAfterCreate(t *testing.T) { 287 | testDir := mkTestDir(t, "testfile") 288 | fn := filepath.Join(testDir, "file") 289 | f, err := testConfig.clnt.FCreate(fn, 0600, go9p.OWRITE) 290 | if err != nil { 291 | fatalf(t, "Create failed: %v", err) 292 | } 293 | if _, err := testConfig.clnt.FWalk(fn); err != nil { 294 | fatalf(t, "FWalk failed after create: %v", err) 295 | } 296 | if err := f.Close(); err != nil { 297 | fatalf(t, "Close failed: %v", err) 298 | } 299 | } 300 | 301 | func rename(src, dst string) error { 302 | d := go9p.NewWstatDir() 303 | fid, err := testConfig.clnt.FWalk(src) 304 | if err != nil { 305 | return err 306 | } 307 | d.Name = dst 308 | return testConfig.clnt.Wstat(fid, d) 309 | } 310 | 311 | // TestRename tests renaming a file. 312 | func TestRename(t *testing.T) { 313 | testDir := mkTestDir(t, "testrename") 314 | 315 | // Check that file is renamed and old name is no longer valid. 316 | original := filepath.Join(testDir, "original") 317 | newname := filepath.Join(testDir, "newname") 318 | mkFile(t, original, []byte(original)) 319 | if err := rename(original, newname); err != nil { 320 | t.Fatal(err) 321 | } 322 | readAndCheckContentsOrDie(t, newname, []byte(original)) 323 | notExist(t, original, "rename") 324 | remove(t, newname) 325 | 326 | remove(t, testDir) 327 | } 328 | 329 | // TestMultiWrites tests concurrent writes. 330 | func TestMultiWrites(t *testing.T) { 331 | testDir := mkTestDir(t, "testwrite") 332 | 333 | fn := filepath.Join(testDir, "file") 334 | f1, err := testConfig.clnt.FCreate(fn, 0600, go9p.OWRITE) 335 | if err != nil { 336 | fatalf(t, "Create failed: %v", err) 337 | } 338 | f2, err := testConfig.clnt.FOpen(fn, go9p.OWRITE) 339 | if err != nil { 340 | f1.Close() 341 | fatalf(t, "Open failed: %v", err) 342 | } 343 | buf := []byte("hello world\n") 344 | off := len(buf) / 2 345 | n, err := f1.Writen(buf[off:], uint64(off)) 346 | if err != nil { 347 | f1.Close() 348 | f2.Close() 349 | fatal(t, err) 350 | } 351 | if n != len(buf[off:]) { 352 | f1.Close() 353 | f2.Close() 354 | fatalf(t, "%s: wrote %d bytes, expected %d", fn, n, len(buf[off:])) 355 | } 356 | n, err = f2.Writen(buf[:off], 0) 357 | if err != nil { 358 | f1.Close() 359 | f2.Close() 360 | fatal(t, err) 361 | } 362 | if n != len(buf[:off]) { 363 | f1.Close() 364 | f2.Close() 365 | fatalf(t, "%s: wrote %d bytes, expected %d", fn, n, len(buf[:off])) 366 | } 367 | if err := f2.Close(); err != nil { 368 | f1.Close() 369 | fatalf(t, "Closing f2 failed: %v\n", err) 370 | } 371 | if err := f1.Close(); err != nil { 372 | fatalf(t, "Closing f1 failed: %v\n", err) 373 | } 374 | readAndCheckContentsOrDie(t, fn, buf) 375 | remove(t, fn) 376 | remove(t, testDir) 377 | } 378 | 379 | func fatal(t *testing.T, args ...interface{}) { 380 | t.Log(fmt.Sprintln(args...)) 381 | t.Log(string(rtdebug.Stack())) 382 | t.FailNow() 383 | } 384 | 385 | func fatalf(t *testing.T, format string, args ...interface{}) { 386 | t.Log(fmt.Sprintf(format, args...)) 387 | t.Log(string(rtdebug.Stack())) 388 | t.FailNow() 389 | } 390 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 The Upspin Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/fhs/9upspinfs?status.svg)](https://godoc.org/github.com/fhs/9upspinfs) 2 | 3 | ## 9upspinfs 4 | 5 | 9upspinfs is a 9P file server for [upspin](https://upspin.io/). 6 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Upspin Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Command 9upspinfs is a 9P file server for Upspin. 7 | 8 | If the config or flags specify a cache server endpoint and cacheserver 9 | is not running, upspinfs will attempt to start one. All the flags listed 10 | below are also passed to the cacheserver should one be started. 11 | 12 | Usage: 13 | 14 | 9upspinfs [flags] 15 | 16 | By default, 9upspinfs starts the 9P file server as the Plan 9 17 | service named "upspin". 18 | 19 | The flags are: 20 | 21 | -9paddr string 22 | network listen address (default "upspin") 23 | -9pnet string 24 | network name for listen address (default "service") 25 | -addr host:port 26 | publicly accessible network address (host:port) 27 | -cachedir directory 28 | directory containing all file caches (default "$HOME/upspin") 29 | -cachesize int 30 | maximum bytes for file caches (default 5000000000) 31 | -config file 32 | user's configuration file (default "$HOME/upspin/config") 33 | -debug int 34 | 9P debug level 35 | -http address 36 | address for incoming insecure network connections (default ":80") 37 | -https address 38 | address for incoming secure network connections (default ":443") 39 | -insecure 40 | whether to serve insecure HTTP instead of HTTPS 41 | -letscache directory 42 | Let's Encrypt cache directory (default "$HOME/upspin/letsencrypt") 43 | -log level 44 | level of logging: debug, info, error, disabled (default info) 45 | -prudent 46 | protect against malicious directory server 47 | -tls_cert file 48 | TLS Certificate file in PEM format 49 | -tls_key file 50 | TLS Key file in PEM format 51 | -version 52 | print build version and exit 53 | -writethrough 54 | make storage cache writethrough 55 | 56 | Examples: 57 | 58 | To listen on TCP: 59 | 60 | 9upspinfs -9pnet tcp -9paddr localhost:7777 61 | 62 | If you have Plan9Port (https://9fans.github.io/plan9port/): 63 | 64 | 9upspinfs & # posts service to p9p namespace directory 65 | # mount using v9fs 66 | mount -t 9p $(namespace)/acme /mnt/upspin -o trans=unix,uname=$USER 67 | # or mount using fuse 68 | 9pfuse $(namespace)/upspin /mnt/upspin 69 | 70 | But you're better off using upspinfs 71 | (https://godoc.org/upspin.io/cmd/upspinfs) instead. 72 | 73 | On Plan 9: 74 | 75 | 9upspinfs & 76 | mount /srv/upspin /n/upspin 77 | */ 78 | package main 79 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Upspin Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // The code in this file is derived from upspin.io/client/file package 6 | // to workaround an issue with WriteAt. 7 | // See https://github.com/upspin/upspin/issues/573 8 | 9 | package main 10 | 11 | import ( 12 | "upspin.io/errors" 13 | "upspin.io/upspin" 14 | ) 15 | 16 | // maxInt is the int64 representation of the maximum value of an int. 17 | // It allows us to verify that an int64 value never exceeds the length of a slice. 18 | // In the tests, we cut it down to manageable size for overflow checking. 19 | var maxInt = int64(^uint(0) >> 1) 20 | 21 | // File is a simple implementation of upspin.File. 22 | // It always keeps the whole file in memory under the assumption 23 | // that it is encrypted and must be read and written atomically. 24 | type File struct { 25 | name upspin.PathName // Full path name. 26 | offset int64 // File location for next read or write operation. Constrained to <= maxInt. 27 | writable bool // File is writable (made with Create, not Open). 28 | closed bool // Whether the file has been closed, preventing further operations. 29 | 30 | // Used only by readers. 31 | config upspin.Config 32 | entry *upspin.DirEntry 33 | size int64 34 | bu upspin.BlockUnpacker 35 | // Keep the most recently unpacked block around 36 | // in case a subsequent readAt starts at the same place. 37 | lastBlockIndex int 38 | lastBlockBytes []byte 39 | 40 | // Used only by writers. 41 | client upspin.Client // Client the File belongs to. 42 | data []byte // Contents of file. 43 | } 44 | 45 | var _ upspin.File = (*File)(nil) 46 | 47 | // Writable creates a new file with a given name, belonging to a given 48 | // client for write. Once closed, the file will overwrite any existing 49 | // file with the same name. 50 | func Writable(client upspin.Client, name upspin.PathName, truncate bool) (*File, error) { 51 | var data []byte 52 | if !truncate { 53 | var err error 54 | data, err = client.Get(name) 55 | if err != nil { 56 | return nil, err 57 | } 58 | } 59 | return &File{ 60 | client: client, 61 | name: name, 62 | writable: true, 63 | data: data, 64 | }, nil 65 | } 66 | 67 | // Name implements upspin.File. 68 | func (f *File) Name() upspin.PathName { 69 | return f.name 70 | } 71 | 72 | // Read implements upspin.File. 73 | func (f *File) Read(b []byte) (n int, err error) { 74 | panic("not implemented") 75 | } 76 | 77 | // ReadAt implements upspin.File. 78 | func (f *File) ReadAt(b []byte, off int64) (n int, err error) { 79 | panic("not implemented") 80 | } 81 | 82 | // Seek implements upspin.File. 83 | func (f *File) Seek(offset int64, whence int) (ret int64, err error) { 84 | panic("not implemented") 85 | } 86 | 87 | // Write implements upspin.File. 88 | func (f *File) Write(b []byte) (n int, err error) { 89 | panic("not implemented") 90 | } 91 | 92 | // WriteAt implements upspin.File. 93 | func (f *File) WriteAt(b []byte, off int64) (n int, err error) { 94 | const op errors.Op = "file.WriteAt" 95 | return f.writeAt(op, b, off) 96 | } 97 | 98 | func (f *File) writeAt(op errors.Op, b []byte, off int64) (n int, err error) { 99 | if f.closed { 100 | return 0, f.errClosed(op) 101 | } 102 | if !f.writable { 103 | return 0, errors.E(op, errors.Invalid, f.name, "not open for write") 104 | } 105 | if off < 0 { 106 | return 0, errors.E(op, errors.Invalid, f.name, "negative offset") 107 | } 108 | end := off + int64(len(b)) 109 | if end > maxInt { 110 | return 0, errors.E(op, errors.Invalid, f.name, "file too long") 111 | } 112 | if end > int64(cap(f.data)) { 113 | // Grow the capacity of f.data but keep length the same. 114 | // Be careful not to ask for more than an int's worth of length. 115 | nLen := end * 3 / 2 116 | if nLen > maxInt { 117 | nLen = maxInt 118 | } 119 | ndata := make([]byte, len(f.data), nLen) 120 | copy(ndata, f.data) 121 | f.data = ndata 122 | } 123 | // Capacity is OK now. Fix the length if necessary. 124 | if end > int64(len(f.data)) { 125 | f.data = f.data[:end] 126 | } 127 | copy(f.data[off:], b) 128 | return len(b), nil 129 | } 130 | 131 | // Close implements upspin.File. 132 | func (f *File) Close() error { 133 | const op errors.Op = "file.Close" 134 | if f.closed { 135 | return f.errClosed(op) 136 | } 137 | f.closed = true 138 | if !f.writable { 139 | f.lastBlockIndex = -1 140 | f.lastBlockBytes = nil 141 | if err := f.bu.Close(); err != nil { 142 | return errors.E(op, err) 143 | } 144 | return nil 145 | } 146 | _, err := f.client.Put(f.name, f.data) 147 | f.data = nil // Might as well release it early. 148 | return err 149 | } 150 | 151 | func (f *File) errClosed(op errors.Op) error { 152 | return errors.E(op, errors.Invalid, f.name, "is closed") 153 | } 154 | -------------------------------------------------------------------------------- /fs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Upspin Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "crypto/sha1" 9 | "io" 10 | "os" 11 | "path" 12 | "runtime" 13 | "sort" 14 | "strings" 15 | "sync" 16 | 17 | "upspin.io/client" 18 | "upspin.io/log" 19 | "upspin.io/upspin" 20 | 21 | plan9 "9fans.net/go/plan9/client" 22 | go9p "github.com/lionkov/go9p/p" 23 | "github.com/lionkov/go9p/p/srv" 24 | ) 25 | 26 | type upspinFS struct { 27 | srv.Srv 28 | client upspin.Client 29 | userDirs map[upspin.UserName]bool 30 | fileCache *fileCache 31 | } 32 | 33 | var _ srv.FidOps = (*upspinFS)(nil) 34 | var _ srv.ReqOps = (*upspinFS)(nil) 35 | 36 | func newUpspinFS(cfg upspin.Config, debug int) *upspinFS { 37 | return &upspinFS{ 38 | Srv: srv.Srv{Debuglevel: debug}, 39 | client: client.New(cfg), 40 | userDirs: map[upspin.UserName]bool{cfg.UserName(): true}, 41 | fileCache: &fileCache{ 42 | m: make(map[upspin.PathName]*File), 43 | }, 44 | } 45 | } 46 | 47 | func (f *upspinFS) Attach(req *srv.Req) { 48 | if req.Afid != nil { 49 | req.RespondError(srv.Enoauth) 50 | return 51 | } 52 | req.Fid.Aux = new(Fid) 53 | req.RespondRattach(&rootQid) 54 | } 55 | 56 | func (f *upspinFS) Walk(req *srv.Req) { 57 | fid := req.Fid.Aux.(*Fid) 58 | tc := req.Tc 59 | 60 | if req.Newfid.Aux == nil { 61 | req.Newfid.Aux = new(Fid) 62 | } 63 | nfid := req.Newfid.Aux.(*Fid) 64 | *nfid = *fid 65 | 66 | wqids := make([]go9p.Qid, len(tc.Wname)) 67 | path := string(fid.path) 68 | entry := fid.entry 69 | i := 0 70 | for ; i < len(tc.Wname); i++ { 71 | var p string 72 | if path == "" { 73 | p = tc.Wname[i] 74 | } else { 75 | p = path + "/" + tc.Wname[i] 76 | } 77 | ent, err := f.client.Lookup(upspin.PathName(p), false) 78 | if err != nil { 79 | if i == 0 { 80 | req.RespondError(srv.Enoent) 81 | return 82 | } 83 | break 84 | } 85 | if path == "" { 86 | f.userDirs[upspin.UserName(tc.Wname[i])] = true 87 | } 88 | wqids[i] = *dir2Qid(ent) 89 | path = p 90 | entry = ent 91 | } 92 | nfid.path = upspin.PathName(path) 93 | nfid.entry = entry 94 | req.RespondRwalk(wqids[0:i]) 95 | } 96 | 97 | func (f *upspinFS) Open(req *srv.Req) { 98 | fid := req.Fid.Aux.(*Fid) 99 | tc := req.Tc 100 | 101 | if fid.path == "" { 102 | count := 0 103 | for user := range f.userDirs { 104 | entry, err := f.client.Lookup(upspin.PathName(user), false) 105 | if err != nil { 106 | req.RespondError(err) 107 | } 108 | st := dir2Dir(string(user), entry) 109 | b := go9p.PackDir(st, req.Conn.Dotu) 110 | fid.dirents = append(fid.dirents, b...) 111 | count += len(b) 112 | fid.direntends = append(fid.direntends, count) 113 | } 114 | req.RespondRopen(&rootQid, 0) 115 | return 116 | } 117 | if fid.entry.IsDir() { 118 | dirContents, err := f.client.Glob(string(fid.path) + "/*") 119 | if err != nil { 120 | req.RespondError(err) 121 | } 122 | count := 0 123 | for _, entry := range dirContents { 124 | st := dir2Dir(string(entry.Name), entry) 125 | b := go9p.PackDir(st, req.Conn.Dotu) 126 | fid.dirents = append(fid.dirents, b...) 127 | count += len(b) 128 | fid.direntends = append(fid.direntends, count) 129 | } 130 | } else { 131 | var err error 132 | switch tc.Mode & 3 { 133 | case go9p.OWRITE, go9p.ORDWR: 134 | fid.file, err = f.fileCache.Writable(f.client, fid.path, tc.Mode&go9p.OTRUNC != 0) 135 | default: 136 | fid.file, err = f.client.Open(fid.path) 137 | } 138 | if err != nil { 139 | req.RespondError(err) 140 | return 141 | } 142 | } 143 | req.RespondRopen(dir2Qid(fid.entry), 0) 144 | } 145 | 146 | func (f *upspinFS) Create(req *srv.Req) { 147 | fid := req.Fid.Aux.(*Fid) 148 | tc := req.Tc 149 | 150 | path := upspin.PathName(string(fid.path) + "/" + tc.Name) 151 | if _, err := f.client.Lookup(path, false); err == nil { 152 | req.RespondError(srv.Eexist) 153 | return 154 | } 155 | const badPerms = go9p.DMSYMLINK | go9p.DMLINK | go9p.DMNAMEDPIPE | go9p.DMDEVICE 156 | var err error 157 | var entry *upspin.DirEntry 158 | var file upspin.File 159 | switch { 160 | case tc.Perm&go9p.DMDIR != 0: 161 | entry, err = f.client.MakeDirectory(path) 162 | case tc.Perm&badPerms != 0: 163 | req.RespondError(&go9p.Error{"not implemented", go9p.EIO}) 164 | return 165 | default: 166 | // Write an empty file in case Walk happened before file is closed. 167 | entry, err = f.client.Put(path, []byte{}) 168 | if err == nil { 169 | file, err = f.fileCache.Writable(f.client, path, true) 170 | } 171 | } 172 | if err != nil { 173 | req.RespondError(err) 174 | return 175 | } 176 | fid.path = path 177 | fid.entry = entry 178 | fid.file = file 179 | req.RespondRcreate(dir2Qid(fid.entry), 0) 180 | } 181 | 182 | func (f *upspinFS) Read(req *srv.Req) { 183 | fid := req.Fid.Aux.(*Fid) 184 | tc := req.Tc 185 | rc := req.Rc 186 | 187 | go9p.InitRread(rc, tc.Count) 188 | var count int 189 | if fid.path == "" || fid.entry.IsDir() { 190 | if tc.Count == 0 || len(fid.direntends) == 0 { 191 | goto done 192 | } 193 | i := 0 194 | if tc.Offset != 0 { 195 | i = sort.SearchInts(fid.direntends, int(tc.Offset)) 196 | if i >= len(fid.direntends) || fid.direntends[i] != int(tc.Offset) { 197 | req.RespondError(&go9p.Error{"invalid offset", go9p.EINVAL}) 198 | } 199 | } 200 | if int(tc.Offset) == fid.direntends[len(fid.direntends)-1] { 201 | goto done 202 | } 203 | count = int(tc.Count) 204 | j := sort.SearchInts(fid.direntends, int(tc.Offset)+count) 205 | if j >= len(fid.direntends) || fid.direntends[j] != int(tc.Offset)+count { 206 | if j == 0 { 207 | count = 0 208 | } else { 209 | count = fid.direntends[j-1] - int(tc.Offset) 210 | } 211 | } 212 | if count <= 0 { 213 | req.RespondError(&go9p.Error{"too small read size for dir entry", go9p.EINVAL}) 214 | return 215 | } 216 | copy(rc.Data, fid.dirents[tc.Offset:int(tc.Offset)+count]) 217 | } else { 218 | var err error 219 | count, err = fid.file.ReadAt(rc.Data, int64(tc.Offset)) 220 | if err != nil && err != io.EOF { 221 | req.RespondError(err) 222 | return 223 | } 224 | } 225 | done: 226 | go9p.SetRreadCount(rc, uint32(count)) 227 | req.Respond() 228 | } 229 | 230 | func (f *upspinFS) Write(req *srv.Req) { 231 | fid := req.Fid.Aux.(*Fid) 232 | tc := req.Tc 233 | 234 | n, err := fid.file.WriteAt(tc.Data, int64(tc.Offset)) 235 | if err != nil { 236 | req.RespondError(err) 237 | return 238 | } 239 | req.RespondRwrite(uint32(n)) 240 | } 241 | 242 | func (f *upspinFS) Clunk(req *srv.Req) { 243 | req.RespondRclunk() 244 | } 245 | 246 | func (f *upspinFS) Remove(req *srv.Req) { 247 | fid := req.Fid.Aux.(*Fid) 248 | if err := f.client.Delete(fid.path); err != nil { 249 | req.RespondError(err) 250 | return 251 | } 252 | req.RespondRremove() 253 | } 254 | 255 | func (f *upspinFS) Stat(req *srv.Req) { 256 | fid := req.Fid.Aux.(*Fid) 257 | req.RespondRstat(dir2Dir(string(fid.path), fid.entry)) 258 | } 259 | 260 | func (f *upspinFS) Wstat(req *srv.Req) { 261 | fid := req.Fid.Aux.(*Fid) 262 | dir := &req.Tc.Dir 263 | 264 | os.Stdout.Sync() 265 | if dir.Name != "" { 266 | fiddir, _ := path.Split(string(fid.path)) 267 | destpath := upspin.PathName(dir.Name) 268 | if destdir, _ := path.Split(string(dir.Name)); destdir == "" { 269 | // filename is relative to source directory 270 | destpath = upspin.PathName(path.Join(fiddir, dir.Name)) 271 | } 272 | if _, err := f.client.Lookup(destpath, false); err == nil { 273 | req.RespondError(srv.Eexist) 274 | return 275 | } 276 | entry, err := f.client.Rename(fid.path, destpath) 277 | if err != nil { 278 | req.RespondError(err) 279 | return 280 | } 281 | fid.path = destpath 282 | fid.entry = entry 283 | req.RespondRwstat() 284 | return 285 | } 286 | req.RespondError(srv.Enotimpl) 287 | } 288 | 289 | func (f *upspinFS) FidDestroy(sfid *srv.Fid) { 290 | if sfid.Aux == nil { 291 | return 292 | } 293 | fid := sfid.Aux.(*Fid) 294 | if fid.file != nil { 295 | f.fileCache.Close(fid.file) 296 | } 297 | // TODO: delete file if ORCLOSE create mode? 298 | } 299 | 300 | type Fid struct { 301 | path upspin.PathName 302 | entry *upspin.DirEntry 303 | 304 | // Initialized in Open or Create 305 | file upspin.File 306 | dirents []byte 307 | direntends []int 308 | } 309 | 310 | func dir2Dir(path string, d *upspin.DirEntry) *go9p.Dir { 311 | dir := new(go9p.Dir) 312 | dir.Uid = "augie" 313 | dir.Gid = "augie" 314 | dir.Mode = 0700 315 | 316 | if path == "" { 317 | dir.Qid = rootQid 318 | dir.Mode |= go9p.DMDIR 319 | dir.Name = "/" 320 | return dir 321 | } 322 | dir.Qid = *dir2Qid(d) 323 | if d.IsDir() { 324 | dir.Mode |= go9p.DMDIR 325 | } 326 | dir.Uid = string(d.Writer) 327 | dir.Atime = uint32(d.Time) 328 | dir.Mtime = uint32(d.Time) 329 | sz, _ := d.Size() 330 | dir.Length = uint64(sz) 331 | dir.Name = path[strings.LastIndex(path, "/")+1:] 332 | return dir 333 | } 334 | 335 | func getuint64(v []byte) uint64 { 336 | n := uint64(0) 337 | for _, b := range v[:8] { 338 | n = (n << 8) | uint64(b) 339 | } 340 | return n 341 | } 342 | 343 | // Qidpath returns the QID path for a path name. 344 | // Some (old) 9fans discussion on "Qid path generation" using hash functions: 345 | // https://marc.info/?l=9fans&m=111558880320502&w=2 346 | func qidpath(name upspin.PathName) uint64 { 347 | b := sha1.Sum([]byte(name)) 348 | return getuint64(b[:8]) 349 | } 350 | 351 | func dir2Qid(d *upspin.DirEntry) *go9p.Qid { 352 | typ := uint8(0) 353 | if d.IsDir() { 354 | typ |= go9p.QTDIR 355 | } 356 | return &go9p.Qid{ 357 | Path: qidpath(d.Name), 358 | Version: uint32(d.Sequence), 359 | Type: typ, 360 | } 361 | } 362 | 363 | var rootQid = go9p.Qid{ 364 | Path: qidpath(upspin.PathName("/")), 365 | Version: 0, 366 | Type: go9p.QTDIR, 367 | } 368 | 369 | func do(cfg upspin.Config, net, addr string, debug int) { 370 | srv := newUpspinFS(cfg, debug) 371 | if !srv.Start(srv) { 372 | log.Debug.Fatal("Srv start failed") 373 | } 374 | if net == "service" { 375 | switch runtime.GOOS { 376 | case "plan9": 377 | conn, err := NewServiceConn(addr) 378 | if err != nil { 379 | log.Debug.Fatalf("DialService failed: %v", err) 380 | } 381 | srv.NewConn(conn) 382 | // Wait for Go runtime to detect deadlock. 383 | // Go9p does not provide a way detect when the goroutines 384 | // started by srv.NewConn have terminated. 385 | select {} 386 | default: 387 | net = "unix" 388 | addr = plan9.Namespace() + "/" + addr 389 | } 390 | } 391 | if err := srv.StartNetListener(net, addr); err != nil { 392 | log.Debug.Fatal(err) 393 | } 394 | } 395 | 396 | // FileCache stores a mapping of path name to the open file used for writing. 397 | // This is used to implement concurrent writes. 398 | type fileCache struct { 399 | m map[upspin.PathName]*File 400 | sync.Mutex 401 | } 402 | 403 | func (fc *fileCache) Writable(client upspin.Client, name upspin.PathName, truncate bool) (*File, error) { 404 | fc.Lock() 405 | defer fc.Unlock() 406 | file, ok := fc.m[name] 407 | if ok { 408 | return file, nil 409 | } 410 | file, err := Writable(client, name, truncate) 411 | if err != nil { 412 | return nil, err 413 | } 414 | fc.m[name] = file 415 | return file, nil 416 | } 417 | 418 | func (fc *fileCache) Close(file upspin.File) error { 419 | fc.Lock() 420 | defer fc.Unlock() 421 | 422 | name := file.Name() 423 | ff, ok := fc.m[name] 424 | if !ok || ff != file { 425 | // Some possibilities: 426 | // (1) The file was not opened for writing. 427 | // (2) The file is already closed by a Tcluck of some other fid 428 | // that pointed to the same file. 429 | return file.Close() 430 | } 431 | err := file.Close() 432 | delete(fc.m, name) 433 | return err 434 | } 435 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fhs/9upspinfs 2 | 3 | go 1.11 4 | 5 | require ( 6 | 9fans.net/go v0.0.0-20180426154737-50ea98054622 7 | github.com/lionkov/go9p v0.0.0-20160331031206-9fba78810500 8 | upspin.io v0.0.0-20230206012608-67e250ec27d8 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | 9fans.net/go v0.0.0-20180426154737-50ea98054622 h1:qd4awIHXQOfnhAYFOpAj3jfvonTbgjXWuMBqtdACl58= 2 | 9fans.net/go v0.0.0-20180426154737-50ea98054622/go.mod h1:diCsxrliIURU9xsYtjCp5AbpQKqdhKmf0ujWDUSkfoY= 3 | github.com/NYTimes/gziphandler v0.0.0-20170916004738-97ae7fbaf816/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/golang/protobuf v0.0.0-20171021043952-1643683e1b54 h1:nRNJXiJvemchkOTn0V4U11TZkvacB94gTzbTZbSA7Rw= 6 | github.com/golang/protobuf v0.0.0-20171021043952-1643683e1b54/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 7 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 8 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 9 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 10 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 11 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 12 | github.com/lionkov/go9p v0.0.0-20160331031206-9fba78810500 h1:IcEJQI21XUKxA5orQSK3kVRSQPxPx7QVWBsPnuE35aE= 13 | github.com/lionkov/go9p v0.0.0-20160331031206-9fba78810500/go.mod h1:Nbrxd4FljESFB/bUvzT5xm1VNO2VfzMKSIv4L++4J4U= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/presotto/fuse v0.0.0-20220404205012-944bbcc73d97/go.mod h1:vjhV4Wnt7kY0vn360hioikNp2LXu53SYY2Bsp7REtAs= 16 | github.com/russross/blackfriday v0.0.0-20171011182219-6d1ef893fcb0/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 17 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 18 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 19 | github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= 20 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 21 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 22 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= 23 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 24 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 25 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 26 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 27 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= 28 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 29 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= 31 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 32 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 33 | golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 34 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 35 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 39 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 40 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 41 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 42 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 43 | golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 44 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 45 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 46 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 47 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 48 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 49 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 50 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 51 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 54 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 55 | upspin.io v0.0.0-20230206012608-67e250ec27d8 h1:x55+24g67CayyswxubZJ3j96jksYfT4AOddh/7R5YdE= 56 | upspin.io v0.0.0-20230206012608-67e250ec27d8/go.mod h1:V2rWKQE4OdPyt8815mBOhh0SpyI97O+biY5CgNOoIug= 57 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Upspin Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | 12 | "upspin.io/cmd/cacheserver/cacheutil" 13 | "upspin.io/config" 14 | "upspin.io/flags" 15 | "upspin.io/log" 16 | "upspin.io/transports" 17 | "upspin.io/version" 18 | ) 19 | 20 | const cmdName = "9upspinfs" 21 | 22 | var _9pnet = flag.String("9pnet", "service", "network name for listen address") 23 | var _9paddr = flag.String("9paddr", "upspin", "network listen address") 24 | var debug = flag.Int("debug", 0, "9P debug level") 25 | 26 | func usage() { 27 | fmt.Fprintf(os.Stderr, "Usage: %s\n", os.Args[0]) 28 | flag.PrintDefaults() 29 | } 30 | 31 | func main() { 32 | flag.Usage = usage 33 | flags.Parse(flags.Server, "cachedir", "cachesize", "prudent", "version") 34 | 35 | if flags.Version { 36 | fmt.Print(version.Version()) 37 | return 38 | } 39 | 40 | // Normal setup, get configuration from file and push user cache onto config. 41 | cfg, err := config.FromFile(flags.Config) 42 | if err != nil { 43 | log.Debug.Fatal(err) 44 | } 45 | 46 | // Set any flags contained in the config. 47 | if err := config.SetFlagValues(cfg, cmdName); err != nil { 48 | log.Fatalf("%s: %s", cmdName, err) 49 | } 50 | 51 | transports.Init(cfg) 52 | 53 | // Start the cacheserver if needed. 54 | if cacheutil.Start(cfg) { 55 | // Using a cacheserver, adjust cache size for upspinfs down. 56 | flags.CacheSize = flags.CacheSize / 10 57 | } 58 | 59 | if flag.NArg() != 0 { 60 | usage() 61 | os.Exit(2) 62 | } 63 | do(cfg, *_9pnet, *_9paddr, *debug) 64 | } 65 | -------------------------------------------------------------------------------- /service_plan9.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Upspin Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build plan9 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "net" 12 | "os" 13 | "syscall" 14 | 15 | go9p "github.com/lionkov/go9p/p" 16 | ) 17 | 18 | func PostFD(name string, pfd int) (*os.File, error) { 19 | p := fmt.Sprintf("/srv/%s", name) 20 | fd, err := syscall.Create(p, go9p.OWRITE|go9p.ORCLOSE|go9p.OCEXEC, 0600) 21 | if err != nil { 22 | return nil, err 23 | } 24 | f := os.NewFile(uintptr(fd), "|0") 25 | _, err = fmt.Fprintf(f, "%d", pfd) 26 | return f, err 27 | } 28 | 29 | type ServiceConn struct { 30 | *os.File 31 | name string 32 | srvf *os.File 33 | } 34 | 35 | // NewServiceConn returns a connection that has been posted 36 | // to a Plan 9 service file (in /srv). 37 | func NewServiceConn(name string) (net.Conn, error) { 38 | var fd [2]int 39 | if err := syscall.Pipe(fd[:]); err != nil { 40 | return nil, err 41 | } 42 | srvf, err := PostFD(name, fd[0]) 43 | if err != nil { 44 | syscall.Close(fd[0]) 45 | syscall.Close(fd[1]) 46 | return nil, err 47 | } 48 | syscall.Close(fd[0]) 49 | 50 | return &ServiceConn{ 51 | File: os.NewFile(uintptr(fd[1]), "|1"), 52 | name: name, 53 | srvf: srvf, 54 | }, nil 55 | } 56 | 57 | func (c *ServiceConn) LocalAddr() net.Addr { 58 | return ServiceAddr(c.name) 59 | } 60 | 61 | func (c *ServiceConn) RemoteAddr() net.Addr { 62 | return ServiceAddr(c.name) 63 | } 64 | 65 | type ServiceAddr string 66 | 67 | func (sa ServiceAddr) Network() string { 68 | return "service" 69 | } 70 | 71 | func (sa ServiceAddr) String() string { 72 | return string(sa) 73 | } 74 | -------------------------------------------------------------------------------- /service_posix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Upspin Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows 6 | 7 | package main 8 | 9 | import ( 10 | "net" 11 | ) 12 | 13 | func NewServiceConn(name string) (net.Conn, error) { 14 | panic("unimplemented") 15 | } 16 | --------------------------------------------------------------------------------