Search code examples
csystem-calls

The `write` syscall in C writes to an unexpected position


I've written a simple C program.

It takes four command line parameters: file name, start position, length and byte value; opens the specified file for writing and, starting at the specified position, fills a segment of the specified length in the file with the specified byte value.

It writes in blocks (the block size is 4 bytes now for debugging purposes).

/* fill_file_w_byte_val.c */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int block_size = 4;

void error_handling(int res, const char *file_name)
{
    if (res == -1) {
        perror(file_name);
        exit(1);
    }
}

void write_n_blocks_of_bytes_to_file(
    const char *file_name, int fd, void *mem,
    int block_size, int num_of_blocks, int byte_val
)
{
    int i, res;
    mem = malloc(sizeof(char) * block_size);
    memset(mem, byte_val, block_size);
    for (i=num_of_blocks; i > 0; i--) {
        res = write(fd, mem, block_size);
        error_handling(res, file_name);
    }
    free(mem);
}

int main(int argc, char **argv)
{
    char *file_name, *mem = NULL;
    int len, byte_val, num_of_blocks, fd, res, reminder;
    long start_pos;
    if (argc != 5) {
        printf("Wrong number of arguments: %d. It must be %d", argc-1, 4);
        return 1;
    }
    file_name = argv[1];
    fd = open(file_name, O_WRONLY);
    error_handling(fd, file_name);
    start_pos = atoi(argv[2]);
    res = lseek(fd, start_pos, SEEK_SET);
    error_handling(res, file_name);
    len = atoi(argv[3]);
    byte_val = atoi(argv[4]);
    if ((byte_val < 0) || (byte_val > 255)) {
        printf("Wrong value of byte: %d. It must be from 0 to 255.", byte_val);
        return 1;
    }
    num_of_blocks = len / block_size;
    if (num_of_blocks > 0)
        write_n_blocks_of_bytes_to_file(
            file_name, fd, mem, block_size, num_of_blocks, byte_val
        );
    reminder = len % block_size;
    if (reminder != 0)
        write_n_blocks_of_bytes_to_file(
            file_name, fd, mem, reminder, 1, byte_val
        );
    res = close(fd);
    error_handling(res, file_name);
    return 0;
}

Here is the working session:

fedor@fedor-Latitude-E7250:~/c$ hexdump test.bin
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
000001c
fedor@fedor-Latitude-E7250:~/c$ ./fill_file_w_byte_val test.bin 1 2 255
fedor@fedor-Latitude-E7250:~/c$ hexdump test.bin
0000000 ff00 00ff 0000 0000 0000 0000 0000 0000
0000010 0000 0000 0000 0000 0000 0000          
000001c

Why does my program write the 0th and 3rd bytes instead of the 1st and 2nd?


Solution

  • As requested, my comments in the form of an answer. Those are the 1st and 2nd positions (on a linux intel PC. And, roughly, on almost all modern machines).

    hexdump default behavior is to show unsigned short ints values, in hex. Your file contains (as you wanted) 0 255 255 0 0 0....

    The first unsigned short int in that file is therefore the one made of bytes 0 and 255. That is, in little endian 65280 aka 0xff00. The second unsigned short in the file is the one made of 255 and 0, that is, in little endian 255 aka 0x00ff.

    Which is exactly what hexdump shows.

    Try hexdump -C test.bin