summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibC/bits/stdio_file_implementation.h
blob: 81a762b5c1a1a5a3f956f441e4c6e3efda240add (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
 * Copyright (c) 2021, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Types.h>
#include <LibC/bits/FILE.h>
#include <LibC/bits/pthread_integration.h>
#include <sys/types.h>

#pragma once

struct FILE {
public:
    FILE(int fd, int mode)
        : m_fd(fd)
        , m_mode(mode)
    {
        __pthread_mutex_init(&m_mutex, nullptr);
    }
    ~FILE();

    static FILE* create(int fd, int mode);

    void setbuf(u8* data, int mode, size_t size) { m_buffer.setbuf(data, mode, size); }

    bool flush();
    void purge();
    bool close();

    int fileno() const { return m_fd; }
    bool eof() const { return m_eof; }
    int mode() const { return m_mode; }
    u8 flags() const { return m_flags; }

    int error() const { return m_error; }
    void clear_err() { m_error = 0; }

    size_t read(u8*, size_t);
    size_t write(const u8*, size_t);

    bool gets(u8*, size_t);
    bool ungetc(u8 byte) { return m_buffer.enqueue_front(byte); }

    int seek(off_t offset, int whence);
    off_t tell();

    pid_t popen_child() { return m_popen_child; }
    void set_popen_child(pid_t child_pid) { m_popen_child = child_pid; }

    void reopen(int fd, int mode);

    enum Flags : u8 {
        None = 0,
        LastRead = 1,
        LastWrite = 2,
    };

private:
    struct Buffer {
        // A ringbuffer that also transparently implements ungetc().
    public:
        ~Buffer();

        int mode() const { return m_mode; }
        void setbuf(u8* data, int mode, size_t size);
        // Make sure to call realize() before enqueuing any data.
        // Dequeuing can be attempted without it.
        void realize(int fd);
        void drop();

        bool may_use() const { return m_ungotten || m_mode != _IONBF; }
        bool is_not_empty() const { return m_ungotten || !m_empty; }
        size_t buffered_size() const;

        const u8* begin_dequeue(size_t& available_size) const;
        void did_dequeue(size_t actual_size);

        u8* begin_enqueue(size_t& available_size) const;
        void did_enqueue(size_t actual_size);

        bool enqueue_front(u8 byte);

    private:
        // Note: the fields here are arranged this way
        // to make sizeof(Buffer) smaller.
        u8* m_data { nullptr };
        size_t m_capacity { BUFSIZ };
        size_t m_begin { 0 };
        size_t m_end { 0 };

        int m_mode { -1 };
        u8 m_unget_buffer { 0 };
        bool m_ungotten : 1 { false };
        bool m_data_is_malloced : 1 { false };
        // When m_begin == m_end, we want to distinguish whether
        // the buffer is full or empty.
        bool m_empty : 1 { true };
    };

    // Read or write using the underlying fd, bypassing the buffer.
    ssize_t do_read(u8*, size_t);
    ssize_t do_write(const u8*, size_t);

    // Read some data into the buffer.
    bool read_into_buffer();
    // Flush *some* data from the buffer.
    bool write_from_buffer();

    void lock();
    void unlock();

    int m_fd { -1 };
    int m_mode { 0 };
    u8 m_flags { Flags::None };
    int m_error { 0 };
    bool m_eof { false };
    pid_t m_popen_child { -1 };
    Buffer m_buffer;
    __pthread_mutex_t m_mutex;

    friend class ScopedFileLock;
};

class ScopedFileLock {
public:
    ScopedFileLock(FILE* file)
        : m_file(file)
    {
        m_file->lock();
    }

    ~ScopedFileLock()
    {
        m_file->unlock();
    }

private:
    FILE* m_file;
};