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 }