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

mendersoftware / mender-flash / 1897880619

30 Jun 2025 12:35PM UTC coverage: 77.67% (-14.2%) from 91.892%
1897880619

push

gitlab-ci

web-flow
Merge pull request #27 from vpodzime/mini_c

C rewrite -- attempt++

160 of 206 new or added lines in 1 file covered. (77.67%)

160 of 206 relevant lines covered (77.67%)

52.52 hits per line

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

77.67
/main.c
1
// Copyright 2025 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#define _GNU_SOURCE          /* needed for splice() */
16
#define _FILE_OFFSET_BITS 64 /* needed for working with files >4GiB */
17

18
#include <errno.h>
19
#include <fcntl.h>
20
#include <getopt.h>
21
#include <mtd/ubi-user.h>
22
#include <stdbool.h>
23
#include <stdlib.h>
24
#include <stdint.h>
25
#include <stdio.h>
26
#include <string.h>
27
#include <sys/ioctl.h>
28
#include <sys/sendfile.h>
29
#include <sys/stat.h>
30
#include <sys/sysmacros.h>
31
#include <unistd.h>
32

33
#define BLOCK_SIZE (1024 * 1024ULL) /* 1 MiB */
34

35
#define MIN(X, Y) ((X < Y) ? X : Y)
36

37
/**
38
 * Choose the smaller of the sizes X and Y and limit it to SIZE_MAX if needed.
39
 * @note off_t is the right type for file offsets that can be greater than
40
 *       size_t, but also, SIZE_MAX may not fit into off_t because both types
41
 *       can be 64 bits and off_t is signed. I/O syscalls that manipulate data
42
 *       (read, write,...) use size_t for their arguments.
43
 */
44
#define MIN_SIZE(X, Y) ((size_t) MIN(((uintmax_t) SIZE_MAX), (MIN(((uintmax_t) X), ((uintmax_t) Y)))))
45

46
static struct option long_options[] = {
47
    {"help", no_argument, 0, 'h'},
48
    {"write-everything", no_argument, 0, 'w'},
49
    {"input-size", required_argument, 0, 's'},
50
    {"fsync-interval", required_argument, 0, 'f'},
51
    {"input", required_argument, 0, 'i'},
52
    {"output", required_argument, 0, 'o'},
53
    {0, 0, 0, 0}};
54

55
void PrintHelp() {
6✔
56
    fputs(
6✔
57
          "Usage:\n"
58
          "  mender-flash [-h|--help] [-w|--write-everything] [-s|--input-size <INPUT_SIZE>] [-f|--fsync-interval <FSYNC_INTERVAL>] -i|--input <INPUT_PATH> -o|--output <OUTPUT_PATH>\n",
59
          stderr);
60
}
6✔
61

62
typedef ssize_t (*io_fn_t)(int, void*, size_t);
63

64
struct Stats {
65
    size_t blocks_written;
66
    size_t blocks_omitted;
67
    uint64_t bytes_written;
68
    uint64_t bytes_omitted;
69
    uint64_t total_bytes;
70
};
71

72
ssize_t buf_io(io_fn_t io_fn, int fd, unsigned char *buf, size_t len) {
278✔
73
    size_t rem = len;
278✔
74
    ssize_t n_done;
75
    do {
76
        n_done = io_fn(fd, buf + (len - rem), rem);
668✔
77
        if (n_done > 0) {
668✔
78
            rem -= n_done;
592✔
79
        }
80
        else if ((n_done == -1) && (errno == EINTR)) {
76✔
NEW
81
            continue;
×
82
        }
83
    } while ((n_done > 0) && (rem > 0) && (len > 0));
668✔
84

85
    if (n_done < 0) {
278✔
86
        return n_done;
2✔
87
    } else {
88
        return (len - rem);
276✔
89
    }
90
}
91

92
bool shovel_data(int in_fd, int out_fd, off_t len, bool write_optimized, size_t fsync_interval,
56✔
93
                 struct Stats *stats, int *error) {
94
    unsigned char buffer[BLOCK_SIZE];
95
    size_t n_unsynced = 0;
56✔
96
    while (len > 0) {
152✔
97
        ssize_t n_read = buf_io((io_fn_t)read, in_fd, buffer, MIN_SIZE(BLOCK_SIZE, len));
100✔
98
        if (n_read < 0) {
100✔
99
            fprintf(stderr, "Failed to read data: %m\n");
2✔
100
            *error = errno;
2✔
101
            return false;
2✔
102
        }
103
        if ((n_read == 0) && (len > 0)) {
98✔
104
            fprintf(stderr, "Unexpected end of input!\n");
2✔
105
            return false;
2✔
106
        }
107
        if (write_optimized) {
96✔
108
            unsigned char out_fd_buffer[BLOCK_SIZE];
109
            ssize_t out_fd_n_read = buf_io((io_fn_t)read, out_fd, out_fd_buffer, MIN_SIZE(BLOCK_SIZE, len));
96✔
110
            if (out_fd_n_read < 0) {
96✔
NEW
111
                fprintf(stderr, "Failed to read data from the target: %m\n");
×
NEW
112
                *error = errno;
×
NEW
113
                return false;
×
114
            }
115
            if ((n_read == out_fd_n_read) &&
96✔
116
                (memcmp(buffer, out_fd_buffer, n_read) == 0)) {
24✔
117
                stats->blocks_omitted++;
14✔
118
                stats->total_bytes += n_read;
14✔
119
                len -= n_read;
14✔
120
                continue;
14✔
121
            } else {
122
                if (lseek(out_fd, -out_fd_n_read, SEEK_CUR) == -1) {
82✔
NEW
123
                    fprintf(stderr, "Failed to seek on the target: %m\n");
×
NEW
124
                    *error = errno;
×
NEW
125
                    return false;
×
126
                }
127
            }
128
        }
129
        ssize_t n_written = buf_io((io_fn_t)write, out_fd, buffer, n_read);
82✔
130
        if (n_written != n_read) {
82✔
NEW
131
            fprintf(stderr, "Failed to write data: %m\n");
×
NEW
132
            *error = errno;
×
NEW
133
            return false;
×
134
        }
135
        stats->total_bytes += n_read;
82✔
136
        stats->blocks_written++;
82✔
137
        stats->bytes_written += n_written;
82✔
138
        if (fsync_interval != 0) {
82✔
139
            n_unsynced += n_written;
70✔
140
            if (n_unsynced >= fsync_interval) {
70✔
141
                if (fsync(out_fd) == -1) {
62✔
NEW
142
                    fprintf(stderr, "warning: Failed to fsync data to target: %m\n");
×
143
                }
144
                n_unsynced = 0;
62✔
145
            }
146
        }
147
        len -= n_read;
82✔
148
    }
149

150
    if ((fsync_interval != 0) && (n_unsynced >= fsync_interval)) {
52✔
NEW
151
        if (fsync(out_fd) == -1) {
×
NEW
152
            fprintf(stderr, "warning: Failed to fsync data to target: %m\n");
×
153
        }
154
    }
155
    return true;
52✔
156
}
157

158
/**
159
 * @return 1 if true, 0 if false, -1 in case of an error
160
 */
NEW
161
int is_ubi_device(char *path) {
×
162
    char *sysfs_ubi_dir;
NEW
163
    if (asprintf(&sysfs_ubi_dir, "/sys/class/ubi/%s", basename(path)) == -1) {
×
NEW
164
        fprintf(stderr, "Cannot check if output '%s' is a UBI device: %m\n", path);
×
NEW
165
        return -1;
×
166
    }
167

168
    struct stat sysfs_dir_stat;
169
    int ret;
NEW
170
    if (stat(sysfs_ubi_dir, &sysfs_dir_stat) == 0) {
×
NEW
171
        ret = 1;
×
NEW
172
    } else if (errno == ENOENT) {
×
NEW
173
        ret = 0;
×
174
    } else {
NEW
175
        fprintf(stderr, "Failed to check if output '%s' is a UBI device: %m\n", path);
×
NEW
176
        ret = -1;
×
177
    }
NEW
178
    free(sysfs_ubi_dir);
×
NEW
179
    return ret;
×
180
}
181

182
#ifdef __linux__
183
/* Same signature as sendfile() so that we can treat the same (see comment about
184
 * splice() and sendfile() below). */
185
ssize_t splice_sendfile(int out_fd, int in_fd, __attribute__(( unused )) off_t *offset, size_t count) {
96✔
186
    return splice(in_fd, 0, out_fd, 0, count, 0);
96✔
187
}
188
#endif  /* __linux__ */
189

190
int main(int argc, char *argv[]) {
82✔
191
    char *input_path = NULL;
82✔
192
    char *output_path = NULL;
82✔
193
    uint64_t volume_size = 0;
82✔
194
    bool write_optimized = true;
82✔
195
    off_t fsync_interval = BLOCK_SIZE;
82✔
196

197
    int option_index = 0;
82✔
198
    int c = getopt_long(argc, argv, "hws:f:i:o:", long_options, &option_index);
82✔
199
    while (c != -1) {
272✔
200
        switch (c) {
198✔
201
        case 'h':
2✔
202
            PrintHelp();
2✔
203
            return 0;
2✔
204

205
        case 'i':
72✔
206
            input_path = optarg;
72✔
207
            break;
72✔
208

209
        case 'o':
74✔
210
            output_path = optarg;
74✔
211
            break;
74✔
212

213
        case 's': {
30✔
214
            char *end = optarg;
30✔
215
            long long ret = strtoll(optarg, &end, 10);
30✔
216
            if ((ret == 0) || (*end != '\0')) {
30✔
217
                fprintf(stderr, "Invalid input size given: %s\n", optarg);
2✔
218
                return EXIT_FAILURE;
2✔
219
            } else {
220
                volume_size = ret;
28✔
221
            }
222
            break;
28✔
223
        }
224

225
        case 'f': {
8✔
226
            char *end = optarg;
8✔
227
            unsigned long long ret = strtoull(optarg, &end, 10);
8✔
228
            if (((ret == 0) && (strcmp(optarg, "0") != 0)) || (*end != '\0')) {
8✔
229
                fprintf(stderr, "Invalid fsync interval given: %s\n", optarg);
2✔
230
                return EXIT_FAILURE;
2✔
231
            } else {
232
                fsync_interval = ret;
6✔
233
            }
234
            break;
6✔
235
        }
236

237
        case 'w':
10✔
238
            write_optimized = false;
10✔
239
            break;
10✔
240

241
        default:
2✔
242
            PrintHelp();
2✔
243
            return EXIT_FAILURE;
2✔
244
        }
245
        c = getopt_long(argc, argv, "hws:i:o:", long_options, &option_index);
190✔
246
    }
247

248
    if ((input_path == NULL) || (output_path == NULL)) {
74✔
249
        fprintf(stderr, "Wrong input parameters!\n");
2✔
250
        PrintHelp();
2✔
251
        return EXIT_FAILURE;
2✔
252
    }
253

254
    int in_fd;
255
    int out_fd;
256
    if (strcmp(input_path, "-") == 0) {
72✔
257
        in_fd = STDIN_FILENO;
24✔
258
    } else {
259
        in_fd = open(input_path, O_RDONLY);
48✔
260
        if (in_fd == -1) {
48✔
261
            fprintf(stderr, "Failed to open '%s' for reading: %m\n", input_path);
2✔
262
            return EXIT_FAILURE;
2✔
263
        }
264
    }
265

266
    if (write_optimized) {
70✔
267
        out_fd = open(output_path, O_CREAT | O_RDWR, 0600);
60✔
268
    } else {
269
        out_fd = open(output_path, O_CREAT | O_WRONLY, 0600);
10✔
270
    }
271
    if (out_fd == -1) {
70✔
272
        fprintf(stderr, "Failed to open '%s' for writing: %m\n", output_path);
2✔
273
        close(in_fd);
2✔
274
        return EXIT_FAILURE;
2✔
275
    }
276

277
    struct stat in_fd_stat;
278
    if (fstat(in_fd, &in_fd_stat) == -1) {
68✔
NEW
279
        close(in_fd);
×
NEW
280
        close(out_fd);
×
NEW
281
        fprintf(stderr, "Failed to stat() input '%s': %m\n", input_path);
×
NEW
282
        return EXIT_FAILURE;
×
283
    }
284

285
    struct stat out_fd_stat;
286
    if (fstat(out_fd, &out_fd_stat) == -1) {
68✔
NEW
287
        close(in_fd);
×
NEW
288
        close(out_fd);
×
NEW
289
        fprintf(stderr, "Failed to stat() output '%s': %m\n", output_path);
×
NEW
290
        return EXIT_FAILURE;
×
291
    }
292
    if (S_ISCHR(out_fd_stat.st_mode)) {
68✔
NEW
293
        int ret = is_ubi_device(output_path);
×
NEW
294
        if (ret == -1) {
×
295
            /* error already logged */
NEW
296
            close(in_fd);
×
NEW
297
            close(out_fd);
×
NEW
298
            return EXIT_FAILURE;
×
NEW
299
        } else if (ret == 1) {
×
NEW
300
            ret = ioctl(out_fd, UBI_IOCVOLUP, &volume_size);
×
NEW
301
            if (ret == -1) {
×
NEW
302
                close(in_fd);
×
NEW
303
                close(out_fd);
×
NEW
304
                fprintf(stderr, "Failed to setup UBI volume '%s': %m\n", output_path);
×
NEW
305
                return EXIT_FAILURE;
×
306
            }
NEW
307
            write_optimized = false;
×
308
        }
309
    }
310

311
    size_t len;
312
    if (volume_size != 0) {
68✔
313
        len = volume_size;
28✔
314
    } else {
315
        if (in_fd_stat.st_size == 0) {
40✔
316
            fprintf(stderr, "Input size not specified and cannot be determined from stat()\n");
2✔
317
            close(in_fd);
2✔
318
            close(out_fd);
2✔
319
            return EXIT_FAILURE;
2✔
320
        } else {
321
            len = in_fd_stat.st_size;
38✔
322
        }
323
    }
324

325
    struct Stats stats = {0};
66✔
326
    bool success = false;
66✔
327
    int error = 0;
66✔
328

329
#ifndef __linux__
330
    /* Nothing better available for non-Linux platforms (for now). */
331
    success = shovel_data(in_fd, out_fd, len, write_optimized, fsync_interval, &stats, &error);
332
#else  /* __linux__ */
333
    /* The fancy syscalls below don't support write-optimized approach or
334
       syncing so we cannot use them for that. */
335
    if (write_optimized) {
66✔
336
        success = shovel_data(in_fd, out_fd, len, write_optimized, fsync_interval, &stats, &error);
56✔
337
    } else {
338
        /***
339
            On Linux the splice() and sendfile() syscalls can be useful for us (see
340
            their descriptions taken from the respective man pages below), on other
341
            operating systems there might be functions with the same names, but
342
            potentially doing something completely different.
343

344
            splice() moves  data  between two file descriptors without copying be‐
345
            tween kernel address space and user address space.  It transfers up  to
346
            len bytes of data from the file descriptor fd_in to the file descriptor
347
            fd_out, where one of the file descriptors must refer to a pipe.
348

349
            sendfile()  copies  data  between one file descriptor and another.  Be‐
350
            cause this copying is done within the kernel, sendfile() is more  effi‐
351
            cient than the combination of read(2) and write(2), which would require
352
            transferring data to and from user space.
353
            The   in_fd   argument   must  correspond  to  a  file  which  supports
354
            mmap(2)-like operations (i.e., it cannot be a socket or a pipe).
355
        ***/
356
        ssize_t (*sendfile_fn)(int out_fd, int in_fd, off_t *offset, size_t count);
357
        if (S_ISFIFO(in_fd_stat.st_mode)) {
10✔
358
            sendfile_fn = splice_sendfile;
4✔
359
        } else {
360
            sendfile_fn = sendfile;
6✔
361
        }
362

363
        if (fsync_interval == 0) {
10✔
364
            fsync_interval = len;
2✔
365
        }
366
        ssize_t ret;
367
        off_t n_unsynced = 0;
10✔
368
        do {
369
            ret = sendfile_fn(out_fd, in_fd, 0, MIN_SIZE(len, fsync_interval));
104✔
370
            if (ret > 0) {
104✔
371
                len -= ret;
104✔
372
                stats.total_bytes += ret;
104✔
373
                n_unsynced += ret;
104✔
374
                if (n_unsynced >= fsync_interval) {
104✔
375
                    fsync(out_fd);
14✔
376
                    n_unsynced = 0;
14✔
377
                }
378
            }
379
        } while ((ret > 0) && (len > 0));
104✔
380
        success = ((ret == 0) || ((ret > 0) && (len == 0)));
10✔
381
        error = errno;
10✔
382
    }
383
#endif  /* __linux__ */
384

385
    close(in_fd);
66✔
386
    /* close() doesn't guarantee a sync */
387
    fsync(out_fd);
66✔
388
    close(out_fd);
66✔
389

390
    if (!success) {
66✔
391
        if (error != 0) {
4✔
392
            fprintf(stderr, "Failed to copy data: %s\n", strerror(error));
2✔
393
            printf("Total bytes written: %ju\n", (intmax_t) stats.total_bytes);
2✔
394
        } else {
395
            fprintf(stderr, "Failed to copy data\n");
2✔
396
            printf("Total bytes written: %ju\n", (intmax_t) stats.total_bytes);
2✔
397
        }
398
        return EXIT_FAILURE;
4✔
399
    } else {
400
        if (write_optimized) {
62✔
401
            puts("================ STATISTICS ================");
52✔
402
            printf("Blocks written: %10zu\n", stats.blocks_written);
52✔
403
            printf("Blocks omitted: %10zu\n", stats.blocks_omitted);
52✔
404
            printf("Bytes written: %11ju\n", (intmax_t) stats.bytes_written);
52✔
405
            printf("Total bytes: %13ju\n", (intmax_t) stats.total_bytes);
52✔
406
            puts("============================================");
52✔
407
        } else {
408
            printf("Total bytes written: %ju\n", (intmax_t) stats.total_bytes);
10✔
409
        }
410
    }
411

412
    return 0;
62✔
413
}
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