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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 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✔
UNCOV
114
                return fmt.Errorf("unable to create temp file: %w", err)
×
UNCOV
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 {
9✔
189
                log.Debug("No old channel backup file to archive")
1✔
190
                return nil
1✔
191
        }
1✔
192

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

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

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

7✔
202
        oldBackupFile, err := os.Open(b.fileName)
7✔
203
        if err != nil {
7✔
204
                return fmt.Errorf("unable to open old channel backup file: "+
×
205
                        "%w", err)
×
206
        }
×
207
        defer func() {
14✔
208
                err := oldBackupFile.Close()
7✔
209
                if err != nil {
7✔
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
7✔
217
        err = os.MkdirAll(b.archiveDir, archiveDirPermissions)
7✔
218
        if err != nil {
7✔
219
                return fmt.Errorf("unable to create archive directory: %w", err)
×
220
        }
×
221

222
        // Create new archive file.
223
        archiveFile, err := os.Create(archiveFilePath)
7✔
224
        if err != nil {
8✔
225
                return fmt.Errorf("unable to create archive file: %w", err)
1✔
226
        }
1✔
227
        defer func() {
12✔
228
                err := archiveFile.Close()
6✔
229
                if err != nil {
6✔
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)
6✔
236
        if err != nil {
6✔
237
                return fmt.Errorf("unable to copy to archive file: %w", err)
×
238
        }
×
239
        err = archiveFile.Sync()
6✔
240
        if err != nil {
6✔
241
                return fmt.Errorf("unable to sync archive file: %w", err)
×
242
        }
×
243

244
        return nil
6✔
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