File Position/Offset (lseek64())#

#include <sys/types.h>
#include <unistd.h>

off64_t lseek64(int fd, off64_t offset, int whence);

File Offset, And read()/write()#

  • read() always reads from current file offset

  • Offset is advanced as part of the read operation

  • (Same with write())

  • ⟶ offset is part of the “open file description” (struct file) in kernel space

../../../../../../../_images/offset.svg

How lseek64() Works#

  • ⟶ how to determine current offset, how to explicitly set offset?

  • lseek() can only handle 32 bit offsets (historical baggage)

  • lseek64()

  • Possible values for whence

    Macro

    Description

    SEEK_SET

    The file offset is set to offset bytes, absolutely.

    SEEK_CUR

    The file offset is set to its current location plus offset bytes, relatively.

    SEEK_END

    The file offset is set to the size of the file plus offset bytes, relatively.

Getting Current Offset#

  • Use SEEK_CUR with offset 0 to get current position: lseek64(fd, 0, SEEK_CUR). (“Add 0 to current position, and return new position”)

  • The following program visualizes what’s shown in the sketch above

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <print>

int main()
{
    int fd = open("/etc/passwd", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    off64_t pos = lseek64(fd, 0, SEEK_CUR);
    if (pos == -1) {
        perror("lseek");
        return 1;
    }

    std::println("initial offset: {}", pos);

    char buf[10];
    ssize_t nread = read(fd, buf, 10);
    if (nread == -1) {
        perror("read");
        return 1;
    }

    pos = lseek64(fd, 0, SEEK_CUR);
    if (pos == -1) {
        perror("lseek");
        return 1;
    }

    std::println("offset after 10 bytes read: {}", pos);

    return 0;                                          // <-- fd automatically closed at exit
}
$ ./sysprog-lseek-getpos
initial offset: 0
offset after 10 bytes read: 10

Setting File Offset#

  • Use offset parameter, in combination with SEEK_SET, SEEK_CUR and SEEK_END

  • The program below drops an 'X' in position 2 of /tmp/somefile

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <print>

int main()
{
    int fd = open("/tmp/somefile", O_WRONLY|O_CREAT|O_EXCL, 0666);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    off64_t pos = lseek64(fd, 2, SEEK_SET);            // <-- absolute position 2
    if (pos == -1) {
        perror("lseek");
        return 1;
    }

    char byte = 'X';
    ssize_t nwritten = write(fd, &byte, 1);            // <-- drop an 'X' to current position (2)
    if (nwritten == -1) {
        perror("write");
        return 1;
    }

    return 0;                                          // <-- fd automatically closed at exit
}
$ echo 012345 > /tmp/somefile
$ ./sysprog-lseek-setpos-write
$ cat /tmp/somefile
01X345

Holes In Files?#

  • One of the more obscure UNIX features

  • Set the offset beyond file size

  • Drop at least one byte there

  • ⟶ gap does not occupy space

  • ⟶ reading from holes yields 0-bytes

../../../../../../../_images/hole.svg

Holes In Files (Program)#

  • The following program uses /tmp/somefile from above

  • Adds a hole according to the sketch

  • Note how it does not consume disk space correspondingly (btrfs works differently/specially though)

#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <print>

int main()
{
    int fd = open("/tmp/somefile", O_WRONLY|O_CREAT|O_EXCL, 0666);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    off64_t pos = lseek64(fd, 10000, SEEK_SET);        // <-- absolute position 10000
    if (pos == -1) {
        perror("lseek");
        return 1;
    }

    char byte = 'X';
    ssize_t nwritten = write(fd, &byte, 1);            // <-- drop byte at 10000
    if (nwritten == -1) {
        perror("write");
        return 1;
    }

    return 0;                                          // <-- fd automatically closed at exit
}