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