func (s *Filesystem) writeLockInfo(info *LockInfo) error {
	path := s.lockInfoPath()
	info.Path = s.readPath
	info.Created = time.Now().UTC()
	log.Printf("[TRACE] statemgr.Filesystem: writing lock metadata to %s", path)
	err := ioutil.WriteFile(path, info.Marshal(), 0600)
	if err != nil {
		return fmt.Errorf("could not write lock info for %q: %s", s.readPath, err)
	}
	return nil
}
write a new lock info file
package statemgr
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sync"
	"time"
	multierror "github.com/hashicorp/go-multierror"
	"github.com/camptocamp/terraboard/internal/terraform/states"
	"github.com/camptocamp/terraboard/internal/terraform/states/statefile"
)
type Filesystem struct {
	mu sync.Mutex
	path string
	readPath string
	backupPath string
	stateFileOut *os.File
	lockID string
	created bool
	file          *statefile.File
	readFile      *statefile.File
	backupFile    *statefile.File
	writtenBackup bool
}
var (
	_ Full           = (*Filesystem)(nil)
	_ PersistentMeta = (*Filesystem)(nil)
	_ Migrator       = (*Filesystem)(nil)
)
func NewFilesystem(statePath string) *Filesystem {
	return &Filesystem{
		path:     statePath,
		readPath: statePath,
	}
}
func NewFilesystemBetweenPaths(readPath, writePath string) *Filesystem {
	return &Filesystem{
		path:     writePath,
		readPath: readPath,
	}
}
func (s *Filesystem) SetBackupPath(path string) {
	s.backupPath = path
	s.backupFile = nil
	s.writtenBackup = false
}
func (s *Filesystem) BackupPath() string {
	return s.backupPath
}
func (s *Filesystem) State() *states.State {
	defer s.mutex()()
	if s.file == nil {
		return nil
	}
	return s.file.DeepCopy().State
}
func (s *Filesystem) WriteState(state *states.State) error {
	defer s.mutex()()
	if s.readFile == nil {
		err := s.refreshState()
		if err != nil {
			return err
		}
	}
	return s.writeState(state, nil)
}
func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error {
	if s.stateFileOut == nil {
		if err := s.createStateFiles(); err != nil {
			return nil
		}
	}
	defer s.stateFileOut.Sync()
	if !s.writtenBackup && s.backupFile != nil && s.backupPath != "" {
		if !statefile.StatesMarshalEqual(state, s.backupFile.State) {
			log.Printf("[TRACE] statemgr.Filesystem: creating backup snapshot at %s", s.backupPath)
			bfh, err := os.Create(s.backupPath)
			if err != nil {
				return fmt.Errorf("failed to create local state backup file: %s", err)
			}
			defer bfh.Close()
			err = statefile.Write(s.backupFile, bfh)
			if err != nil {
				return fmt.Errorf("failed to write to local state backup file: %s", err)
			}
			s.writtenBackup = true
		} else {
			log.Print("[TRACE] statemgr.Filesystem: not making a backup, because the new snapshot is identical to the old")
		}
	} else {
		switch {
		case s.backupPath == "":
			log.Print("[TRACE] statemgr.Filesystem: state file backups are disabled")
		case s.writtenBackup:
			log.Printf("[TRACE] statemgr.Filesystem: have already backed up original %s to %s on a previous write", s.path, s.backupPath)
		case s.backupFile == nil:
			log.Printf("[TRACE] statemgr.Filesystem: no original state snapshot to back up")
		default:
			log.Printf("[TRACE] statemgr.Filesystem: not creating a backup for an unknown reason")
		}
	}
	s.file = s.file.DeepCopy()
	if s.file == nil {
		s.file = NewStateFile()
	}
	s.file.State = state.DeepCopy()
	if _, err := s.stateFileOut.Seek(0, io.SeekStart); err != nil {
		return err
	}
	if err := s.stateFileOut.Truncate(0); err != nil {
		return err
	}
	if state == nil {
		log.Print("[TRACE] statemgr.Filesystem: state is nil, so leaving the file empty")
		return nil
	}
	if meta == nil {
		if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) {
			s.file.Serial++
			log.Printf("[TRACE] statemgr.Filesystem: state has changed since last snapshot, so incrementing serial to %d", s.file.Serial)
		} else {
			log.Print("[TRACE] statemgr.Filesystem: no state changes since last snapshot")
		}
	} else {
		s.file.Lineage = meta.Lineage
		s.file.Serial = meta.Serial
		log.Printf("[TRACE] statemgr.Filesystem: forcing lineage %q serial %d for migration/import", s.file.Lineage, s.file.Serial)
	}
	log.Printf("[TRACE] statemgr.Filesystem: writing snapshot at %s", s.path)
	if err := statefile.Write(s.file, s.stateFileOut); err != nil {
		return err
	}
	s.readPath = s.path
	return nil
}
func (s *Filesystem) PersistState() error {
	return nil
}
func (s *Filesystem) RefreshState() error {
	defer s.mutex()()
	return s.refreshState()
}
func (s *Filesystem) refreshState() error {
	var reader io.Reader
	if s.stateFileOut == nil || s.readPath != s.path {
		log.Printf("[TRACE] statemgr.Filesystem: reading initial snapshot from %s", s.readPath)
		f, err := os.Open(s.readPath)
		if err != nil {
			if !os.IsNotExist(err) {
				return err
			}
			reader = bytes.NewBuffer(nil)
		} else {
			defer f.Close()
			reader = f
		}
	} else {
		log.Printf("[TRACE] statemgr.Filesystem: reading latest snapshot from %s", s.path)
		if s.stateFileOut == nil {
			return nil
		}
		s.stateFileOut.Seek(0, io.SeekStart)
		reader = s.stateFileOut
	}
	f, err := statefile.Read(reader)
	if err != nil {
		if err != statefile.ErrNoState {
			return err
		}
		log.Printf("[TRACE] statemgr.Filesystem: snapshot file has nil snapshot, but that's okay")
	}
	s.file = f
	s.readFile = s.file.DeepCopy()
	if s.file != nil {
		log.Printf("[TRACE] statemgr.Filesystem: read snapshot with lineage %q serial %d", s.file.Lineage, s.file.Serial)
	} else {
		log.Print("[TRACE] statemgr.Filesystem: read nil snapshot")
	}
	return nil
}
func (s *Filesystem) Lock(info *LockInfo) (string, error) {
	defer s.mutex()()
	if s.stateFileOut == nil {
		if err := s.createStateFiles(); err != nil {
			return "", err
		}
	}
	if s.lockID != "" {
		return "", fmt.Errorf("state %q already locked", s.stateFileOut.Name())
	}
	if err := s.lock(); err != nil {
		info, infoErr := s.lockInfo()
		if infoErr != nil {
			err = multierror.Append(err, infoErr)
		}
		lockErr := &LockError{
			Info: info,
			Err:  err,
		}
		return "", lockErr
	}
	s.lockID = info.ID
	return s.lockID, s.writeLockInfo(info)
}
func (s *Filesystem) Unlock(id string) error {
	defer s.mutex()()
	if s.lockID == "" {
		return fmt.Errorf("LocalState not locked")
	}
	if id != s.lockID {
		idErr := fmt.Errorf("invalid lock id: %q. current id: %q", id, s.lockID)
		info, err := s.lockInfo()
		if err != nil {
			idErr = multierror.Append(idErr, err)
		}
		return &LockError{
			Err:  idErr,
			Info: info,
		}
	}
	lockInfoPath := s.lockInfoPath()
	log.Printf("[TRACE] statemgr.Filesystem: removing lock metadata file %s", lockInfoPath)
	os.Remove(lockInfoPath)
	fileName := s.stateFileOut.Name()
	unlockErr := s.unlock()
	s.stateFileOut.Close()
	s.stateFileOut = nil
	s.lockID = ""
	stat, err := os.Stat(fileName)
	if err == nil && stat.Size() == 0 && s.created {
		os.Remove(fileName)
	}
	return unlockErr
}
func (s *Filesystem) StateSnapshotMeta() SnapshotMeta {
	if s.file == nil {
		return SnapshotMeta{}  
	}
	return SnapshotMeta{
		Lineage: s.file.Lineage,
		Serial:  s.file.Serial,
		TerraformVersion: s.file.TerraformVersion,
	}
}
func (s *Filesystem) StateForMigration() *statefile.File {
	return s.file.DeepCopy()
}
func (s *Filesystem) WriteStateForMigration(f *statefile.File, force bool) error {
	defer s.mutex()()
	if s.readFile == nil {
		err := s.refreshState()
		if err != nil {
			return err
		}
	}
	if !force {
		err := CheckValidImport(f, s.readFile)
		if err != nil {
			return err
		}
	}
	if s.readFile != nil {
		log.Printf(
			"[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d over snapshot with lineage %q serial %d at %s",
			f.Lineage, f.Serial,
			s.readFile.Lineage, s.readFile.Serial,
			s.path,
		)
	} else {
		log.Printf(
			"[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d as the initial state snapshot at %s",
			f.Lineage, f.Serial,
			s.path,
		)
	}
	err := s.writeState(f.State, &SnapshotMeta{Lineage: f.Lineage, Serial: f.Serial})
	if err != nil {
		return err
	}
	return nil
}
func (s *Filesystem) createStateFiles() error {
	log.Printf("[TRACE] statemgr.Filesystem: preparing to manage state snapshots at %s", s.path)
	if _, err := os.Stat(s.path); os.IsNotExist(err) {
		s.created = true
	}
	if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
		return err
	}
	f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		return err
	}
	s.stateFileOut = f
	s.backupFile, err = statefile.Read(s.stateFileOut)
	if err != nil {
		if err != statefile.ErrNoState {
			return err
		}
		log.Printf("[TRACE] statemgr.Filesystem: no previously-stored snapshot exists")
	} else {
		log.Printf("[TRACE] statemgr.Filesystem: existing snapshot has lineage %q serial %d", s.backupFile.Lineage, s.backupFile.Serial)
	}
	return nil
}
func (s *Filesystem) lockInfoPath() string {
	stateDir, stateName := filepath.Split(s.path)
	if stateName == "" {
		panic("empty state file path")
	}
	if stateName[0] == '.' {
		stateName = stateName[1:]
	}
	return filepath.Join(stateDir, fmt.Sprintf(".%s.lock.info", stateName))
}
func (s *Filesystem) lockInfo() (*LockInfo, error) {
	path := s.lockInfoPath()
	infoData, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	info := LockInfo{}
	err = json.Unmarshal(infoData, &info)
	if err != nil {
		return nil, fmt.Errorf("state file %q locked, but could not unmarshal lock info: %s", s.readPath, err)
	}
	return &info, nil
}