1// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
2// All rights reserved.
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// +build darwin dragonfly freebsd linux netbsd openbsd
8
9package storage
10
11import (
12	"os"
13	"syscall"
14)
15
16type unixFileLock struct {
17	f *os.File
18}
19
20func (fl *unixFileLock) release() error {
21	if err := setFileLock(fl.f, false, false); err != nil {
22		return err
23	}
24	return fl.f.Close()
25}
26
27func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
28	var flag int
29	if readOnly {
30		flag = os.O_RDONLY
31	} else {
32		flag = os.O_RDWR
33	}
34	f, err := os.OpenFile(path, flag, 0)
35	if os.IsNotExist(err) {
36		f, err = os.OpenFile(path, flag|os.O_CREATE, 0644)
37	}
38	if err != nil {
39		return
40	}
41	err = setFileLock(f, readOnly, true)
42	if err != nil {
43		f.Close()
44		return
45	}
46	fl = &unixFileLock{f: f}
47	return
48}
49
50func setFileLock(f *os.File, readOnly, lock bool) error {
51	how := syscall.LOCK_UN
52	if lock {
53		if readOnly {
54			how = syscall.LOCK_SH
55		} else {
56			how = syscall.LOCK_EX
57		}
58	}
59	return syscall.Flock(int(f.Fd()), how|syscall.LOCK_NB)
60}
61
62func rename(oldpath, newpath string) error {
63	return os.Rename(oldpath, newpath)
64}
65
66func isErrInvalid(err error) bool {
67	if err == os.ErrInvalid {
68		return true
69	}
70	// Go < 1.8
71	if syserr, ok := err.(*os.SyscallError); ok && syserr.Err == syscall.EINVAL {
72		return true
73	}
74	// Go >= 1.8 returns *os.PathError instead
75	if patherr, ok := err.(*os.PathError); ok && patherr.Err == syscall.EINVAL {
76		return true
77	}
78	return false
79}
80
81func syncDir(name string) error {
82	// As per fsync manpage, Linux seems to expect fsync on directory, however
83	// some system don't support this, so we will ignore syscall.EINVAL.
84	//
85	// From fsync(2):
86	//   Calling fsync() does not necessarily ensure that the entry in the
87	//   directory containing the file has also reached disk. For that an
88	//   explicit fsync() on a file descriptor for the directory is also needed.
89	f, err := os.Open(name)
90	if err != nil {
91		return err
92	}
93	defer f.Close()
94	if err := f.Sync(); err != nil && !isErrInvalid(err) {
95		return err
96	}
97	return nil
98}
99