github.com/Cloud-Foundations/Dominator@v0.3.4/lib/fsutil/loopback.go (about)

     1  package fsutil
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/Cloud-Foundations/Dominator/lib/backoffdelay"
    12  	"github.com/Cloud-Foundations/Dominator/lib/format"
    13  	"github.com/Cloud-Foundations/Dominator/lib/log"
    14  )
    15  
    16  var losetupMutex sync.Mutex
    17  
    18  func loopbackDelete(loopDevice string, grabLock bool) error {
    19  	if grabLock {
    20  		losetupMutex.Lock()
    21  		defer losetupMutex.Unlock()
    22  	}
    23  	return exec.Command("losetup", "-d", loopDevice).Run()
    24  }
    25  
    26  func loopbackDeleteAndWaitForPartition(loopDevice, partition string,
    27  	timeout time.Duration, logger log.DebugLogger) error {
    28  	losetupMutex.Lock()
    29  	defer losetupMutex.Unlock()
    30  	if err := loopbackDelete(loopDevice, false); err != nil {
    31  		return err
    32  	}
    33  	// Wait for partition device to disappear. Deleting it directly might not be
    34  	// safe because there may be a pending dynamic device node deletion event.
    35  	partitionDevice := loopDevice + partition
    36  	sleeper := backoffdelay.NewExponential(time.Millisecond,
    37  		100*time.Millisecond, 2)
    38  	startTime := time.Now()
    39  	stopTime := startTime.Add(timeout)
    40  	for count := 0; time.Until(stopTime) >= 0; count++ {
    41  		if _, err := os.Stat(partitionDevice); err != nil {
    42  			if os.IsNotExist(err) {
    43  				return nil
    44  			}
    45  			return err
    46  		}
    47  		sleeper.Sleep()
    48  	}
    49  	if time.Since(startTime) > 15*time.Second {
    50  		if err := os.Remove(partitionDevice); err != nil {
    51  			logger.Printf("failed to forcibly delete partition: %s: %s\n",
    52  				partitionDevice, err)
    53  		} else {
    54  			logger.Printf("forcibly deleted partition: %s\n", partitionDevice)
    55  		}
    56  	}
    57  	return fmt.Errorf("timed out waiting for partition to delete: %s",
    58  		partitionDevice)
    59  }
    60  
    61  func loopbackSetup(filename string, grabLock bool) (string, error) {
    62  	if grabLock {
    63  		losetupMutex.Lock()
    64  		defer losetupMutex.Unlock()
    65  	}
    66  	cmd := exec.Command("losetup", "-fP", "--show", filename)
    67  	output, err := cmd.CombinedOutput()
    68  	if err != nil {
    69  		return "", fmt.Errorf("%s: %s", err, output)
    70  	}
    71  	return strings.TrimSpace(string(output)), nil
    72  }
    73  
    74  func loopbackSetupAndWaitForPartition(filename, partition string,
    75  	timeout time.Duration, logger log.DebugLogger) (string, error) {
    76  	if timeout < 0 || timeout > time.Hour {
    77  		timeout = time.Hour
    78  	}
    79  	losetupMutex.Lock()
    80  	defer losetupMutex.Unlock()
    81  	loopDevice, err := loopbackSetup(filename, false)
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  	doDelete := true
    86  	defer func() {
    87  		if doDelete {
    88  			loopbackDelete(loopDevice, false)
    89  		}
    90  	}()
    91  	// Probe for partition device because it might not be immediately available.
    92  	// Need to open rather than just test for inode existance, because an
    93  	// Open(2) is what may be needed to trigger dynamic device node creation.
    94  	partitionDevice := loopDevice + partition
    95  	sleeper := backoffdelay.NewExponential(time.Millisecond,
    96  		100*time.Millisecond, 2)
    97  	startTime := time.Now()
    98  	stopTime := startTime.Add(timeout)
    99  	var numNonBlock, numOpened uint
   100  	for numIterations := 0; time.Until(stopTime) >= 0; numIterations++ {
   101  		if file, err := os.Open(partitionDevice); err == nil {
   102  			numOpened++
   103  			fi, err := file.Stat()
   104  			file.Close()
   105  			if err != nil {
   106  				return "", err
   107  			}
   108  			if fi.Mode()&os.ModeDevice != 0 {
   109  				if numIterations > 0 {
   110  					if time.Since(startTime) > time.Second {
   111  						logger.Printf(
   112  							"%s valid after: %d iterations (%d/%d were not a block device), %s\n",
   113  							partitionDevice, numIterations, numNonBlock,
   114  							numOpened, format.Duration(time.Since(startTime)))
   115  					} else {
   116  						logger.Debugf(0,
   117  							"%s valid after: %d iterations (%d/%d were not a block device), %s\n",
   118  							partitionDevice, numIterations, numNonBlock,
   119  							numOpened, format.Duration(time.Since(startTime)))
   120  					}
   121  				}
   122  				doDelete = false
   123  				return loopDevice, nil
   124  			}
   125  			numNonBlock++
   126  		}
   127  		sleeper.Sleep()
   128  	}
   129  	if numOpened > 0 {
   130  		if time.Since(startTime) > 15*time.Second {
   131  			if err := os.Remove(partitionDevice); err != nil {
   132  				logger.Printf("failed to forcibly delete partition: %s: %s\n",
   133  					partitionDevice, err)
   134  			} else {
   135  				logger.Printf("forcibly deleted partition: %s\n",
   136  					partitionDevice)
   137  			}
   138  		}
   139  	}
   140  	return "", fmt.Errorf("timed out waiting for partition (%d non-block): %s",
   141  		numNonBlock, partitionDevice)
   142  }