Kernel: Remove offset from OpenFileDescriptor
This is now handled on the libc side. There might be reasons to have it in kernel side, but for simplicity's sake I'm moving it to libc for now :)
This commit is contained in:
parent
5248a3fe48
commit
1cf7ef3de6
|
@ -25,7 +25,7 @@ namespace LibELF
|
|||
|
||||
TRY(data.resize(st.st_size));
|
||||
|
||||
TRY(Kernel::Process::current().read(fd, data.data(), data.size()));
|
||||
TRY(Kernel::Process::current().read(fd, data.data(), 0, data.size()));
|
||||
|
||||
elf = new ELF(BAN::move(data));
|
||||
ASSERT(elf);
|
||||
|
|
|
@ -153,9 +153,13 @@ irq 13
|
|||
irq 14
|
||||
irq 15
|
||||
|
||||
// arguments in RAX, RBX, RCX, RDX, RSI, RDI
|
||||
// System V ABI: RDI, RSI, RDX, RCX, R8, R9
|
||||
.global syscall_asm
|
||||
syscall_asm:
|
||||
pushaq
|
||||
movq %rsi, %r8
|
||||
movq %rdi, %r9
|
||||
movq %rax, %rdi
|
||||
movq %rbx, %rsi
|
||||
xchgq %rcx, %rdx
|
||||
|
|
|
@ -41,16 +41,15 @@ namespace Kernel
|
|||
pid_t pid() const { return m_pid; }
|
||||
|
||||
BAN::ErrorOr<int> open(BAN::StringView, int);
|
||||
BAN::ErrorOr<void> close(int);
|
||||
BAN::ErrorOr<size_t> read(int, void*, size_t);
|
||||
BAN::ErrorOr<size_t> write(int, const void*, size_t);
|
||||
BAN::ErrorOr<void> creat(BAN::StringView, mode_t);
|
||||
BAN::ErrorOr<void> seek(int, size_t);
|
||||
BAN::ErrorOr<void> close(int fd);
|
||||
BAN::ErrorOr<size_t> read(int fd, void* buffer, size_t offset, size_t count);
|
||||
BAN::ErrorOr<size_t> write(int fd, const void* buffer, size_t offset, size_t count);
|
||||
BAN::ErrorOr<void> creat(BAN::StringView name, mode_t);
|
||||
|
||||
BAN::ErrorOr<void> fstat(int, stat*);
|
||||
BAN::ErrorOr<void> stat(BAN::StringView, stat*);
|
||||
BAN::ErrorOr<void> fstat(int fd, stat*);
|
||||
BAN::ErrorOr<void> stat(BAN::StringView path, stat*);
|
||||
|
||||
BAN::ErrorOr<void> mount(BAN::StringView, BAN::StringView);
|
||||
BAN::ErrorOr<void> mount(BAN::StringView source, BAN::StringView target);
|
||||
|
||||
BAN::ErrorOr<BAN::Vector<BAN::String>> read_directory_entries(int);
|
||||
|
||||
|
@ -78,7 +77,6 @@ namespace Kernel
|
|||
{
|
||||
BAN::RefPtr<Inode> inode;
|
||||
BAN::String path;
|
||||
size_t offset = 0;
|
||||
uint8_t flags = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -5,21 +5,20 @@
|
|||
#define SYS_WRITE 3
|
||||
#define SYS_TERMID 4
|
||||
#define SYS_CLOSE 5
|
||||
#define SYS_SEEK 6
|
||||
#define SYS_OPEN 7
|
||||
#define SYS_ALLOC 8
|
||||
#define SYS_FREE 9
|
||||
#define SYS_OPEN 6
|
||||
#define SYS_ALLOC 7
|
||||
#define SYS_FREE 8
|
||||
|
||||
#include <kernel/Attributes.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
template<typename T1 = void*, typename T2 = void*, typename T3 = void*>
|
||||
inline long syscall(int syscall, T1 arg1 = nullptr, T2 arg2 = nullptr, T3 arg3 = nullptr)
|
||||
ALWAYS_INLINE long syscall(int syscall, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0, uintptr_t arg4 = 0, uintptr_t arg5 = 0)
|
||||
{
|
||||
long ret;
|
||||
asm volatile("int $0x80" : "=a"(ret) : "a"(syscall), "b"((uintptr_t)arg1), "c"((uintptr_t)arg2), "d"((uintptr_t)arg3) : "memory");
|
||||
asm volatile("int $0x80" : "=a"(ret) : "a"(syscall), "b"((uintptr_t)arg1), "c"((uintptr_t)arg2), "d"((uintptr_t)arg3), "S"((uintptr_t)arg4), "D"((uintptr_t)arg5) : "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace Kernel
|
|||
|
||||
BAN::Vector<uint8_t> file_data;
|
||||
TRY(file_data.resize(st.st_size));
|
||||
TRY(Process::current().read(fd, file_data.data(), st.st_size));
|
||||
TRY(Process::current().read(fd, file_data.data(), 0, st.st_size));
|
||||
|
||||
if (file_data.size() < 4)
|
||||
return BAN::Error::from_error_code(ErrorCode::Font_FileTooSmall);
|
||||
|
|
|
@ -202,7 +202,6 @@ namespace Kernel
|
|||
auto& open_file_description = m_open_files[fd];
|
||||
open_file_description.inode = file.inode;
|
||||
open_file_description.path = BAN::move(file.canonical_path);
|
||||
open_file_description.offset = 0;
|
||||
open_file_description.flags = flags;
|
||||
|
||||
return fd;
|
||||
|
@ -217,7 +216,7 @@ namespace Kernel
|
|||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<size_t> Process::read(int fd, void* buffer, size_t count)
|
||||
BAN::ErrorOr<size_t> Process::read(int fd, void* buffer, size_t offset, size_t count)
|
||||
{
|
||||
OpenFileDescription open_fd_copy;
|
||||
|
||||
|
@ -229,18 +228,10 @@ namespace Kernel
|
|||
|
||||
if (!(open_fd_copy.flags & O_RDONLY))
|
||||
return BAN::Error::from_errno(EBADF);
|
||||
size_t n_read = TRY(open_fd_copy.inode->read(open_fd_copy.offset, buffer, count));
|
||||
open_fd_copy.offset += n_read;
|
||||
|
||||
m_lock.lock();
|
||||
MUST(validate_fd(fd));
|
||||
open_file_description(fd) = open_fd_copy;
|
||||
m_lock.unlock();
|
||||
|
||||
return n_read;
|
||||
return TRY(open_fd_copy.inode->read(offset, buffer, count));
|
||||
}
|
||||
|
||||
BAN::ErrorOr<size_t> Process::write(int fd, const void* buffer, size_t count)
|
||||
BAN::ErrorOr<size_t> Process::write(int fd, const void* buffer, size_t offset, size_t count)
|
||||
{
|
||||
OpenFileDescription open_fd_copy;
|
||||
|
||||
|
@ -252,16 +243,7 @@ namespace Kernel
|
|||
|
||||
if (!(open_fd_copy.flags & O_WRONLY))
|
||||
return BAN::Error::from_errno(EBADF);
|
||||
size_t n_written = TRY(open_fd_copy.inode->write(open_fd_copy.offset, buffer, count));
|
||||
open_fd_copy.offset += n_written;
|
||||
|
||||
{
|
||||
LockGuard _(m_lock);
|
||||
MUST(validate_fd(fd));
|
||||
open_file_description(fd) = open_fd_copy;
|
||||
}
|
||||
|
||||
return n_written;
|
||||
return TRY(open_fd_copy.inode->write(offset, buffer, count));
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Process::creat(BAN::StringView path, mode_t mode)
|
||||
|
@ -282,11 +264,11 @@ namespace Kernel
|
|||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Process::mount(BAN::StringView partition, BAN::StringView path)
|
||||
BAN::ErrorOr<void> Process::mount(BAN::StringView source, BAN::StringView target)
|
||||
{
|
||||
auto absolute_partition = TRY(absolute_path_of(partition));
|
||||
auto absolute_path = TRY(absolute_path_of(path));
|
||||
TRY(VirtualFileSystem::get().mount(absolute_partition, absolute_path));
|
||||
auto absolute_source = TRY(absolute_path_of(source));
|
||||
auto absolute_target = TRY(absolute_path_of(target));
|
||||
TRY(VirtualFileSystem::get().mount(absolute_source, absolute_target));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -325,15 +307,7 @@ namespace Kernel
|
|||
return ret;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Process::seek(int fd, size_t offset)
|
||||
{
|
||||
LockGuard _(m_lock);
|
||||
TRY(validate_fd(fd));
|
||||
auto& open_fd = open_file_description(fd);
|
||||
open_fd.offset = offset;
|
||||
return {};
|
||||
}
|
||||
|
||||
// FIXME: This whole API has to be rewritten
|
||||
BAN::ErrorOr<BAN::Vector<BAN::String>> Process::read_directory_entries(int fd)
|
||||
{
|
||||
OpenFileDescription open_fd_copy;
|
||||
|
@ -344,15 +318,7 @@ namespace Kernel
|
|||
open_fd_copy = open_file_description(fd);
|
||||
}
|
||||
|
||||
auto result = TRY(open_fd_copy.inode->read_directory_entries(open_fd_copy.offset));
|
||||
open_fd_copy.offset++;
|
||||
|
||||
m_lock.lock();
|
||||
MUST(validate_fd(fd));
|
||||
open_file_description(fd) = open_fd_copy;
|
||||
m_lock.unlock();
|
||||
|
||||
return result;
|
||||
return TRY(open_fd_copy.inode->read_directory_entries(0));
|
||||
}
|
||||
|
||||
BAN::ErrorOr<BAN::String> Process::working_directory() const
|
||||
|
|
|
@ -22,14 +22,14 @@ namespace Kernel
|
|||
static void TTY_PRINT(Args&&... args)
|
||||
{
|
||||
BAN::String message = BAN::String::formatted(BAN::forward<Args>(args)...);
|
||||
MUST(Process::current().write(STDOUT_FILENO, message.data(), message.size()));
|
||||
MUST(Process::current().write(STDOUT_FILENO, message.data(), 0, message.size()));
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void TTY_PRINTLN(Args&&... args)
|
||||
{
|
||||
TTY_PRINT(BAN::forward<Args>(args)...);
|
||||
MUST(Process::current().write(STDOUT_FILENO, "\n", 1));
|
||||
MUST(Process::current().write(STDOUT_FILENO, "\n", 0, 1));
|
||||
}
|
||||
|
||||
static auto s_default_prompt = "\\[\e[32m\\]user\\[\e[m\\]:\\[\e[34m\\]\\w\\[\e[m\\]# "sv;
|
||||
|
@ -112,7 +112,7 @@ namespace Kernel
|
|||
|
||||
void Shell::run()
|
||||
{
|
||||
auto getch = [this] { uint8_t ch; MUST(Process::current().read(STDIN_FILENO, &ch, 1)); return ch; };
|
||||
auto getch = [this] { uint8_t ch; MUST(Process::current().read(STDIN_FILENO, &ch, 0, 1)); return ch; };
|
||||
|
||||
MUST(m_buffer.push_back(""sv));
|
||||
|
||||
|
@ -130,7 +130,7 @@ namespace Kernel
|
|||
while ((current.back() & 0xC0) == 0x80)
|
||||
current.pop_back();
|
||||
current.pop_back();
|
||||
MUST(Process::current().write(STDOUT_FILENO, "\b \b", 3));
|
||||
MUST(Process::current().write(STDOUT_FILENO, "\b \b", 0, 3));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ namespace Kernel
|
|||
continue;
|
||||
}
|
||||
|
||||
MUST(Process::current().write(STDOUT_FILENO, &ch, 1));
|
||||
MUST(Process::current().write(STDOUT_FILENO, &ch, 0, 1));
|
||||
|
||||
if (ch != '\n')
|
||||
{
|
||||
|
@ -556,8 +556,12 @@ argument_done:
|
|||
BAN::ScopeGuard buffer_guard([buffer] { delete[] buffer; });
|
||||
ASSERT(buffer);
|
||||
|
||||
while (size_t n_read = TRY(Process::current().read(fd, buffer, buffer_size)))
|
||||
size_t offset = 0;
|
||||
while (size_t n_read = TRY(Process::current().read(fd, buffer, offset, buffer_size)))
|
||||
{
|
||||
TTY_PRINT("{}", BAN::StringView(buffer, n_read));
|
||||
offset += n_read;
|
||||
}
|
||||
TTY_PRINTLN("");
|
||||
}
|
||||
else if (arguments.front() == "stat")
|
||||
|
@ -635,7 +639,7 @@ argument_done:
|
|||
|
||||
while (true)
|
||||
{
|
||||
size_t n_read = TRY(Process::current().read(fd, buffer, buffer_size));
|
||||
size_t n_read = TRY(Process::current().read(fd, buffer, total_read, buffer_size));
|
||||
if (n_read == 0)
|
||||
break;
|
||||
for (size_t j = 0; j < n_read; j++)
|
||||
|
|
|
@ -10,17 +10,17 @@ namespace Kernel
|
|||
Process::current().exit();
|
||||
}
|
||||
|
||||
long sys_read(int fd, void* buffer, size_t size)
|
||||
long sys_read(int fd, void* buffer, size_t offset, size_t size)
|
||||
{
|
||||
auto res = Process::current().read(fd, buffer, size);
|
||||
auto res = Process::current().read(fd, buffer, offset, size);
|
||||
if (res.is_error())
|
||||
return -res.error().get_error_code();
|
||||
return res.value();
|
||||
}
|
||||
|
||||
long sys_write(int fd, const void* buffer, size_t size)
|
||||
long sys_write(int fd, const void* buffer, size_t offset, size_t size)
|
||||
{
|
||||
auto res = Process::current().write(fd, buffer, size);
|
||||
auto res = Process::current().write(fd, buffer, offset, size);
|
||||
if (res.is_error())
|
||||
return -res.error().get_error_code();
|
||||
return res.value();
|
||||
|
@ -34,14 +34,6 @@ namespace Kernel
|
|||
return 0;
|
||||
}
|
||||
|
||||
int sys_seek(int fd, long offset)
|
||||
{
|
||||
auto res = Process::current().seek(fd, offset);
|
||||
if (res.is_error())
|
||||
return -res.error().get_error_code();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sys_termid(char* buffer)
|
||||
{
|
||||
Process::current().termid(buffer);
|
||||
|
@ -68,12 +60,14 @@ namespace Kernel
|
|||
Process::current().free(ptr);
|
||||
}
|
||||
|
||||
extern "C" long cpp_syscall_handler(int syscall, void* arg1, void* arg2, void* arg3)
|
||||
extern "C" long cpp_syscall_handler(int syscall, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t arg4, uintptr_t arg5)
|
||||
{
|
||||
Thread::current().set_in_syscall(true);
|
||||
|
||||
asm volatile("sti");
|
||||
|
||||
(void)arg5;
|
||||
|
||||
long ret = 0;
|
||||
switch (syscall)
|
||||
{
|
||||
|
@ -81,28 +75,25 @@ namespace Kernel
|
|||
sys_exit();
|
||||
break;
|
||||
case SYS_READ:
|
||||
ret = sys_read((int)(uintptr_t)arg1, arg2, (size_t)(uintptr_t)arg3);
|
||||
ret = sys_read((int)arg1, (void*)arg2, (size_t)arg3, (size_t)arg4);
|
||||
break;
|
||||
case SYS_WRITE:
|
||||
ret = sys_write((int)(uintptr_t)arg1, arg2, (size_t)(uintptr_t)arg3);
|
||||
ret = sys_write((int)arg1, (const void*)arg2, (size_t)arg3, (size_t)arg4);
|
||||
break;
|
||||
case SYS_TERMID:
|
||||
sys_termid((char*)arg1);
|
||||
break;
|
||||
case SYS_CLOSE:
|
||||
ret = sys_close((int)(uintptr_t)arg1);
|
||||
break;
|
||||
case SYS_SEEK:
|
||||
ret = sys_seek((int)(uintptr_t)arg1, (long)arg2);
|
||||
ret = sys_close((int)arg1);
|
||||
break;
|
||||
case SYS_OPEN:
|
||||
ret = sys_open((const char*)arg1, (int)(uintptr_t)arg2);
|
||||
ret = sys_open((const char*)arg1, (int)arg2);
|
||||
break;
|
||||
case SYS_ALLOC:
|
||||
ret = sys_alloc((size_t)arg1);
|
||||
break;
|
||||
case SYS_FREE:
|
||||
sys_free(arg1);
|
||||
sys_free((void*)arg1);
|
||||
break;
|
||||
default:
|
||||
Kernel::panic("Unknown syscall {}", syscall);
|
||||
|
|
|
@ -66,7 +66,7 @@ namespace Kernel
|
|||
while (true)
|
||||
{
|
||||
Input::KeyEvent event;
|
||||
ASSERT(MUST(Process::current().read(fd, &event, sizeof(event))) == sizeof(event));
|
||||
ASSERT(MUST(Process::current().read(fd, &event, 0, sizeof(event))) == sizeof(event));
|
||||
TTY::current()->on_key(event);
|
||||
}
|
||||
}, nullptr
|
||||
|
|
|
@ -76,7 +76,7 @@ int fflush(FILE* file)
|
|||
if (file->buffer_index == 0)
|
||||
return 0;
|
||||
|
||||
if (syscall(SYS_WRITE, file->fd, file->buffer, file->buffer_index) < 0)
|
||||
if (syscall(SYS_WRITE, file->fd, file->buffer, file->offset, file->buffer_index) < 0)
|
||||
{
|
||||
file->error = true;
|
||||
return EOF;
|
||||
|
@ -92,7 +92,7 @@ int fgetc(FILE* file)
|
|||
return EOF;
|
||||
|
||||
unsigned char c;
|
||||
long ret = syscall(SYS_READ, file->fd, &c, 1);
|
||||
long ret = syscall(SYS_READ, file->fd, &c, file->offset, 1);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
|
@ -235,7 +235,7 @@ size_t fread(void* buffer, size_t size, size_t nitems, FILE* file)
|
|||
{
|
||||
if (file->eof || nitems * size == 0)
|
||||
return 0;
|
||||
long ret = syscall(SYS_READ, file->fd, buffer, size * nitems);
|
||||
long ret = syscall(SYS_READ, file->fd, buffer, file->offset, size * nitems);
|
||||
if (ret < 0)
|
||||
{
|
||||
file->error = true;
|
||||
|
@ -260,7 +260,13 @@ int fseek(FILE* file, long offset, int whence)
|
|||
|
||||
int fseeko(FILE* file, off_t offset, int whence)
|
||||
{
|
||||
if (whence == SEEK_CUR)
|
||||
if (offset < 0)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (whence == SEEK_CUR && offset <= file->offset)
|
||||
file->offset += offset;
|
||||
else if (whence == SEEK_SET)
|
||||
file->offset = offset;
|
||||
|
@ -275,16 +281,6 @@ int fseeko(FILE* file, off_t offset, int whence)
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (file->offset < 0)
|
||||
{
|
||||
file->offset -= offset;
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (syscall(SYS_SEEK, file->fd, file->offset))
|
||||
return -1;
|
||||
|
||||
file->eof = false;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -31,22 +31,24 @@ long syscall(long syscall, ...)
|
|||
{
|
||||
int fd = va_arg(args, int);
|
||||
void* buffer = va_arg(args, void*);
|
||||
size_t offset = va_arg(args, size_t);
|
||||
size_t bytes = va_arg(args, size_t);
|
||||
ret = Kernel::syscall(SYS_READ, fd, buffer, bytes);
|
||||
ret = Kernel::syscall(SYS_READ, fd, (uintptr_t)buffer, offset, bytes);
|
||||
break;
|
||||
}
|
||||
case SYS_WRITE:
|
||||
{
|
||||
int fd = va_arg(args, int);
|
||||
const char* string = va_arg(args, const char*);
|
||||
size_t offset = va_arg(args, size_t);
|
||||
size_t bytes = va_arg(args, size_t);
|
||||
ret = Kernel::syscall(SYS_WRITE, fd, string, bytes);
|
||||
ret = Kernel::syscall(SYS_WRITE, fd, (uintptr_t)string, offset, bytes);
|
||||
break;
|
||||
}
|
||||
case SYS_TERMID:
|
||||
{
|
||||
char* buffer = va_arg(args, char*);
|
||||
ret = Kernel::syscall(SYS_TERMID, buffer);
|
||||
ret = Kernel::syscall(SYS_TERMID, (uintptr_t)buffer);
|
||||
break;
|
||||
}
|
||||
case SYS_CLOSE:
|
||||
|
@ -55,18 +57,11 @@ long syscall(long syscall, ...)
|
|||
ret = Kernel::syscall(SYS_CLOSE, fd);
|
||||
break;
|
||||
}
|
||||
case SYS_SEEK:
|
||||
{
|
||||
int fd = va_arg(args, int);
|
||||
long offset = va_arg(args, long);
|
||||
ret = Kernel::syscall(SYS_SEEK, fd, offset);
|
||||
break;
|
||||
}
|
||||
case SYS_OPEN:
|
||||
{
|
||||
const char* path = va_arg(args, const char*);
|
||||
int oflags = va_arg(args, int);
|
||||
ret = Kernel::syscall(SYS_OPEN, path, oflags);
|
||||
ret = Kernel::syscall(SYS_OPEN, (uintptr_t)path, oflags);
|
||||
break;
|
||||
}
|
||||
case SYS_ALLOC:
|
||||
|
@ -78,7 +73,7 @@ long syscall(long syscall, ...)
|
|||
case SYS_FREE:
|
||||
{
|
||||
void* ptr = va_arg(args, void*);
|
||||
ret = Kernel::syscall(SYS_FREE, ptr);
|
||||
ret = Kernel::syscall(SYS_FREE, (uintptr_t)ptr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -1,35 +1,30 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define N 1024
|
||||
#define ERROR(msg) { perror(msg); return 1; }
|
||||
#define BUF_SIZE 1024
|
||||
|
||||
int main()
|
||||
{
|
||||
for (int i = 0; i <= 10; i++)
|
||||
{
|
||||
int** ptrs = malloc(N * sizeof(int*));
|
||||
if (ptrs == NULL)
|
||||
{
|
||||
perror("malloc");
|
||||
return 1;
|
||||
}
|
||||
for (int j = 0; j < N; j++)
|
||||
{
|
||||
ptrs[j] = malloc(sizeof(int));
|
||||
if (ptrs[j] == NULL)
|
||||
{
|
||||
perror("malloc");
|
||||
return 1;
|
||||
}
|
||||
*ptrs[j] = j;
|
||||
putchar('0' + *ptrs[j] % 10);
|
||||
}
|
||||
putchar('\n');
|
||||
FILE* fp = fopen("/usr/include/stdio.h", "r");
|
||||
if (fp == NULL)
|
||||
ERROR("fopen");
|
||||
|
||||
for (int j = 0; j < N; j++)
|
||||
free(ptrs[j]);
|
||||
free(ptrs);
|
||||
char* buffer = malloc(BUF_SIZE);
|
||||
if (buffer == NULL)
|
||||
ERROR("malloc");
|
||||
|
||||
for (;;)
|
||||
{
|
||||
size_t n_read = fread(buffer, 1, BUF_SIZE - 1, fp);
|
||||
if (n_read == 0)
|
||||
break;
|
||||
buffer[n_read] = '\0';
|
||||
printf("%s", buffer);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue