summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibC/bits/stdio_file_implementation.h
blob: 555cdcbc97f86f0b4ddf831ca897e9c6bee4d039 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
 * Copyright (c) 2021, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/Array.h>
#include <AK/IntrusiveList.h>
#include <AK/Types.h>
#include <LibC/bits/FILE.h>
#include <LibC/bits/pthread_integration.h>
#include <LibC/bits/wchar.h>
#include <pthread.h>
#include <sys/types.h>

struct FILE {
public:
    FILE(int fd, int mode)
        : m_fd(fd)
        , m_mode(mode)
    {
        pthread_mutexattr_t attr = { __PTHREAD_MUTEX_RECURSIVE };
        pthread_mutex_init(&m_mutex, &attr);
    }
    ~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();
    size_t pending();
    bool close();

    void lock();
    void unlock();

    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; }
    void set_err() { m_error = 1; }

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

    template<typename CharType>
    bool gets(CharType*, 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);

    u8 const* readptr(size_t& available_size);
    void readptr_increase(size_t increment);

    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;
        bool is_not_empty() const { return m_ungotten || !m_empty; }
        size_t buffered_size() const;

        u8 const* 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:
        constexpr static auto unget_buffer_size = MB_CUR_MAX;
        constexpr static u32 ungotten_mask = ((u32)0xffffffff) >> (sizeof(u32) * 8 - unget_buffer_size);

        // 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 };
        Array<u8, unget_buffer_size> m_unget_buffer { 0 };
        u32 m_ungotten : unget_buffer_size { 0 };
        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(u8 const*, size_t);

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

    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;
    IntrusiveListNode<FILE> m_list_node;

public:
    using List = IntrusiveList<&FILE::m_list_node>;
};

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

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

private:
    FILE* m_file;
};