github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfs/leveldb_storage.go (about)

     1  // Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
     2  // All rights reservefs.
     3  //
     4  // Use of this source code is governed by a BSD-style license that can be
     5  // found in the LICENSE file.
     6  
     7  // This is a modified version of
     8  // github.com/syndtr/goleveldb/leveldb/storage/file_storage.go.
     9  //
    10  // Modifications: Copyright 2019 Keybase Inc. All rights reserved.
    11  // Use of this source code is governed by a BSD
    12  // license that can be found in the LICENSE file.
    13  
    14  package libfs
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"runtime"
    22  	"sort"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/syndtr/goleveldb/leveldb/storage"
    29  	billy "gopkg.in/src-d/go-billy.v4"
    30  )
    31  
    32  var (
    33  	errReadOnly = errors.New("leveldb/storage: storage is read-only")
    34  )
    35  
    36  type levelDBStorageLock struct {
    37  	fs *levelDBStorage
    38  }
    39  
    40  func (lock *levelDBStorageLock) Unlock() {
    41  	if lock.fs != nil {
    42  		lock.fs.mu.Lock()
    43  		defer lock.fs.mu.Unlock()
    44  		if lock.fs.slock == lock {
    45  			lock.fs.slock = nil
    46  		}
    47  	}
    48  }
    49  
    50  type int64Slice []int64
    51  
    52  func (p int64Slice) Len() int           { return len(p) }
    53  func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] }
    54  func (p int64Slice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
    55  
    56  const logSizeThreshold = 1024 * 1024 // 1 MiB
    57  
    58  // levelDBStorage is a billy-filesystem-backed storage.
    59  type levelDBStorage struct {
    60  	fs       billy.Filesystem
    61  	readOnly bool
    62  
    63  	mu      sync.Mutex
    64  	flock   billy.File
    65  	slock   *levelDBStorageLock
    66  	logw    billy.File
    67  	logSize int64
    68  	buf     []byte
    69  	// Opened file counter; if open < 0 means closed.
    70  	open int
    71  	day  int
    72  
    73  	syncLock sync.RWMutex // sync takes write lock, modifiers take read lock
    74  }
    75  
    76  var _ storage.Storage = (*levelDBStorage)(nil)
    77  
    78  // OpenLevelDBStorage returns a new billy-filesystem-backed storage
    79  // implementation of the levelDB storage interface. This also acquires
    80  // a file lock, so any subsequent attempt to open the same path will
    81  // fail.
    82  func OpenLevelDBStorage(bfs billy.Filesystem, readOnly bool) (
    83  	s storage.Storage, err error) {
    84  	flock, err := bfs.OpenFile("LOCK", os.O_CREATE|os.O_TRUNC, 0600)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	defer func() {
    89  		if err != nil {
    90  			flock.Close()
    91  		}
    92  	}()
    93  	err = flock.Lock()
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	var (
    99  		logw    billy.File
   100  		logSize int64
   101  	)
   102  	if !readOnly {
   103  		logw, err = bfs.OpenFile("LOG", os.O_WRONLY|os.O_CREATE, 0644)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  		logSize, err = logw.Seek(0, os.SEEK_END)
   108  		if err != nil {
   109  			logw.Close()
   110  			return nil, err
   111  		}
   112  	}
   113  
   114  	fs := &levelDBStorage{
   115  		fs:       bfs,
   116  		readOnly: readOnly,
   117  		flock:    flock,
   118  		logw:     logw,
   119  		logSize:  logSize,
   120  	}
   121  	runtime.SetFinalizer(fs, (*levelDBStorage).Close)
   122  	return fs, nil
   123  }
   124  
   125  func (fs *levelDBStorage) writeFileSyncedRLocked(
   126  	filename string, data []byte, perm os.FileMode) error {
   127  	f, err := fs.fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
   128  	if err != nil {
   129  		return err
   130  	}
   131  	n, err := f.Write(data)
   132  	if err == nil && n < len(data) {
   133  		err = io.ErrShortWrite
   134  	}
   135  	if err1 := f.Close(); err == nil {
   136  		err = err1
   137  	}
   138  	if err != nil {
   139  		return err
   140  	}
   141  	return fs.syncRLocked()
   142  }
   143  
   144  func (fs *levelDBStorage) Lock() (storage.Locker, error) {
   145  	fs.mu.Lock()
   146  	defer fs.mu.Unlock()
   147  	if fs.open < 0 {
   148  		return nil, storage.ErrClosed
   149  	}
   150  	if fs.readOnly {
   151  		return &levelDBStorageLock{}, nil
   152  	}
   153  	if fs.slock != nil {
   154  		return nil, storage.ErrLocked
   155  	}
   156  	fs.slock = &levelDBStorageLock{fs: fs}
   157  	return fs.slock, nil
   158  }
   159  
   160  func itoa(buf []byte, i int, wid int) []byte {
   161  	u := uint(i)
   162  	if u == 0 && wid <= 1 {
   163  		return append(buf, '0')
   164  	}
   165  
   166  	// Assemble decimal in reverse order.
   167  	var b [32]byte
   168  	bp := len(b)
   169  	for ; u > 0 || wid > 0; u /= 10 {
   170  		bp--
   171  		wid--
   172  		b[bp] = byte(u%10) + '0'
   173  	}
   174  	return append(buf, b[bp:]...)
   175  }
   176  
   177  func (fs *levelDBStorage) printDay(t time.Time) {
   178  	if fs.day == t.Day() {
   179  		return
   180  	}
   181  	fs.day = t.Day()
   182  	_, _ = fs.logw.Write([]byte("=============== " + t.Format("Jan 2, 2006 (MST)") + " ===============\n"))
   183  }
   184  
   185  func (fs *levelDBStorage) doLogRLocked(t time.Time, str string) {
   186  	if fs.logSize > logSizeThreshold {
   187  		// Rotate log file.
   188  		fs.logw.Close()
   189  		fs.logw = nil
   190  		fs.logSize = 0
   191  		err := fs.fs.Rename("LOG", "LOG.old")
   192  		if err != nil {
   193  			return
   194  		}
   195  	}
   196  	if fs.logw == nil {
   197  		var err error
   198  		fs.logw, err = fs.fs.OpenFile("LOG", os.O_WRONLY|os.O_CREATE, 0644)
   199  		if err != nil {
   200  			return
   201  		}
   202  		// Force printDay on new log file.
   203  		fs.day = 0
   204  	}
   205  	fs.printDay(t)
   206  	hour, min, sec := t.Clock()
   207  	msec := t.Nanosecond() / 1e3
   208  	// time
   209  	fs.buf = itoa(fs.buf[:0], hour, 2)
   210  	fs.buf = append(fs.buf, ':')
   211  	fs.buf = itoa(fs.buf, min, 2)
   212  	fs.buf = append(fs.buf, ':')
   213  	fs.buf = itoa(fs.buf, sec, 2)
   214  	fs.buf = append(fs.buf, '.')
   215  	fs.buf = itoa(fs.buf, msec, 6)
   216  	fs.buf = append(fs.buf, ' ')
   217  	// write
   218  	fs.buf = append(fs.buf, []byte(str)...)
   219  	fs.buf = append(fs.buf, '\n')
   220  	n, _ := fs.logw.Write(fs.buf)
   221  	fs.logSize += int64(n)
   222  }
   223  
   224  func (fs *levelDBStorage) Log(str string) {
   225  	if !fs.readOnly {
   226  		t := time.Now()
   227  		fs.mu.Lock()
   228  		defer fs.mu.Unlock()
   229  		if fs.open < 0 {
   230  			return
   231  		}
   232  		fs.syncLock.RLock()
   233  		defer fs.syncLock.RUnlock()
   234  		fs.doLogRLocked(t, str)
   235  	}
   236  }
   237  
   238  func (fs *levelDBStorage) logRLocked(str string) {
   239  	if !fs.readOnly {
   240  		fs.doLogRLocked(time.Now(), str)
   241  	}
   242  }
   243  
   244  func (fs *levelDBStorage) log(str string) {
   245  	if !fs.readOnly {
   246  		fs.syncLock.RLock()
   247  		defer fs.syncLock.RUnlock()
   248  		fs.doLogRLocked(time.Now(), str)
   249  	}
   250  }
   251  
   252  func (fs *levelDBStorage) syncLocked() (err error) {
   253  	// Force a sync with a lock/unlock cycle, since the billy
   254  	// interface doesn't have an explicit sync call.
   255  	const syncLockName = "sync.lock"
   256  	f, err := fs.fs.OpenFile(syncLockName, os.O_CREATE|os.O_TRUNC, 0600)
   257  	if err != nil {
   258  		return err
   259  	}
   260  	defer func() {
   261  		closeErr := f.Close()
   262  		if err == nil {
   263  			err = closeErr
   264  		}
   265  	}()
   266  	return f.Lock()
   267  }
   268  
   269  func (fs *levelDBStorage) sync() (err error) {
   270  	fs.syncLock.Lock()
   271  	defer fs.syncLock.Unlock()
   272  	return fs.syncLocked()
   273  }
   274  
   275  func (fs *levelDBStorage) syncRLocked() (err error) {
   276  	fs.syncLock.RUnlock()
   277  	defer fs.syncLock.RLock()
   278  	return fs.sync()
   279  }
   280  
   281  func (fs *levelDBStorage) setMetaRLocked(fd storage.FileDesc) error {
   282  	content := fsGenName(fd) + "\n"
   283  	// Check and backup old CURRENT file.
   284  	currentPath := "CURRENT"
   285  	if _, err := fs.fs.Stat(currentPath); err == nil {
   286  		f, err := fs.fs.Open(currentPath)
   287  		if err != nil {
   288  			return err
   289  		}
   290  		defer f.Close()
   291  		b, err := io.ReadAll(f)
   292  		if err != nil {
   293  			fs.logRLocked(fmt.Sprintf("backup CURRENT: %v", err))
   294  			return err
   295  		}
   296  		if string(b) == content {
   297  			// Content not changed, do nothing.
   298  			return nil
   299  		}
   300  		if err := fs.writeFileSyncedRLocked(
   301  			currentPath+".bak", b, 0644); err != nil {
   302  			fs.logRLocked(fmt.Sprintf("backup CURRENT: %v", err))
   303  			return err
   304  		}
   305  	} else if !os.IsNotExist(err) {
   306  		return err
   307  	}
   308  	path := fmt.Sprintf("CURRENT.%d", fd.Num)
   309  	if err := fs.writeFileSyncedRLocked(
   310  		path, []byte(content), 0644); err != nil {
   311  		fs.logRLocked(fmt.Sprintf("create CURRENT.%d: %v", fd.Num, err))
   312  		return err
   313  	}
   314  	// Replace CURRENT file.
   315  	if err := fs.fs.Rename(path, currentPath); err != nil {
   316  		fs.logRLocked(fmt.Sprintf("rename CURRENT.%d: %v", fd.Num, err))
   317  		return err
   318  	}
   319  	return fs.syncRLocked()
   320  }
   321  
   322  func (fs *levelDBStorage) setMeta(fd storage.FileDesc) error {
   323  	fs.syncLock.RLock()
   324  	defer fs.syncLock.RUnlock()
   325  	return fs.setMetaRLocked(fd)
   326  }
   327  
   328  func (fs *levelDBStorage) SetMeta(fd storage.FileDesc) error {
   329  	fs.syncLock.RLock()
   330  	defer fs.syncLock.RUnlock()
   331  
   332  	if !storage.FileDescOk(fd) {
   333  		return storage.ErrInvalidFile
   334  	}
   335  	if fs.readOnly {
   336  		return errReadOnly
   337  	}
   338  
   339  	fs.mu.Lock()
   340  	defer fs.mu.Unlock()
   341  	if fs.open < 0 {
   342  		return storage.ErrClosed
   343  	}
   344  	return fs.setMetaRLocked(fd)
   345  }
   346  
   347  func isCorrupted(err error) bool {
   348  	switch err.(type) {
   349  	case *storage.ErrCorrupted:
   350  		return true
   351  	default:
   352  		return false
   353  	}
   354  }
   355  
   356  func (fs *levelDBStorage) GetMeta() (storage.FileDesc, error) {
   357  	fs.mu.Lock()
   358  	defer fs.mu.Unlock()
   359  	if fs.open < 0 {
   360  		return storage.FileDesc{}, storage.ErrClosed
   361  	}
   362  	fis, err := fs.fs.ReadDir("")
   363  	if err != nil {
   364  		return storage.FileDesc{}, err
   365  	}
   366  	// Try this in order:
   367  	// - CURRENT.[0-9]+ ('pending rename' file, descending order)
   368  	// - CURRENT
   369  	// - CURRENT.bak
   370  	//
   371  	// Skip corrupted file or file that point to a missing target file.
   372  	type currentFile struct {
   373  		name string
   374  		fd   storage.FileDesc
   375  	}
   376  	tryCurrent := func(name string) (*currentFile, error) {
   377  		f, err := fs.fs.Open(name)
   378  		if err != nil {
   379  			if os.IsNotExist(err) {
   380  				err = os.ErrNotExist
   381  			}
   382  			return nil, err
   383  		}
   384  		defer f.Close()
   385  		b, err := io.ReadAll(f)
   386  		if err != nil {
   387  			return nil, err
   388  		}
   389  		var fd storage.FileDesc
   390  		if len(b) < 1 || b[len(b)-1] != '\n' || !fsParseNamePtr(string(b[:len(b)-1]), &fd) {
   391  			fs.logRLocked(fmt.Sprintf("%s: corrupted content: %q", name, b))
   392  			err := &storage.ErrCorrupted{
   393  				Err: fmt.Errorf("leveldb/storage: corrupted or incomplete CURRENT file %s %q", name, b),
   394  			}
   395  			return nil, err
   396  		}
   397  		if _, err := fs.fs.Stat(fsGenName(fd)); err != nil {
   398  			if os.IsNotExist(err) {
   399  				fs.logRLocked(
   400  					fmt.Sprintf("%s: missing target file: %s", name, fd))
   401  				err = os.ErrNotExist
   402  			}
   403  			return nil, err
   404  		}
   405  		return &currentFile{name: name, fd: fd}, nil
   406  	}
   407  	tryCurrents := func(names []string) (*currentFile, error) {
   408  		var (
   409  			cur *currentFile
   410  			// Last corruption error.
   411  			lastCerr error
   412  		)
   413  		for _, name := range names {
   414  			var err error
   415  			cur, err = tryCurrent(name)
   416  			if err == nil {
   417  				break
   418  			} else if err == os.ErrNotExist {
   419  				// Fallback to the next file.
   420  			} else if isCorrupted(err) {
   421  				lastCerr = err
   422  				// Fallback to the next file.
   423  			} else {
   424  				// In case the error is due to permission, etc.
   425  				return nil, err
   426  			}
   427  		}
   428  		if cur == nil {
   429  			err := os.ErrNotExist
   430  			if lastCerr != nil {
   431  				err = lastCerr
   432  			}
   433  			return nil, err
   434  		}
   435  		return cur, nil
   436  	}
   437  
   438  	// Try 'pending rename' files.
   439  	var nums []int64
   440  	for _, fi := range fis {
   441  		name := fi.Name()
   442  		if strings.HasPrefix(name, "CURRENT.") && name != "CURRENT.bak" {
   443  			i, err := strconv.ParseInt(name[8:], 10, 64)
   444  			if err == nil {
   445  				nums = append(nums, i)
   446  			}
   447  		}
   448  	}
   449  	var (
   450  		pendCur   *currentFile
   451  		pendErr   = os.ErrNotExist
   452  		pendNames []string
   453  	)
   454  	if len(nums) > 0 {
   455  		sort.Sort(sort.Reverse(int64Slice(nums)))
   456  		pendNames = make([]string, len(nums))
   457  		for i, num := range nums {
   458  			pendNames[i] = fmt.Sprintf("CURRENT.%d", num)
   459  		}
   460  		pendCur, pendErr = tryCurrents(pendNames)
   461  		if pendErr != nil && pendErr != os.ErrNotExist && !isCorrupted(pendErr) {
   462  			return storage.FileDesc{}, pendErr
   463  		}
   464  	}
   465  
   466  	// Try CURRENT and CURRENT.bak.
   467  	curCur, curErr := tryCurrents([]string{"CURRENT", "CURRENT.bak"})
   468  	if curErr != nil && curErr != os.ErrNotExist && !isCorrupted(curErr) {
   469  		return storage.FileDesc{}, curErr
   470  	}
   471  
   472  	// pendCur takes precedence, but guards against obsolete pendCur.
   473  	if pendCur != nil && (curCur == nil || pendCur.fd.Num > curCur.fd.Num) {
   474  		curCur = pendCur
   475  	}
   476  
   477  	if curCur != nil {
   478  		// Restore CURRENT file to proper state.
   479  		if !fs.readOnly && (curCur.name != "CURRENT" || len(pendNames) != 0) {
   480  			// Ignore setMeta errors, however don't delete obsolete files if we
   481  			// catch error.
   482  			if err := fs.setMeta(curCur.fd); err == nil {
   483  				// Remove 'pending rename' files.
   484  				for _, name := range pendNames {
   485  					if err := fs.fs.Remove(name); err != nil {
   486  						fs.logRLocked(fmt.Sprintf("remove %s: %v", name, err))
   487  					}
   488  				}
   489  			}
   490  		}
   491  		return curCur.fd, nil
   492  	}
   493  
   494  	// Nothing found.
   495  	if isCorrupted(pendErr) {
   496  		return storage.FileDesc{}, pendErr
   497  	}
   498  	return storage.FileDesc{}, curErr
   499  }
   500  
   501  func (fs *levelDBStorage) List(ft storage.FileType) (fds []storage.FileDesc, err error) {
   502  	fs.mu.Lock()
   503  	defer fs.mu.Unlock()
   504  	if fs.open < 0 {
   505  		return nil, storage.ErrClosed
   506  	}
   507  	fis, err := fs.fs.ReadDir("")
   508  	if err != nil {
   509  		return nil, err
   510  	}
   511  	if err == nil {
   512  		for _, fi := range fis {
   513  			if fd, ok := fsParseName(fi.Name()); ok && fd.Type&ft != 0 {
   514  				fds = append(fds, fd)
   515  			}
   516  		}
   517  	}
   518  	return
   519  }
   520  
   521  func (fs *levelDBStorage) Open(fd storage.FileDesc) (storage.Reader, error) {
   522  	if !storage.FileDescOk(fd) {
   523  		return nil, storage.ErrInvalidFile
   524  	}
   525  
   526  	fs.mu.Lock()
   527  	defer fs.mu.Unlock()
   528  	if fs.open < 0 {
   529  		return nil, storage.ErrClosed
   530  	}
   531  	of, err := fs.fs.OpenFile(fsGenName(fd), os.O_RDONLY, 0)
   532  	if err != nil {
   533  		if fsHasOldName(fd) && os.IsNotExist(err) {
   534  			of, err = fs.fs.OpenFile(fsGenOldName(fd), os.O_RDONLY, 0)
   535  			if err == nil {
   536  				goto ok
   537  			}
   538  		}
   539  		return nil, err
   540  	}
   541  ok:
   542  	fs.open++
   543  	return &fileWrap{File: of, fs: fs, fd: fd}, nil
   544  }
   545  
   546  func (fs *levelDBStorage) Create(fd storage.FileDesc) (storage.Writer, error) {
   547  	fs.syncLock.RLock()
   548  	defer fs.syncLock.RUnlock()
   549  
   550  	if !storage.FileDescOk(fd) {
   551  		return nil, storage.ErrInvalidFile
   552  	}
   553  	if fs.readOnly {
   554  		return nil, errReadOnly
   555  	}
   556  
   557  	fs.mu.Lock()
   558  	defer fs.mu.Unlock()
   559  	if fs.open < 0 {
   560  		return nil, storage.ErrClosed
   561  	}
   562  	of, err := fs.fs.OpenFile(fsGenName(fd), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
   563  	if err != nil {
   564  		return nil, err
   565  	}
   566  	fs.open++
   567  	return &fileWrap{File: of, fs: fs, fd: fd}, nil
   568  }
   569  
   570  func (fs *levelDBStorage) Remove(fd storage.FileDesc) error {
   571  	fs.syncLock.RLock()
   572  	defer fs.syncLock.RUnlock()
   573  
   574  	if !storage.FileDescOk(fd) {
   575  		return storage.ErrInvalidFile
   576  	}
   577  	if fs.readOnly {
   578  		return errReadOnly
   579  	}
   580  
   581  	fs.mu.Lock()
   582  	defer fs.mu.Unlock()
   583  	if fs.open < 0 {
   584  		return storage.ErrClosed
   585  	}
   586  	err := fs.fs.Remove(fsGenName(fd))
   587  	if err != nil {
   588  		if fsHasOldName(fd) && os.IsNotExist(err) {
   589  			if e1 := fs.fs.Remove(fsGenOldName(fd)); !os.IsNotExist(e1) {
   590  				fs.logRLocked(fmt.Sprintf("remove %s: %v (old name)", fd, err))
   591  				err = e1
   592  			}
   593  		} else {
   594  			fs.logRLocked(fmt.Sprintf("remove %s: %v", fd, err))
   595  		}
   596  	}
   597  	return err
   598  }
   599  
   600  func (fs *levelDBStorage) Rename(oldfd, newfd storage.FileDesc) error {
   601  	fs.syncLock.RLock()
   602  	defer fs.syncLock.RUnlock()
   603  
   604  	if !storage.FileDescOk(oldfd) || !storage.FileDescOk(newfd) {
   605  		return storage.ErrInvalidFile
   606  	}
   607  	if oldfd == newfd {
   608  		return nil
   609  	}
   610  	if fs.readOnly {
   611  		return errReadOnly
   612  	}
   613  
   614  	fs.mu.Lock()
   615  	defer fs.mu.Unlock()
   616  	if fs.open < 0 {
   617  		return storage.ErrClosed
   618  	}
   619  	return fs.fs.Rename(fsGenName(oldfd), fsGenName(newfd))
   620  }
   621  
   622  func (fs *levelDBStorage) Close() error {
   623  	fs.mu.Lock()
   624  	defer fs.mu.Unlock()
   625  	if fs.open < 0 {
   626  		return storage.ErrClosed
   627  	}
   628  	// Clear the finalizer.
   629  	runtime.SetFinalizer(fs, nil)
   630  
   631  	if fs.open > 0 {
   632  		fs.log(fmt.Sprintf("close: warning, %d files still open", fs.open))
   633  	}
   634  	fs.open = -1
   635  	if fs.logw != nil {
   636  		fs.logw.Close()
   637  	}
   638  	return fs.flock.Close()
   639  }
   640  
   641  type fileWrap struct {
   642  	billy.File
   643  	fs     *levelDBStorage
   644  	fd     storage.FileDesc
   645  	closed bool
   646  }
   647  
   648  func (fw *fileWrap) Write(p []byte) (n int, err error) {
   649  	fw.fs.syncLock.RLock()
   650  	defer fw.fs.syncLock.RUnlock()
   651  	return fw.File.Write(p)
   652  }
   653  
   654  func (fw *fileWrap) Sync() error {
   655  	return fw.fs.sync()
   656  }
   657  
   658  func (fw *fileWrap) Close() error {
   659  	fw.fs.mu.Lock()
   660  	defer fw.fs.mu.Unlock()
   661  	if fw.closed {
   662  		return storage.ErrClosed
   663  	}
   664  	fw.closed = true
   665  	fw.fs.open--
   666  	err := fw.File.Close()
   667  	if err != nil {
   668  		fw.fs.log(fmt.Sprintf("close %s: %v", fw.fd, err))
   669  	}
   670  	return err
   671  }
   672  
   673  func fsGenName(fd storage.FileDesc) string {
   674  	switch fd.Type {
   675  	case storage.TypeManifest:
   676  		return fmt.Sprintf("MANIFEST-%06d", fd.Num)
   677  	case storage.TypeJournal:
   678  		return fmt.Sprintf("%06d.log", fd.Num)
   679  	case storage.TypeTable:
   680  		return fmt.Sprintf("%06d.ldb", fd.Num)
   681  	case storage.TypeTemp:
   682  		return fmt.Sprintf("%06d.tmp", fd.Num)
   683  	default:
   684  		panic("invalid file type")
   685  	}
   686  }
   687  
   688  func fsHasOldName(fd storage.FileDesc) bool {
   689  	return fd.Type == storage.TypeTable
   690  }
   691  
   692  func fsGenOldName(fd storage.FileDesc) string {
   693  	switch fd.Type {
   694  	case storage.TypeTable:
   695  		return fmt.Sprintf("%06d.sst", fd.Num)
   696  	default:
   697  		return fsGenName(fd)
   698  	}
   699  }
   700  
   701  func fsParseName(name string) (fd storage.FileDesc, ok bool) {
   702  	var tail string
   703  	_, err := fmt.Sscanf(name, "%d.%s", &fd.Num, &tail)
   704  	if err == nil {
   705  		switch tail {
   706  		case "log":
   707  			fd.Type = storage.TypeJournal
   708  		case "ldb", "sst":
   709  			fd.Type = storage.TypeTable
   710  		case "tmp":
   711  			fd.Type = storage.TypeTemp
   712  		default:
   713  			return
   714  		}
   715  		return fd, true
   716  	}
   717  	n, _ := fmt.Sscanf(name, "MANIFEST-%d%s", &fd.Num, &tail)
   718  	if n == 1 {
   719  		fd.Type = storage.TypeManifest
   720  		return fd, true
   721  	}
   722  	return
   723  }
   724  
   725  func fsParseNamePtr(name string, fd *storage.FileDesc) bool {
   726  	_fd, ok := fsParseName(name)
   727  	if fd != nil {
   728  		*fd = _fd
   729  	}
   730  	return ok
   731  }