1// This file is part of the ustl library, an STL implementation.
2//
3// Copyright (C) 2005 by Mike Sharov <msharov@users.sourceforge.net>
4// This file is free software, distributed under the MIT License.
5//
6// file.cc
7//
8
9#include "fstream.h"
10#include "uassert.h"
11#include "uexception.h"
12#include "uutility.h"
13
14#include <fcntl.h>
15#include <unistd.h>
16#include <errno.h>
17#include <sys/stat.h>
18#include <sys/mman.h>
19#include <sys/ioctl.h>
20
21#if PLATFORM_ANDROID
22#include <stdio.h>
23#endif
24
25namespace ustl {
26
27/// Default constructor.
28fstream::fstream (void)
29: ios_base (),
30  m_fd (-1),
31  m_Filename ()
32{
33}
34
35/// Opens \p filename in \p mode.
36fstream::fstream (const char* filename, openmode mode)
37: ios_base (),
38  m_fd (-1),
39  m_Filename ()
40{
41    open (filename, mode);
42}
43
44/// Attaches to \p nfd of \p filename.
45fstream::fstream (int nfd, const char* filename)
46: ios_base (),
47  m_fd (-1),
48  m_Filename ()
49{
50    attach (nfd, filename);
51}
52
53/// Destructor. Closes if still open, but without throwing.
54fstream::~fstream (void) throw()
55{
56    clear (goodbit);
57    exceptions (goodbit);
58    close();
59    assert (!(rdstate() & badbit) && "close failed in the destructor! This may lead to loss of user data. Please call close() manually and either enable exceptions or check the badbit.");
60}
61
62/// Sets state \p s and throws depending on the exception setting.
63void fstream::set_and_throw (iostate s, const char* op)
64{
65    if (ios_base::set_and_throw (s))
66#if PLATFORM_ANDROID
67        printf("file_exception\n");
68#else /* !PLATFORM_ANDROID */
69	throw file_exception (op, name());
70#endif
71}
72
73/// Attaches to the given \p nfd.
74void fstream::attach (int nfd, const char* filename)
75{
76    assert (filename && "Don't do that");
77    clear (goodbit);
78    if (nfd < 0 && ios_base::set_and_throw (badbit))
79#if PLATFORM_ANDROID
80        printf("file exception\n");
81#else /* !PLATFORM_ANDROID */
82	throw file_exception ("open", filename);
83#endif
84    close();
85    m_fd = nfd;
86    m_Filename = filename;
87}
88
89/// Detaches from the current fd.
90void fstream::detach (void)
91{
92    m_fd = -1;
93    m_Filename.clear();
94}
95
96/// Converts openmode bits into libc open flags.
97/*static*/ int fstream::om_to_flags (openmode m)
98{
99    static const int s_OMFlags [nombits] = {
100	0,		// in
101	O_CREAT,	// out
102	O_APPEND,	// app
103	O_APPEND,	// ate
104	0,		// binary
105	O_TRUNC,	// trunc
106	O_NONBLOCK,	// nonblock
107	0,		// nocreate
108	O_NOCTTY	// noctty
109    };
110    int flags = (m - 1) & O_ACCMODE;	// in and out
111    for (uoff_t i = 0; i < VectorSize(s_OMFlags); ++ i)
112	if (m & (1 << i))
113	    flags |= s_OMFlags[i];
114    if (m & nocreate)
115	flags &= ~O_CREAT;
116    return (flags);
117}
118
119/// \brief Opens \p filename in the given mode.
120/// \warning The string at \p filename must exist until the object is closed.
121void fstream::open (const char* filename, openmode mode, mode_t perms)
122{
123    int nfd = ::open (filename, om_to_flags(mode), perms);
124    attach (nfd, filename);
125}
126
127/// Closes the file and throws on error.
128void fstream::close (void)
129{
130    if (m_fd >= 0 && ::close(m_fd))
131	set_and_throw (badbit | failbit, "close");
132    detach();
133}
134
135/// Moves the current file position to \p n.
136off_t fstream::seek (off_t n, seekdir whence)
137{
138    off_t p = lseek (m_fd, n, whence);
139    if (p < 0)
140	set_and_throw (failbit, "seek");
141    return (p);
142}
143
144/// Returns the current file position.
145off_t fstream::pos (void) const
146{
147    return (lseek (m_fd, 0, SEEK_CUR));
148}
149
150/// Reads \p n bytes into \p p.
151off_t fstream::read (void* p, off_t n)
152{
153    off_t br (0);
154    while (br < n && good())
155	br += readsome (advance (p, br), n - br);
156    return (br);
157}
158
159/// Reads at most \p n bytes into \p p, stopping when it feels like it.
160off_t fstream::readsome (void* p, off_t n)
161{
162    ssize_t brn;
163    do { brn = ::read (m_fd, p, n); } while (brn < 0 && errno == EINTR);
164    if (brn > 0)
165	return (brn);
166    if (brn < 0 && errno != EAGAIN)
167	set_and_throw (failbit, "read");
168    if (!brn && ios_base::set_and_throw (eofbit | failbit))
169#if PLATFORM_ANDROID
170        printf("stream_bounds_exception\n");
171#else /* !PLATFORM_ANDROID */
172        throw stream_bounds_exception ("read", name(), pos(), n, 0);
173#endif
174    return (0);
175}
176
177/// Writes \p n bytes from \p p.
178off_t fstream::write (const void* p, off_t n)
179{
180    off_t btw (n);
181    while (btw) {
182	const off_t bw (n - btw);
183	ssize_t bwn = ::write (m_fd, advance(p,bw), btw);
184	if (bwn > 0)
185	    btw -= bwn;
186	else if (!bwn) {
187	    if (ios_base::set_and_throw (eofbit | failbit))
188#if PLATFORM_ANDROID
189	      printf("stream_bounds_exception\n");
190#else /* !PLATFORM_ANDROID */
191	    throw stream_bounds_exception ("write", name(), pos() - bw, n, bw);
192#endif
193	    break;
194	} else if (errno != EINTR) {
195	    if (errno != EAGAIN)
196		set_and_throw (failbit, "write");
197	    break;
198	}
199    }
200    return (n - btw);
201}
202
203/// Returns the file size.
204off_t fstream::size (void) const
205{
206    struct stat st;
207    st.st_size = 0;
208    stat (st);
209    return (st.st_size);
210}
211
212/// Synchronizes the file's data and status with the disk.
213void fstream::sync (void)
214{
215    if (fsync (m_fd))
216	set_and_throw (failbit, "sync");
217}
218
219/// Get the stat structure.
220void fstream::stat (struct stat& rs) const
221{
222    if (fstat (m_fd, &rs))
223#if PLATFORM_ANDROID
224        printf("file_exception\n");
225#else
226	throw file_exception ("stat", name());
227#endif
228}
229
230/// Calls the given ioctl. Use IOCTLID macro to pass in both \p name and \p request.
231int fstream::ioctl (const char* rname, int request, long argument)
232{
233    int rv = ::ioctl (m_fd, request, argument);
234    if (rv < 0)
235	set_and_throw (failbit, rname);
236    return (rv);
237}
238
239/// Calls the given fcntl. Use FCNTLID macro to pass in both \p name and \p request.
240int fstream::fcntl (const char* rname, int request, long argument)
241{
242    int rv = ::fcntl (m_fd, request, argument);
243    if (rv < 0)
244	set_and_throw (failbit, rname);
245    return (rv);
246}
247
248/// Memory-maps the file and returns a link to it.
249memlink fstream::mmap (off_t n, off_t offset)
250{
251    void* result = ::mmap (NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, offset);
252    if (result == MAP_FAILED)
253	set_and_throw (failbit, "mmap");
254    return (memlink (result, n));
255}
256
257/// Unmaps a memory-mapped area.
258void fstream::munmap (memlink& l)
259{
260    if (::munmap (l.data(), l.size()))
261	set_and_throw (failbit, "munmap");
262    l.unlink();
263}
264
265/// Synchronizes a memory-mapped area.
266void fstream::msync (memlink& l)
267{
268    if (::msync (l.data(), l.size(), MS_ASYNC | MS_INVALIDATE))
269	set_and_throw (failbit, "msync");
270}
271
272void fstream::set_nonblock (bool v)
273{
274    int curf = fcntl (FCNTLID (F_GETFL));
275    if (curf < 0) return;
276    if (v) curf |=  O_NONBLOCK;
277    else   curf &= ~O_NONBLOCK;
278    fcntl (FCNTLID (F_SETFL), curf);
279}
280
281} // namespace ustl
282
283