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

lightningnetwork / lnd / 12456011944

22 Dec 2024 04:45PM UTC coverage: 58.556% (-0.04%) from 58.598%
12456011944

Pull #9232

github

Abdulkbk
chanbackup: test archiving chan backups
Pull Request #9232: chanbackup: archive old channel backups

46 of 58 new or added lines in 2 files covered. (79.31%)

324 existing lines in 30 files now uncovered.

134919 of 230410 relevant lines covered (58.56%)

19202.13 hits per line

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

81.67
/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
)
12

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

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

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

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

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

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

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

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

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

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

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

8✔
75
        return &MultiFile{
8✔
76
                fileName:        fileName,
8✔
77
                tempFileName:    tempFileName,
8✔
78
                archiveDir:      archiveDir,
8✔
79
                deleteOldBackup: deleteOldBackup,
8✔
80
        }
8✔
81
}
8✔
82

83
// UpdateAndSwap will attempt write a new temporary backup file to disk with
84
// the newBackup encoded, then atomically swap (via rename) the old file for
85
// the new file by updating the name of the new file to the old.
86
func (b *MultiFile) UpdateAndSwap(newBackup PackedMulti) error {
7✔
87
        // If the main backup file isn't set, then we can't proceed.
7✔
88
        if b.fileName == "" {
8✔
89
                return ErrNoBackupFileExists
1✔
90
        }
1✔
91

92
        log.Infof("Updating backup file at %v", b.fileName)
6✔
93

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

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

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

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

6✔
127
        log.Infof("Swapping old multi backup file from %v to %v",
6✔
128
                b.tempFileName, b.fileName)
6✔
129

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

137
        if !b.deleteOldBackup {
12✔
138
                // We check if the main backup file exists, if it does we
6✔
139
                // archive it before replacing it with the new backup.
6✔
140
                if _, err := os.Stat(b.fileName); err == nil {
11✔
141
                        log.Infof("Archiving old backup file at %v", b.fileName)
5✔
142

5✔
143
                        if err := createArchiveFile(
5✔
144
                                b.archiveDir, b.fileName,
5✔
145
                        ); err != nil {
5✔
NEW
146
                                return fmt.Errorf("unable to archive old "+
×
NEW
147
                                        "backup file: %w", err)
×
NEW
148
                        }
×
149
                }
150
        }
151

152
        // Finally, we'll attempt to atomically rename the temporary file to
153
        // the main back up file. If this succeeds, then we'll only have a
154
        // single file on disk once this method exits.
155
        return os.Rename(b.tempFileName, b.fileName)
6✔
156
}
157

158
// ExtractMulti attempts to extract the packed multi backup we currently point
159
// to into an unpacked version. This method will fail if no backup file
160
// currently exists as the specified location.
161
func (b *MultiFile) ExtractMulti(keyChain keychain.KeyRing) (*Multi, error) {
6✔
162
        var err error
6✔
163

6✔
164
        // We'll return an error if the main file isn't currently set.
6✔
165
        if b.fileName == "" {
7✔
166
                return nil, ErrNoBackupFileExists
1✔
167
        }
1✔
168

169
        // Now that we've confirmed the target file is populated, we'll read
170
        // all the contents of the file. This function ensures that file is
171
        // always closed, even if we can't read the contents.
172
        multiBytes, err := os.ReadFile(b.fileName)
5✔
173
        if err != nil {
8✔
174
                return nil, err
3✔
175
        }
3✔
176

177
        // Finally, we'll attempt to unpack the file and return the unpack
178
        // version to the caller.
179
        packedMulti := PackedMulti(multiBytes)
4✔
180
        return packedMulti.Unpack(keyChain)
4✔
181
}
182

183
func createArchiveFile(archiveDir string, fileName string) error {
8✔
184
        // Generate archive file path with timestamped name.
8✔
185
        baseFileName := filepath.Base(fileName)
8✔
186
        timestamp := time.Now().Format("2006-01-02-15-04-05")
8✔
187

8✔
188
        archiveFileName := fmt.Sprintf("%s-%s", baseFileName, timestamp)
8✔
189
        archiveFilePath := filepath.Join(archiveDir, archiveFileName)
8✔
190

8✔
191
        oldBackupFile, err := os.Open(fileName)
8✔
192
        if err != nil {
9✔
193
                return fmt.Errorf("unable to open old backup file: %w", err)
1✔
194
        }
1✔
195
        defer func() {
14✔
196
                if cerr := oldBackupFile.Close(); cerr != nil && err == nil {
7✔
NEW
197
                        err = fmt.Errorf("error closing old backup file: %w",
×
NEW
198
                                cerr)
×
NEW
199
                }
×
200
        }()
201

202
        // Ensure the archive directory exists. If it doesn't we create it.
203
        const archiveDirPermissions = 0o755
7✔
204
        err = os.MkdirAll(archiveDir, archiveDirPermissions)
7✔
205
        if err != nil {
7✔
NEW
206
                return fmt.Errorf("unable to create archive directory: %w", err)
×
NEW
207
        }
×
208

209
        // Create new archive file.
210
        archiveFile, err := os.Create(archiveFilePath)
7✔
211
        if err != nil {
8✔
212
                return fmt.Errorf("unable to create archive file: %w", err)
1✔
213
        }
1✔
214
        defer func() {
12✔
215
                if cerr := archiveFile.Close(); cerr != nil && err == nil {
6✔
NEW
216
                        err = fmt.Errorf("error closing archive file: %w", cerr)
×
NEW
217
                }
×
218
        }()
219

220
        // Copy contents of old backup to the newly created archive file.
221
        if _, err := io.Copy(archiveFile, oldBackupFile); err != nil {
6✔
NEW
222
                return fmt.Errorf("error copying to archive file: %w", err)
×
NEW
223
        }
×
224

225
        return nil
6✔
226
}
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