mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 02:27:19 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			141 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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),
 | |
| 	}
 | |
| 	// parent dir must exist
 | |
| 	if err := os.MkdirAll(s.storage.site(name), 0700); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	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)
 | |
| 	}
 | |
| 	// remove lock file
 | |
| 	os.Remove(fw.filename)
 | |
| 
 | |
| 	// 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()
 | |
| 	}
 | |
| 
 | |
| 	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{}
 |