Branch data Line data Source code
1 : : #include "precizer.h"
2 : : #include <errno.h>
3 : :
4 : : #ifdef TESTITALL_TEST_HOOKS
5 : : /**
6 : : * @brief Check whether a path points to the large interruption test file.
7 : : */
8 : 2084 : static bool is_huge_interruption_target(const char *path)
9 : : {
10 [ - + ]: 2084 : if(path == NULL)
11 : : {
12 : 0 : return(false);
13 : : }
14 : :
15 : 2084 : const char *needle = "hugetestfile";
16 : 2084 : const size_t path_length = strlen(path);
17 : 2084 : const size_t needle_length = strlen(needle);
18 : :
19 [ - + ]: 2084 : if(path_length < needle_length)
20 : : {
21 : 0 : return(false);
22 : : }
23 : :
24 : 2084 : return(0 == strcmp(path + (path_length - needle_length),needle));
25 : : }
26 : :
27 : : /**
28 : : * @brief Generate a pseudo-random stop byte in the closed range [1, file_size].
29 : : */
30 : 4 : static uint64_t random_stop_byte(const uint64_t file_size)
31 : : {
32 [ - + ]: 4 : if(file_size == 0U)
33 : : {
34 : 0 : return(0U);
35 : : }
36 : :
37 : 4 : struct timespec now = {0};
38 : 4 : (void)clock_gettime(CLOCK_MONOTONIC,&now);
39 : :
40 : 4 : uint64_t seed = (uint64_t)now.tv_nsec;
41 : 4 : seed ^= ((uint64_t)now.tv_sec << 32);
42 : 4 : seed ^= (uint64_t)getpid();
43 : :
44 : 4 : return((seed % file_size) + 1U);
45 : : }
46 : : #endif
47 : :
48 : : /**
49 : : * @brief Calculate SHA512 cryptographic hash of a file, optionally resuming from offset.
50 : : *
51 : : * Reads file data starting from @p file->checksum_offset, updates @p file->mdContext,
52 : : * increments @p summary->total_hashed_bytes for each processed chunk, and accumulates
53 : : * per-call hashing elapsed time into @p summary->total_hashing_elapsed_ns.
54 : : *
55 : : * @param path File path (relative or absolute).
56 : : * @param path_size Length of @p path.
57 : : * @param file_buffer Read buffer descriptor.
58 : : * @param summary Traversal counters updated with hashed byte count and hashing time.
59 : : * @param file Per-file state object used as input and output. checksum_offset is the
60 : : * starting byte offset for resumption and is updated as bytes are
61 : : * hashed. sha512 receives the final digest. mdContext holds the
62 : : * incremental hashing state. read_error and read_errno describe
63 : : * read failures. wrong_file_type is set for non-seekable or
64 : : * otherwise unsupported file types
65 : : * @return SUCCESS or FAILURE.
66 : : */
67 : 2086 : Return sha512sum(
68 : : const char *path,
69 : : const size_t path_size,
70 : : memory *file_buffer,
71 : : TraversalSummary *summary,
72 : : File *file)
73 : : {
74 : : /* Status returned by this function through provide()
75 : : Default value assumes successful completion */
76 : 2086 : Return status = SUCCESS;
77 : :
78 [ - + ]: 2086 : if(file_buffer->length == 0)
79 : : {
80 : 0 : slog(ERROR,"Invalid buffer size: %ld bytes\n",file_buffer->length);
81 : 0 : provide(FAILURE);
82 : : }
83 : :
84 : 2086 : char *absolute_path = NULL;
85 : :
86 : 2086 : FILE *fileptr = fopen(path,"rb");
87 : :
88 [ + + ]: 2086 : if(fileptr == NULL)
89 : : {
90 : : // No read permission
91 [ - + ]: 1978 : if(errno == EACCES)
92 : : {
93 : : // Flag the read failure
94 : 0 : file->read_error = true;
95 : :
96 : : // Preserve errno before returning
97 : 0 : file->read_errno = errno;
98 : :
99 : 0 : provide(status);
100 : : }
101 : :
102 : 1978 : status = path_absolute_from_relative(&absolute_path,path,path_size);
103 : :
104 [ + - - + ]: 1978 : if(absolute_path == NULL || (TRIUMPH & status) == 0)
105 : : {
106 : 0 : slog(ERROR,"Can't constructs an absolute path from the base directory %s and a relative path %s\n",config->running_dir,path);
107 : :
108 [ # # ]: 0 : if(absolute_path != NULL)
109 : : {
110 : 0 : free(absolute_path);
111 : : }
112 : 0 : provide(status);
113 : : }
114 : :
115 : 1978 : fileptr = fopen(absolute_path,"rb");
116 : :
117 [ - + ]: 1978 : if(fileptr == NULL)
118 : : {
119 : : // No read permission
120 [ # # ]: 0 : if(errno == EACCES)
121 : : {
122 : 0 : file->read_error = true;
123 : :
124 : 0 : file->read_errno = errno;
125 : :
126 : 0 : free(absolute_path);
127 : 0 : provide(status);
128 : : }
129 : :
130 : 0 : slog(ERROR,"Can open the file using neither relative %s nor absolute %s path with errno: %d\n",path,absolute_path,errno);
131 : 0 : free(absolute_path);
132 : 0 : provide(FAILURE);
133 : : }
134 : : }
135 : :
136 : : // Move the file pointer checksum_offset bytes from the beginning of the file
137 [ - + ]: 2086 : if(fseek(fileptr,file->checksum_offset,SEEK_SET) != 0)
138 : : {
139 : : /*
140 : : * This looks like an unsupported file type.
141 : : * Doesn't need to return FAILURE status.
142 : : */
143 : 0 : file->wrong_file_type = true;
144 : 0 : free(absolute_path);
145 : 0 : fclose(fileptr);
146 : 0 : provide(status);
147 : : }
148 : :
149 : 2086 : bool loop_was_interrupted = false;
150 : 4172 : bool perform_file_hashing = config->dry_run == false
151 [ + + + + ]: 2086 : || config->dry_run_with_checksums == true;
152 : :
153 : : #ifdef TESTITALL_TEST_HOOKS
154 : : /*
155 : : * 0 means random-stop flow is disabled for this file.
156 : : * Non-zero means upper bound for random stop byte selection.
157 : : */
158 : 2086 : uint64_t random_stop_limit = 0U;
159 : : /* 0 means stop byte has not been selected yet. */
160 : 2086 : uint64_t random_stop_byte_value = 0U;
161 : : /* Separate state flag: do not overload stop-byte numeric value. */
162 : 2086 : bool random_stop_triggered = false;
163 : : #endif
164 : :
165 [ + + ]: 2086 : if(file->checksum_offset == 0)
166 : : {
167 : : // Fresh hashing pass: initialize the SHA512 state from scratch
168 [ - + ]: 2084 : if(sha512_init(&file->mdContext) == 1)
169 : : {
170 : 0 : slog(ERROR,"SHA512 initialization failed\n");
171 : 0 : free(absolute_path);
172 : 0 : fclose(fileptr);
173 : 0 : provide(FAILURE);
174 : : }
175 : : }
176 : :
177 : : #ifdef TESTITALL_TEST_HOOKS
178 : : /*
179 : : * Activate random interruption only for a fresh pass of hugetestfile.
180 : : * Resume path (checksum_offset > 0 at entry) must continue from saved state
181 : : * without selecting a new random stop point.
182 : : */
183 [ + + ]: 2086 : if(file->checksum_offset == 0
184 [ + + ]: 2084 : && is_huge_interruption_target(path) == true
185 [ + - ]: 4 : && file->stat.st_size > 0)
186 : : {
187 : 4 : random_stop_limit = (uint64_t)file->stat.st_size;
188 : :
189 : : /*
190 : : * Select interruption target before the first fread() call so
191 : : * the first chunk can be bounded and the stop point can land
192 : : * anywhere in [1, file_size].
193 : : */
194 : 4 : random_stop_byte_value = random_stop_byte(random_stop_limit);
195 : :
196 : : /* Defensive fallback: never allow a zero stop byte. */
197 [ - + ]: 4 : if(random_stop_byte_value == 0U)
198 : : {
199 : 0 : random_stop_byte_value = 1U;
200 : : }
201 : :
202 : : /*
203 : : * Keep the stop point strictly inside file data for multi-byte files.
204 : : * If random selection lands exactly at EOF, shift it one byte left.
205 : : * The guard keeps subtraction safe and avoids unsigned underflow.
206 : : */
207 [ + - - + ]: 4 : if(random_stop_limit > 1U && random_stop_byte_value >= random_stop_limit)
208 : : {
209 : 0 : random_stop_byte_value = random_stop_limit - 1U;
210 : : }
211 : : }
212 : : #endif
213 : :
214 [ + + ]: 2086 : if(perform_file_hashing == true)
215 : : {
216 : 2026 : long long int hashing_start_ns = cur_time_monotonic_ns();
217 : :
218 : 2026 : unsigned char *buffer = rawdata(file_buffer);
219 : :
220 : : while(true)
221 : 2027 : {
222 : : #ifdef TESTITALL_TEST_HOOKS
223 : : /*
224 : : * Trigger point 2 exactly once when selected stop byte is reached.
225 : : */
226 [ + + ]: 4053 : if(random_stop_limit > 0U
227 [ + + ]: 10 : && random_stop_triggered == false
228 [ + - ]: 8 : && random_stop_byte_value > 0U
229 [ + + ]: 8 : && (uint64_t)(file->checksum_offset) >= random_stop_byte_value)
230 : : {
231 : 4 : signal_wait_at_point(2U);
232 : 4 : random_stop_triggered = true;
233 : : }
234 : : #endif
235 : :
236 : : /* Interrupt the loop smoothly */
237 : : /* Interrupt when Ctrl+C */
238 : : #ifdef TESTITALL_TEST_HOOKS
239 : : /*
240 : : * Temporary guard: when random-stop mode is active for hugetestfile,
241 : : * do not break on global_interrupt_flag until point 2 has really
242 : : * happened. Otherwise interruption may fire too early and miss the
243 : : * controlled "interrupt at random byte" scenario.
244 : : */
245 : 4053 : bool delay_interrupt_for_random_stop = false;
246 : :
247 [ + + + + ]: 4053 : if(random_stop_limit > 0U && random_stop_triggered == false)
248 : : {
249 [ - + ]: 4 : if(random_stop_byte_value == 0U)
250 : : {
251 : : /* No stop byte yet: wait until at least one block is hashed. */
252 : 0 : delay_interrupt_for_random_stop = true;
253 : :
254 [ + - ]: 4 : } else if((uint64_t)(file->checksum_offset) < random_stop_byte_value){
255 : : /* Stop byte is known but not reached yet: keep hashing. */
256 : 4 : delay_interrupt_for_random_stop = true;
257 : : }
258 : : }
259 : : #endif
260 : :
261 [ + + ]: 4053 : if(global_interrupt_flag == true
262 : : #ifdef TESTITALL_TEST_HOOKS
263 [ + - ]: 2 : && delay_interrupt_for_random_stop == false
264 : : #endif
265 : : )
266 : : {
267 : 2 : loop_was_interrupted = true;
268 : 2 : break;
269 : : }
270 : :
271 : 4051 : size_t read_limit = file_buffer->length;
272 : :
273 : : #ifdef TESTITALL_TEST_HOOKS
274 : : /*
275 : : * Keep the read bounded so offset can stop exactly at the selected
276 : : * byte instead of jumping to EOF in a single large fread().
277 : : */
278 [ + + ]: 4051 : if(random_stop_limit > 0U
279 [ + + ]: 8 : && random_stop_triggered == false
280 [ + - ]: 4 : && random_stop_byte_value > 0U
281 [ + - ]: 4 : && (uint64_t)(file->checksum_offset) < random_stop_byte_value)
282 : : {
283 : 4 : const uint64_t bytes_left_to_stop = random_stop_byte_value - (uint64_t)(file->checksum_offset);
284 : :
285 [ + - ]: 4 : if(bytes_left_to_stop < (uint64_t)read_limit)
286 : : {
287 : 4 : read_limit = (size_t)bytes_left_to_stop;
288 : : }
289 : : }
290 : : #endif
291 : :
292 : 4051 : size_t len = fread(buffer,sizeof(unsigned char),read_limit,fileptr);
293 : :
294 [ + + ]: 4051 : if(len == 0)
295 : : {
296 [ + + ]: 2024 : if(ferror(fileptr))
297 : : {
298 : 1 : file->read_error = true;
299 : 1 : file->read_errno = errno;
300 : : }
301 : :
302 : 2024 : break;
303 : : }
304 : :
305 [ + - ]: 2027 : if(SUCCESS == status)
306 : : {
307 [ - + ]: 2027 : if(sha512_update(&file->mdContext,buffer,len) == 1)
308 : : {
309 : 0 : slog(ERROR,"SHA512 update failed\n");
310 : 0 : status = FAILURE;
311 : 0 : break;
312 : : }
313 : :
314 : 2027 : file->checksum_offset += (sqlite3_int64)len;
315 : 2027 : summary->total_hashed_bytes += len;
316 : : }
317 : : }
318 : :
319 : 2026 : long long int hashing_stop_ns = cur_time_monotonic_ns();
320 : :
321 : 2026 : long long int hashing_elapsed_ns = hashing_stop_ns - hashing_start_ns;
322 : :
323 [ - + ]: 2026 : if(hashing_elapsed_ns < 0LL)
324 : : {
325 : 0 : hashing_elapsed_ns = 0LL;
326 : : }
327 : :
328 : 2026 : summary->total_hashing_elapsed_ns += hashing_elapsed_ns;
329 : : }
330 : :
331 [ - + ]: 2086 : if(fclose(fileptr) != 0)
332 : : {
333 : 0 : slog(ERROR,"Error closing file %s\n",path);
334 : : }
335 : :
336 : 2086 : free(absolute_path);
337 : :
338 [ + - ]: 2086 : if(SUCCESS == status
339 [ + + ]: 2086 : && perform_file_hashing == true
340 [ + + ]: 2026 : && loop_was_interrupted == false)
341 : : {
342 : 2024 : file->checksum_offset = 0;
343 : :
344 [ - + ]: 2024 : if(sha512_final(&file->mdContext,file->sha512) == 1)
345 : : {
346 : 0 : slog(ERROR,"SHA512 finalization failed\n");
347 : 0 : status = FAILURE;
348 : : }
349 : : }
350 : :
351 : : #if 0
352 : : for(size_t i = 0; i < SHA512_DIGEST_LENGTH; i++)
353 : : {
354 : : printf("%02x",file->sha512[i]);
355 : : }
356 : : putchar('\n');
357 : : #endif
358 : :
359 : 2086 : provide(status);
360 : : }
|