• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14358372723

09 Apr 2025 01:26PM UTC coverage: 56.696% (-12.3%) from 69.037%
14358372723

Pull #9696

github

web-flow
Merge e2837e400 into 867d27d68
Pull Request #9696: Add `development_guidelines.md` for both human and machine

107055 of 188823 relevant lines covered (56.7%)

22721.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.03
/chanbackup/backupfile.go
1
package chanbackup
2

3
import (
4
        "fmt"
5
        "io"
6
        "os"
7
        "path/filepath"
8
        "time"
9

10
        "github.com/lightningnetwork/lnd/keychain"
11
        "github.com/lightningnetwork/lnd/lnrpc"
12
)
13

14
const (
15
        // DefaultBackupFileName is the default name of the auto updated static
16
        // channel backup fie.
17
        DefaultBackupFileName = "channel.backup"
18

19
        // DefaultTempBackupFileName is the default name of the temporary SCB
20
        // file that we'll use to atomically update the primary back up file
21
        // when new channel are detected.
22
        DefaultTempBackupFileName = "temp-dont-use.backup"
23

24
        // DefaultChanBackupArchiveDirName is the default name of the directory
25
        // that we'll use to store old channel backups.
26
        DefaultChanBackupArchiveDirName = "chan-backup-archives"
27
)
28

29
var (
30
        // ErrNoBackupFileExists is returned if caller attempts to call
31
        // UpdateAndSwap with the file name not set.
32
        ErrNoBackupFileExists = fmt.Errorf("back up file name not set")
33

34
        // ErrNoTempBackupFile is returned if caller attempts to call
35
        // UpdateAndSwap with the temp back up file name not set.
36
        ErrNoTempBackupFile = fmt.Errorf("temp backup file not set")
37
)
38

39
// MultiFile represents a file on disk that a caller can use to read the packed
40
// multi backup into an unpacked one, and also atomically update the contents
41
// on disk once new channels have been opened, and old ones closed. This struct
42
// relies on an atomic file rename property which most widely use file systems
43
// have.
44
type MultiFile struct {
45
        // fileName is the file name of the main back up file.
46
        fileName string
47

48
        // tempFileName is the name of the file that we'll use to stage a new
49
        // packed multi-chan backup, and the rename to the main back up file.
50
        tempFileName string
51

52
        // tempFile is an open handle to the temp back up file.
53
        tempFile *os.File
54

55
        // archiveDir is the directory where we'll store old channel backups.
56
        archiveDir string
57

58
        // noBackupArchive indicates whether old backups should be deleted
59
        // rather than archived.
60
        noBackupArchive bool
61
}
62

63
// NewMultiFile create a new multi-file instance at the target location on the
64
// file system.
65
func NewMultiFile(fileName string, noBackupArchive bool) *MultiFile {
11✔
66
        // We'll our temporary backup file in the very same directory as the
11✔
67
        // main backup file.
11✔
68
        backupFileDir := filepath.Dir(fileName)
11✔
69
        tempFileName := filepath.Join(
11✔
70
                backupFileDir, DefaultTempBackupFileName,
11✔
71
        )
11✔
72
        archiveDir := filepath.Join(
11✔
73
                backupFileDir, DefaultChanBackupArchiveDirName,
11✔
74
        )
11✔
75

11✔
76
        return &MultiFile{
11✔
77
                fileName:        fileName,
11✔
78
                tempFileName:    tempFileName,
11✔
79
                archiveDir:      archiveDir,
11✔
80
                noBackupArchive: noBackupArchive,
11✔
81
        }
11✔
82
}
11✔
83

84
// UpdateAndSwap will attempt write a new temporary backup file to disk with
85
// the newBackup encoded, then atomically swap (via rename) the old file for
86
// the new file by updating the name of the new file to the old. It also checks
87
// if the old file should be archived first before swapping it.
88
func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
9✔
89
        // If the main backup file isn't set, then we can't proceed.
9✔
90
        if b.fileName == "" {
10✔
91
                return ErrNoBackupFileExists
1✔
92
        }
1✔
93

94
        log.Infof("Updating backup file at %v", b.fileName)
8✔
95

8✔
96
        // If the old back up file still exists, then we'll delete it before
8✔
97
        // proceeding.
8✔
98
        if _, err := os.Stat(b.tempFileName); err == nil {
9✔
99
                log.Infof("Found old temp backup @ %v, removing before swap",
1✔
100
                        b.tempFileName)
1✔
101

1✔
102
                err = os.Remove(b.tempFileName)
1✔
103
                if err != nil {
1✔
104
                        return fmt.Errorf("unable to remove temp "+
×
105
                                "backup file: %v", err)
×
106
                }
×
107
        }
108

109
        // Now that we know the staging area is clear, we'll create the new
110
        // temporary back up file.
111
        var err error
8✔
112
        b.tempFile, err = os.Create(b.tempFileName)
8✔
113
        if err != nil {
8✔
114
                return fmt.Errorf("unable to create temp file: %w", err)
×
115
        }
×
116

117
        // With the file created, we'll write the new packed multi backup and
118
        // remove the temporary file all together once this method exits.
119
        _, err = b.tempFile.Write([]byte(newBackup))
8✔
120
        if err != nil {
8✔
121
                return fmt.Errorf("unable to write backup to temp file: %w",
×
122
                        err)
×
123
        }
×
124
        if err := b.tempFile.Sync(); err != nil {
8✔
125
                return fmt.Errorf("unable to sync temp file: %w", err)
×
126
        }
×
127
        defer os.Remove(b.tempFileName)
8✔
128

8✔
129
        log.Infof("Swapping old multi backup file from %v to %v",
8✔
130
                b.tempFileName, b.fileName)
8✔
131

8✔
132
        // Before we rename the swap (atomic name swap), we'll make
8✔
133
        // sure to close the current file as some OSes don't support
8✔
134
        // renaming a file that's already open (Windows).
8✔
135
        if err := b.tempFile.Close(); err != nil {
8✔
136
                return fmt.Errorf("unable to close file: %w", err)
×
137
        }
×
138

139
        // Archive the old channel backup file before replacing.
140
        if err := b.createArchiveFile(); err != nil {
8✔
141
                return fmt.Errorf("unable to archive old channel "+
×
142
                        "backup file: %w", err)
×
143
        }
×
144

145
        // Finally, we'll attempt to atomically rename the temporary file to
146
        // the main back up file. If this succeeds, then we'll only have a
147
        // single file on disk once this method exits.
148
        return os.Rename(b.tempFileName, b.fileName)
8✔
149
}
150

151
// ExtractMulti attempts to extract the packed multi backup we currently point
152
// to into an unpacked version. This method will fail if no backup file
153
// currently exists as the specified location.
154
func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) {
4✔
155
        var err error
4✔
156

4✔
157
        // We'll return an error if the main file isn't currently set.
4✔
158
        if b.fileName == "" {
5✔
159
                return nil, ErrNoBackupFileExists
1✔
160
        }
1✔
161

162
        // Now that we've confirmed the target file is populated, we'll read
163
        // all the contents of the file. This function ensures that file is
164
        // always closed, even if we can't read the contents.
165
        multiBytes, err := os.ReadFile(b.fileName)
3✔
166
        if err != nil {
4✔
167
                return nil, err
1✔
168
        }
1✔
169

170
        // Finally, we'll attempt to unpack the file and return the unpack
171
        // version to the caller.
172
        packedMulti := PackedMulti(multiBytes)
2✔
173
        return packedMulti.Unpack(keyChain)
2✔
174
}
175

176
// createArchiveFile creates an archive file with a timestamped name in the
177
// specified archive directory, and copies the contents of the main backup file
178
// to the new archive file.
179
func (b *MultiFile) createArchiveFile() error {
11✔
180
        // User can skip archiving of old backup files to save disk space.
11✔
181
        if b.noBackupArchive {
14✔
182
                log.Debug("Skipping archive of old backup file as configured")
3✔
183
                return nil
3✔
184
        }
3✔
185

186
        // Check for old channel backup file.
187
        oldFileExists := lnrpc.FileExists(b.fileName)
8✔
188
        if !oldFileExists {
11✔
189
                log.Debug("No old channel backup file to archive")
3✔
190
                return nil
3✔
191
        }
3✔
192

193
        log.Infof("Archiving old channel backup to %v", b.archiveDir)
5✔
194

5✔
195
        // Generate archive file path with timestamped name.
5✔
196
        baseFileName := filepath.Base(b.fileName)
5✔
197
        timestamp := time.Now().Format("2006-01-02-15-04-05")
5✔
198

5✔
199
        archiveFileName := fmt.Sprintf("%s-%s", baseFileName, timestamp)
5✔
200
        archiveFilePath := filepath.Join(b.archiveDir, archiveFileName)
5✔
201

5✔
202
        oldBackupFile, err := os.Open(b.fileName)
5✔
203
        if err != nil {
5✔
204
                return fmt.Errorf("unable to open old channel backup file: "+
×
205
                        "%w", err)
×
206
        }
×
207
        defer func() {
10✔
208
                err := oldBackupFile.Close()
5✔
209
                if err != nil {
5✔
210
                        log.Errorf("unable to close old channel backup file: "+
×
211
                                "%v", err)
×
212
                }
×
213
        }()
214

215
        // Ensure the archive directory exists. If it doesn't we create it.
216
        const archiveDirPermissions = 0o700
5✔
217
        err = os.MkdirAll(b.archiveDir, archiveDirPermissions)
5✔
218
        if err != nil {
5✔
219
                return fmt.Errorf("unable to create archive directory: %w", err)
×
220
        }
×
221

222
        // Create new archive file.
223
        archiveFile, err := os.Create(archiveFilePath)
5✔
224
        if err != nil {
6✔
225
                return fmt.Errorf("unable to create archive file: %w", err)
1✔
226
        }
1✔
227
        defer func() {
8✔
228
                err := archiveFile.Close()
4✔
229
                if err != nil {
4✔
230
                        log.Errorf("unable to close archive file: %v", err)
×
231
                }
×
232
        }()
233

234
        // Copy contents of old backup to the newly created archive files.
235
        _, err = io.Copy(archiveFile, oldBackupFile)
4✔
236
        if err != nil {
4✔
237
                return fmt.Errorf("unable to copy to archive file: %w", err)
×
238
        }
×
239
        err = archiveFile.Sync()
4✔
240
        if err != nil {
4✔
241
                return fmt.Errorf("unable to sync archive file: %w", err)
×
242
        }
×
243

244
        return nil
4✔
245
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc