caam-blob-example: add example demostrating CAAM blob usage
CAAM blobs can be used to secure data with the OTPMK. More information is available in the online documentation. Signed-off-by: Jose Diaz de Grenu <Jose.DiazdeGrenu@digi.com>
This commit is contained in:
parent
41289be92e
commit
ab38a1e6c9
|
|
@ -0,0 +1,37 @@
|
|||
#
|
||||
# Copyright (c) 2019 Digi International Inc.
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for any
|
||||
# purpose with or without fee is hereby granted, provided that the above
|
||||
# copyright notice and this permission notice appear in all copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
#
|
||||
|
||||
PROGRAM := caam-blob-example
|
||||
|
||||
CFLAGS += -Wall
|
||||
|
||||
all: $(PROGRAM)
|
||||
|
||||
SRCS := $(wildcard *.c)
|
||||
OBJS := $(SRCS:.c=.o)
|
||||
|
||||
$(PROGRAM): $(OBJS)
|
||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
|
||||
|
||||
.PHONY: install
|
||||
install: $(PROGRAM)
|
||||
install -d $(DESTDIR)/usr/bin
|
||||
install -m 0755 $(PROGRAM) $(DESTDIR)/usr/bin/
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
-rm -f *.o $(PROGRAM)
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
CAAM blob Example Application
|
||||
===================================
|
||||
|
||||
This example application shows how to encapsulate/decapsulate data to/from CAAM blobs.
|
||||
CAAM blobs are a way to protect sensitive data by encrypting their contents.
|
||||
You can think of CAAM blobs as data encrypted with an internal unreadable device-specific key which is protected by the hardware.
|
||||
Data encapsulated in a CAAM blob can only be decapsulated by the device that created it.
|
||||
When creating CAAM blobs, the input data size is limited to 1048527 bytes.
|
||||
Note that CAAM blobs are slightly bigger than the input data (48 bytes bigger).
|
||||
|
||||
A key modifier may be used to further differentiate the key used in a particular blob.
|
||||
|
||||
WARNING: CAAM blobs are only secure if created in a secure boot enable device.
|
||||
|
||||
For more information about CAAM blobs, see 'Secure Storage' in the [online documentation](https://www.digi.com/resources/documentation/digidocs/embedded/).
|
||||
|
||||
Note: This application is only supported when using a platform and Digi Embedded Yocto version that support Trustfence.
|
||||
|
||||
Running the application
|
||||
-----------------------
|
||||
|
||||
The following example shows how to encrypt and decrypt and file in place:
|
||||
|
||||
```
|
||||
# echo "Test file" > test.txt
|
||||
# caam-blob-example -e test.txt
|
||||
# hexdump -C test.txt
|
||||
00000000 01 ee 30 e3 31 6b 72 94 94 06 62 e2 ef 17 4e 05 |..0.1kr...b...N.|
|
||||
00000010 34 c3 7b 96 58 35 ad b7 a2 89 b6 bc eb eb 81 39 |4.{.X5.........9|
|
||||
00000020 3c b2 e7 d7 2d 93 7d ff 8b a8 80 bd 55 e9 70 cf |<...-.}.....U.p.|
|
||||
00000030 20 51 9f 15 9e c5 aa 68 b3 90 | Q.....h..|
|
||||
0000003a
|
||||
# caam-blob-example -d test.txt
|
||||
# hexdump -C test.txt
|
||||
00000000 54 65 73 74 20 66 69 6c 65 0a |Test file.|
|
||||
0000000a
|
||||
```
|
||||
|
||||
You can also use the "-m" parameter to specify a key modifier. In that case, you also need the same key modifier to decrypt the data.
|
||||
The key modifier is a 16 byte value encoded as 32 hexadecimal characters.
|
||||
If you want to write the CAAM blob to a different file, add another positional argument.
|
||||
|
||||
```
|
||||
# caam-blob-example -e -m ff0102030405060708090a0b0c0d0e0f test.txt encrypted.bin
|
||||
# caam-blob-example -d encrypted.bin decrypted.txt
|
||||
[ERROR] could not decrypt data.
|
||||
[ERROR] Decryption failed
|
||||
# caam-blob-example -d -m ff0102030405060708090a0b0c0d0e0f encrypted.bin decrypted.txt
|
||||
# cat decrypted.txt
|
||||
Test file
|
||||
```
|
||||
|
||||
Compiling the application
|
||||
-------------------------
|
||||
This demo can be compiled using a Digi Embedded Yocto based toolchain. Make
|
||||
sure to source the corresponding toolchain of the platform you are using, e.g:
|
||||
|
||||
```
|
||||
$> . <DEY-toolchain-path>/environment-setup-cortexa7hf-vfp-neon-dey-linux-gnueabi
|
||||
$> make
|
||||
```
|
||||
|
||||
More information about [Digi Embedded Yocto](https://github.com/digi-embedded/meta-digi).
|
||||
|
||||
License
|
||||
-------
|
||||
Copyright 2019, Digi International Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appears in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* CAAM public-level include definitions for the key blob
|
||||
*
|
||||
* Copyright (C) 2015 Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#ifndef CAAM_KEYBLOB_H
|
||||
#define CAAM_KEYBLOB_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct caam_kb_data {
|
||||
char *rawkey;
|
||||
size_t rawkey_len;
|
||||
char *keyblob;
|
||||
size_t keyblob_len;
|
||||
char *keymod;
|
||||
size_t keymod_len;
|
||||
};
|
||||
|
||||
#define CAAM_KB_MAGIC 'I'
|
||||
|
||||
/**
|
||||
* DOC: CAAM_KB_ENCRYPT - generate a key blob from raw key
|
||||
*
|
||||
* Takes an caam_kb_data struct and returns it with the key blob
|
||||
*/
|
||||
#define CAAM_KB_ENCRYPT _IOWR(CAAM_KB_MAGIC, 0, struct caam_kb_data)
|
||||
|
||||
/**
|
||||
* DOC: CAAM_KB_DECRYPT - get keys from a key blob
|
||||
*
|
||||
* Takes an caam_kb_data struct and returns it with the raw key.
|
||||
*/
|
||||
#define CAAM_KB_DECRYPT _IOWR(CAAM_KB_MAGIC, 1, struct caam_kb_data)
|
||||
|
||||
#ifndef GENMEM_KEYMOD_LEN
|
||||
#define GENMEM_KEYMOD_LEN 16
|
||||
#endif
|
||||
|
||||
#endif /* CAAM_KEYBLOB_H */
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* caam_ops.c
|
||||
*
|
||||
* Copyright (C) 2019 by Digi International Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* Description: CAAM blob encryption/decryption functions
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "caam_ops.h"
|
||||
|
||||
int caamblob_encrypt(const uint8_t *data,
|
||||
size_t size,
|
||||
const uint8_t keymod[KEY_MODIFIER_SIZE],
|
||||
uint8_t *encrypted_data)
|
||||
{
|
||||
const struct caam_kb_data kb_data = {
|
||||
.rawkey = (char *) data,
|
||||
.rawkey_len = size,
|
||||
.keyblob = (char *) encrypted_data,
|
||||
.keyblob_len = size + BLOB_OVERHEAD,
|
||||
.keymod = (char *) keymod,
|
||||
.keymod_len = KEY_MODIFIER_SIZE
|
||||
};
|
||||
int fd;
|
||||
int ret = 0;
|
||||
|
||||
if (size <= 0)
|
||||
return -1;
|
||||
|
||||
if ((fd = open(CAAM_KEY_DEV, O_RDWR)) < 0) {
|
||||
perror("[ERROR] could not open CAAM keyblob device node:");
|
||||
return fd;
|
||||
}
|
||||
|
||||
if (ioctl(fd, CAAM_KB_ENCRYPT, &kb_data)) {
|
||||
fprintf(stderr, "[ERROR] could not encrypt data.\n");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int caamblob_decrypt(const uint8_t *encrypted_data,
|
||||
size_t encrypted_size,
|
||||
const uint8_t keymod[KEY_MODIFIER_SIZE],
|
||||
uint8_t *data)
|
||||
{
|
||||
const struct caam_kb_data kb_data = {
|
||||
.rawkey = (char *) data,
|
||||
.rawkey_len = encrypted_size - BLOB_OVERHEAD,
|
||||
.keyblob = (char *) encrypted_data,
|
||||
.keyblob_len = encrypted_size,
|
||||
.keymod = (char *) keymod,
|
||||
.keymod_len = KEY_MODIFIER_SIZE
|
||||
};
|
||||
int fd;
|
||||
int ret = 0;
|
||||
|
||||
if (encrypted_size <= BLOB_OVERHEAD) {
|
||||
fprintf(stderr, "[ERROR] encrypted data is too small (%zu bytes < blob overhead (%d bytes)\n",
|
||||
encrypted_size, BLOB_OVERHEAD);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((fd = open(CAAM_KEY_DEV, O_RDWR)) < 0) {
|
||||
perror("[ERROR] could not open CAAM keyblob device node");
|
||||
return fd;
|
||||
}
|
||||
|
||||
if (ioctl(fd, CAAM_KB_DECRYPT, &kb_data)) {
|
||||
fprintf(stderr, "[ERROR] could not decrypt data.\n");
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* caam_ops.h
|
||||
*
|
||||
* Copyright (C) 2019 by Digi International Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* Description: CAAM blob encryption/decryption functions
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CAAM_OPS_H
|
||||
#define CAAM_OPS_H
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stropts.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "caam_keyblob.h"
|
||||
|
||||
#define CAAM_KEY_DEV "/dev/caam_kb"
|
||||
|
||||
/* Define space required for BKEK (32 bytes) + MAC tag (16 bytes) storage in any blob */
|
||||
#define BLOB_OVERHEAD (32 + 16)
|
||||
|
||||
/* Key modifier: 16 bytes for a general memory blob (see SRM 5.8.4.7.1) */
|
||||
#define KEY_MODIFIER_SIZE 16
|
||||
|
||||
/*
|
||||
* Testing shows that input sizes bigger than this value usually fail.
|
||||
*/
|
||||
#define BLOB_MAX_INPUT_SIZE (1024 * 1024 - BLOB_OVERHEAD)
|
||||
|
||||
int caamblob_encrypt(const uint8_t *data,
|
||||
size_t size,
|
||||
const uint8_t keymod[KEY_MODIFIER_SIZE],
|
||||
uint8_t *encrypted_data);
|
||||
|
||||
int caamblob_decrypt(const uint8_t *encrypted_data,
|
||||
size_t encrypted_size,
|
||||
const uint8_t keymod[KEY_MODIFIER_SIZE],
|
||||
uint8_t *data);
|
||||
|
||||
#endif /* CAAM_OPS_H */
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* main.c
|
||||
*
|
||||
* Copyright (C) 2019 by Digi International Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* Description: CAAM blob encryption/decryption example application
|
||||
*
|
||||
*/
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "caam_ops.h"
|
||||
|
||||
enum caam_op {
|
||||
ENCRYPT = 1 << 0,
|
||||
DECRYPT = 1 << 1
|
||||
};
|
||||
|
||||
static const char *program_name = "";
|
||||
static enum caam_op op;
|
||||
static const char *input_file;
|
||||
static const char *output_file;
|
||||
static const char *key_modifier_string;
|
||||
|
||||
static void usage_and_exit(int exitval)
|
||||
{
|
||||
fprintf(stdout,
|
||||
"Example application for using CAAM blobs.\n"
|
||||
"Copyright(c) Digi International Inc.\n"
|
||||
"\n"
|
||||
"Usage: %s (-e | -d) [options] input_file [output_file]\n"
|
||||
"\n"
|
||||
" -e --encrypt Read plain data and write a CAAM blob\n"
|
||||
" -d --decrypt Read a CAAM blob, validate it and write the data it contains\n"
|
||||
"\n"
|
||||
" -m --modifier=<KM> Set key modifier\n"
|
||||
" -h --help Print help and exit\n"
|
||||
"\n"
|
||||
" [<KM>] Key modifier encoded as 32 hexadecimal characters\n"
|
||||
"\n"
|
||||
"Notes: if the key modifier is not specified, a zero key modifier is used.\n"
|
||||
" if the output_file is not specified, the encryption/decryption will be done in-place.\n"
|
||||
"\n", program_name);
|
||||
|
||||
exit(exitval);
|
||||
}
|
||||
|
||||
static int bitcount(int n)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while (n != 0) {
|
||||
n &= (n - 1);
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void parse_options(int argc, char *argv[])
|
||||
{
|
||||
int opt_index, opt;
|
||||
const char *short_options = "edm:h";
|
||||
const struct option long_options[] = {
|
||||
{"encrypt", no_argument, NULL, 'e'},
|
||||
{"decrypt", no_argument, NULL, 'd'},
|
||||
{"modifier", required_argument, NULL, 'm'},
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
if (argc == 1)
|
||||
usage_and_exit(EXIT_SUCCESS);
|
||||
|
||||
while (1) {
|
||||
opt =
|
||||
getopt_long(argc, argv, short_options, long_options,
|
||||
&opt_index);
|
||||
if (opt == -1)
|
||||
break;
|
||||
|
||||
switch (opt) {
|
||||
case 'e':
|
||||
op |= ENCRYPT;
|
||||
break;
|
||||
case 'd':
|
||||
op |= DECRYPT;
|
||||
break;
|
||||
case 'm':
|
||||
key_modifier_string = optarg;
|
||||
break;
|
||||
default:
|
||||
usage_and_exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse positional arguments:
|
||||
*
|
||||
* - input file [required]
|
||||
* - output file (optional)
|
||||
*/
|
||||
if (argc == (optind + 1)) {
|
||||
input_file = argv[optind++];
|
||||
} else if (argc == (optind + 2)) {
|
||||
input_file = argv[optind++];
|
||||
output_file = argv[optind++];
|
||||
} else {
|
||||
usage_and_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* Validate one and only one operation is set */
|
||||
if (bitcount(op) != 1)
|
||||
usage_and_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int parse_hex_string(const char *hex, uint8_t *bytes, size_t length)
|
||||
{
|
||||
static const char *hex_chars = "0123456789abcdefABCDEF";
|
||||
const size_t string_len = strlen(hex);
|
||||
size_t i;
|
||||
|
||||
/* Ensure correct string size and hexadecimal characters */
|
||||
if (string_len != length * 2 || strspn(hex, hex_chars) != string_len)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
sscanf(hex, "%2hhx", &bytes[i]);
|
||||
hex += 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t filesize(const char *path)
|
||||
{
|
||||
struct stat file_stats;
|
||||
|
||||
if (stat(path, &file_stats) < 0)
|
||||
return SIZE_MAX;
|
||||
|
||||
return file_stats.st_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: open files are explicitly closed only if needed. They will be closed
|
||||
* by the OS when the program finishes, so there is no need to ensure they are
|
||||
* closed before finishing on error conditions.
|
||||
* The same applies to allocated memory.
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int input_fd;
|
||||
uint8_t *input_data;
|
||||
size_t input_len;
|
||||
int output_fd;
|
||||
uint8_t *output_data;
|
||||
size_t output_len;
|
||||
uint8_t key_modifier[KEY_MODIFIER_SIZE];
|
||||
int ret = EXIT_SUCCESS;
|
||||
|
||||
if (argc > 0)
|
||||
program_name = argv[0];
|
||||
|
||||
/* read and parse command line */
|
||||
parse_options(argc, argv);
|
||||
|
||||
/* read input data */
|
||||
if ((input_fd = open(input_file, O_RDONLY, 0)) < 0) {
|
||||
perror("[ERROR] Could not open input file");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
input_len = filesize(input_file);
|
||||
if (input_len == SIZE_MAX) {
|
||||
perror("[ERROR] Could not stat input file");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* this is a limitation of the current driver implementation */
|
||||
if (input_len >= BLOB_MAX_INPUT_SIZE)
|
||||
fprintf(stderr, "[WARNING] Input is too big, %s may fail\n",
|
||||
op == ENCRYPT ? "encryption" : "decryption");
|
||||
|
||||
input_data = mmap(NULL, input_len, PROT_READ,
|
||||
MAP_PRIVATE | MAP_POPULATE, input_fd, 0);
|
||||
if (input_data == MAP_FAILED) {
|
||||
perror("[ERROR] Could not map input file into memory");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* if specified, read and validate key modifier */
|
||||
if (key_modifier_string) {
|
||||
if (parse_hex_string(key_modifier_string, key_modifier,
|
||||
KEY_MODIFIER_SIZE)) {
|
||||
fprintf(stderr,
|
||||
"Invalid key modifier. A 16 bytes value encoded as 32 hexadecimal characters is required.\n"
|
||||
"Example: --modifier=000102030405060708090a0b0c0d0e0f\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
} else {
|
||||
memset(key_modifier, 0, KEY_MODIFIER_SIZE);
|
||||
}
|
||||
|
||||
/* alloc memory for output data */
|
||||
output_len = input_len +
|
||||
(op == ENCRYPT ? BLOB_OVERHEAD : -BLOB_OVERHEAD);
|
||||
if (op == DECRYPT && input_len <= BLOB_OVERHEAD) {
|
||||
fprintf(stderr, "[ERROR] Input data is too small to be a CAAM blob\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
output_data = malloc(output_len);
|
||||
if (!output_data) {
|
||||
fprintf(stderr, "[ERROR] Not enough memory\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* perform encryption/decryption */
|
||||
if (op == ENCRYPT) {
|
||||
if (caamblob_encrypt(input_data, input_len,
|
||||
key_modifier, output_data)) {
|
||||
fprintf(stderr, "[ERROR] Encryption failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
} else {
|
||||
if (caamblob_decrypt(input_data, input_len,
|
||||
key_modifier, output_data)) {
|
||||
fprintf(stderr, "[ERROR] Decryption failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
/* if no output file given, close the input file and write to it */
|
||||
if (!output_file) {
|
||||
if (munmap(input_data, input_len) < 0)
|
||||
perror("[WARNING] Error unmapping input file");
|
||||
|
||||
if (close(input_fd) < 0)
|
||||
perror("[WARNING] Error closing input file");
|
||||
|
||||
output_file = input_file;
|
||||
}
|
||||
|
||||
output_fd = open(output_file, O_RDWR | O_CREAT | O_TRUNC, S_IWUSR);
|
||||
if (output_fd < 0) {
|
||||
perror("[ERROR] Could not open output file");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (write(output_fd, output_data, output_len) != output_len) {
|
||||
perror("[WARNING] write to output file failed");
|
||||
ret = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (close(output_fd) < 0) {
|
||||
perror("[WARNING] could not close output file");
|
||||
ret = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
Loading…
Reference in New Issue