2018-02-13 15:23:09 -05:00
|
|
|
// Copyright 2015 Light Code Labs, LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package caddytls
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/mholt/caddy"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
// be sure to remove lock files when exiting the process!
|
|
|
|
caddy.OnProcessExit = append(caddy.OnProcessExit, func() {
|
|
|
|
fileStorageNameLocksMu.Lock()
|
|
|
|
defer fileStorageNameLocksMu.Unlock()
|
|
|
|
for key, fw := range fileStorageNameLocks {
|
|
|
|
os.Remove(fw.filename)
|
|
|
|
delete(fileStorageNameLocks, key)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// fileStorageLock facilitates ACME-related locking by using
|
|
|
|
// the associated FileStorage, so multiple processes can coordinate
|
|
|
|
// renewals on the certificates on a shared file system.
|
|
|
|
type fileStorageLock struct {
|
|
|
|
caURL string
|
|
|
|
storage *FileStorage
|
|
|
|
}
|
|
|
|
|
|
|
|
// TryLock attempts to get a lock for name, otherwise it returns
|
|
|
|
// a Waiter value to wait until the other process is finished.
|
|
|
|
func (s *fileStorageLock) TryLock(name string) (Waiter, error) {
|
|
|
|
fileStorageNameLocksMu.Lock()
|
|
|
|
defer fileStorageNameLocksMu.Unlock()
|
|
|
|
|
|
|
|
// see if lock already exists within this process
|
|
|
|
fw, ok := fileStorageNameLocks[s.caURL+name]
|
|
|
|
if ok {
|
|
|
|
// lock already created within process, let caller wait on it
|
|
|
|
return fw, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// attempt to persist lock to disk by creating lock file
|
|
|
|
fw = &fileWaiter{
|
|
|
|
filename: s.storage.siteCertFile(name) + ".lock",
|
|
|
|
wg: new(sync.WaitGroup),
|
|
|
|
}
|
2018-02-14 15:32:16 -05:00
|
|
|
// parent dir must exist
|
|
|
|
if err := os.MkdirAll(s.storage.site(name), 0700); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-02-13 15:23:09 -05:00
|
|
|
lf, err := os.OpenFile(fw.filename, os.O_CREATE|os.O_EXCL, 0644)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsExist(err) {
|
|
|
|
// another process has the lock; use it to wait
|
|
|
|
return fw, nil
|
|
|
|
}
|
|
|
|
// otherwise, this was some unexpected error
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
lf.Close()
|
|
|
|
|
|
|
|
// looks like we get the lock
|
|
|
|
fw.wg.Add(1)
|
|
|
|
fileStorageNameLocks[s.caURL+name] = fw
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unlock unlocks name.
|
|
|
|
func (s *fileStorageLock) Unlock(name string) error {
|
|
|
|
fileStorageNameLocksMu.Lock()
|
|
|
|
defer fileStorageNameLocksMu.Unlock()
|
|
|
|
fw, ok := fileStorageNameLocks[s.caURL+name]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("FileStorage: no lock to release for %s", name)
|
|
|
|
}
|
2018-03-28 13:04:35 -05:00
|
|
|
// remove lock file
|
2018-02-13 15:23:09 -05:00
|
|
|
os.Remove(fw.filename)
|
2018-03-28 13:04:35 -05:00
|
|
|
|
|
|
|
// if parent folder is now empty, remove it too to keep it tidy
|
|
|
|
lockParentFolder := s.storage.site(name)
|
|
|
|
dir, err := os.Open(lockParentFolder)
|
|
|
|
if err == nil {
|
|
|
|
items, _ := dir.Readdirnames(3) // OK to ignore error here
|
|
|
|
if len(items) == 0 {
|
|
|
|
os.Remove(lockParentFolder)
|
|
|
|
}
|
|
|
|
dir.Close()
|
|
|
|
}
|
|
|
|
|
2018-02-13 15:23:09 -05:00
|
|
|
fw.wg.Done()
|
|
|
|
delete(fileStorageNameLocks, s.caURL+name)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fileWaiter waits for a file to disappear; it polls
|
|
|
|
// the file system to check for the existence of a file.
|
|
|
|
// It also has a WaitGroup which will be faster than
|
|
|
|
// polling, for when locking need only happen within this
|
|
|
|
// process.
|
|
|
|
type fileWaiter struct {
|
|
|
|
filename string
|
|
|
|
wg *sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait waits until the lock is released.
|
|
|
|
func (fw *fileWaiter) Wait() {
|
|
|
|
start := time.Now()
|
|
|
|
fw.wg.Wait()
|
|
|
|
for time.Since(start) < 1*time.Hour {
|
|
|
|
_, err := os.Stat(fw.filename)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var fileStorageNameLocks = make(map[string]*fileWaiter) // keyed by CA + name
|
|
|
|
var fileStorageNameLocksMu sync.Mutex
|
|
|
|
|
|
|
|
var _ Locker = &fileStorageLock{}
|
|
|
|
var _ Waiter = &fileWaiter{}
|