1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
package main
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"os"
"regexp"
"time"
)
// stateFileHeader : A structure to hold the header of the state file. It is statically aligned for amd64 architecture
// This comes from bareos repository file core/src/lib/bsys.cc:525 and core/src/lib/bsys.cc:652
type stateFileHeader struct {
ID [14]byte
_ int16
Version int32
_ int32
LastJobsAddr uint64
EndOfRecentJobResultsList uint64
Reserved [19]uint64
}
func (sfh stateFileHeader) String() string {
return fmt.Sprintf("ID: \"%s\", Version: %d, LastJobsAddr: %d, EndOfRecentJobResultsList: %d", sfh.ID[:len(sfh.ID)-2], sfh.Version, sfh.EndOfRecentJobResultsList, sfh.Reserved)
}
// jobEntry : A structure to hold a job result from the state file
// This comes from bareos repository file core/src/lib/recent_job_results_list.h:29 and file core/src/lib/recent_job_results_list.cc:44
type jobEntry struct {
Pad [16]byte
Errors int32
JobType int32
JobStatus int32
JobLevel int32
JobID uint32
VolSessionID uint32
VolSessionTime uint32
JobFiles uint32
JobBytes uint64
StartTime uint64
EndTime uint64
Job [maxNameLength]byte
}
func (je jobEntry) String() string {
var matches = jobNameRegex.FindSubmatchIndex(je.Job[:])
var jobNameLen int
if len(matches) >= 4 {
jobNameLen = matches[3]
}
return fmt.Sprintf("Errors: %d, JobType: %c, JobStatus: %c, JobLevel: %c, JobID: %d, VolSessionID: %d, VolSessionTime: %d, JobFiles: %d, JobBytes: %d, StartTime: %s, EndTime: %s, Job: %s",
je.Errors, je.JobType, je.JobStatus, je.JobLevel, je.JobID, je.VolSessionID, je.VolSessionTime, je.JobFiles, je.JobBytes, time.Unix(int64(je.StartTime), 0), time.Unix(int64(je.EndTime), 0), je.Job[:jobNameLen])
}
const (
// maxNameLength : the maximum length of a string, hard coded in bareos
maxNameLength = 128
// stateFileHeaderLength : the length of the state file header struct
stateFileHeaderLength = 14 + 2 + 4 + 4 + 8 + 8 + 19*8
// jobResultLength : the length of the job result struct
jobResultLength = 16 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 8 + 8 + 8 + maxNameLength
)
var jobNameRegex = regexp.MustCompilePOSIX(`^([-A-Za-z0-9_]+)\.[0-9]{4}-[0-9]{2}-[0-9]{2}.*`)
// readNextBytes : Reads the next "number" bytes from a "file", returns the number of bytes actually read as well as the bytes read
func readNextBytes(file *os.File, number int) (n int, bytes []byte, err error) {
bytes = make([]byte, number)
n, err = file.Read(bytes)
if err != nil {
return 0, nil, fmt.Errorf("file.Read failed in %s : %s", stateFile, err)
}
return
}
func parseStateFile() (successfulJobs jobs, errorJobs jobs, err error) {
var (
n int
stateFileHandle *os.File
data []byte
buffer *bytes.Buffer
numberOfJobs uint32
matches []int
)
// Open the state file
stateFileHandle, err = os.Open(stateFile)
if err != nil {
return nil, nil, fmt.Errorf("INFO Couldn't open state file : %s", err)
}
defer stateFileHandle.Close()
// Parsing the state file header
var header stateFileHeader
n, data, err = readNextBytes(stateFileHandle, stateFileHeaderLength)
if err != nil {
return nil, nil, fmt.Errorf("INFO Corrupted state file : %s", err)
}
if n != stateFileHeaderLength {
return nil, nil, fmt.Errorf("INFO Corrupted state file : invalid header length in %s", stateFile)
}
buffer = bytes.NewBuffer(data)
err = binary.Read(buffer, binary.LittleEndian, &header)
if err != nil {
return nil, nil, fmt.Errorf("INFO Corrupted state file : binary.Read failed on header in %s : %s", stateFile, err)
}
if verbose {
log.Printf("Parsed header: %+s\n", header)
}
if id := string(header.ID[:len(header.ID)-1]); id != "Bareos State\n" && id != "Bacula State\n" {
return nil, nil, fmt.Errorf("INFO Corrupted state file : Not a bareos or bacula state file %s", stateFile)
}
if header.Version != 4 {
return nil, nil, fmt.Errorf("INFO Invalid state file : This script only supports bareos state file version 4, got %d", header.Version)
}
if header.LastJobsAddr == 0 {
return nil, nil, fmt.Errorf("INFO No jobs exist in the state file")
}
// We seek to the jobs position in the state file
stateFileHandle.Seek(int64(header.LastJobsAddr), 0)
// We read how many jobs there are in the state file
n, data, err = readNextBytes(stateFileHandle, 4)
if err != nil {
return nil, nil, fmt.Errorf("INFO Corrupted state file : %s", err)
}
if n != 4 {
return nil, nil, fmt.Errorf("INFO Corrupted state file : invalid numberOfJobs read length in %s", stateFile)
}
buffer = bytes.NewBuffer(data)
err = binary.Read(buffer, binary.LittleEndian, &numberOfJobs)
if err != nil {
return nil, nil, fmt.Errorf("INFO Corrupted state file : binary.Read failed on numberOfJobs in %s : %s", stateFile, err)
}
if verbose {
log.Printf("%d jobs found in state file\n", numberOfJobs)
}
// We parse the job entries
successfulJobs = make(map[string]uint64)
errorJobs = make(map[string]uint64)
for ; numberOfJobs > 0; numberOfJobs-- {
var (
jobResult jobEntry
jobName string
)
n, data, err = readNextBytes(stateFileHandle, jobResultLength)
if err != nil {
return nil, nil, fmt.Errorf("INFO Corrupted state file : %s", err)
}
if n != jobResultLength {
return nil, nil, fmt.Errorf("INFO Corrupted state file : invalid job entry in %s", stateFile)
}
buffer = bytes.NewBuffer(data)
err = binary.Read(buffer, binary.LittleEndian, &jobResult)
if err != nil {
return nil, nil, fmt.Errorf("INFO Corrupted state file : binary.Read failed on job entry in %s : %s", stateFile, err)
}
matches = jobNameRegex.FindSubmatchIndex(jobResult.Job[:])
if len(matches) >= 4 {
jobName = string(jobResult.Job[:matches[3]])
} else {
return nil, nil, fmt.Errorf("INFO Couldn't parse job name, this shouldn't happen : %s", jobResult.Job[:])
}
if verbose {
log.Printf("Parsed job entry: %s\n", jobResult)
}
// If the job is of type backup (B == ascii 66)
if jobResult.JobType == 66 {
var (
successExists bool
errorExists bool
currentSuccess uint64
currentError uint64
)
currentSuccess, successExists = successfulJobs[jobName]
currentError, errorExists = errorJobs[jobName]
// If the job is of status success (T == ascii 84)
if jobResult.JobStatus == 84 {
// if there is an older entry in errorJobs we delete it
if errorExists && jobResult.StartTime > currentError {
delete(errorJobs, jobName)
}
// if there are no entries more recent in successfulJobs we add this one
if !successExists || successExists && jobResult.StartTime > currentSuccess {
successfulJobs[jobName] = jobResult.StartTime
}
} else {
if !errorExists || jobResult.StartTime > currentError {
errorJobs[jobName] = jobResult.StartTime
}
}
}
}
return
}
|