Unfortunately, C++ iostream facilities suck. They are heavyweight due to supporting different character encodings. They make it impossible to get useful error messages in a standardised way (the language spec allows for it, but the implementations apparently don't bother and map all error conditions to the same useless message). std::ios::failure extends std::system_error but doesn't use errno as the error code.
We should replace them (including use of cout/cerr) with a wrapper for POSIX file I/O.
Activity
30Do you mean we need to use printf() and friends in all cases or replacing something like ifstream with pure C functions like open() or even both?
for example:
https://github.com/davmac314/dinit/blob/a6cff08795ffaea003c1e893c4b3ec96d81a6ae9/src/dinit-env.cc#L20-L26
to this?
// Read and set environment variables from a file. May throw std::bad_alloc, std::system_error.
void read_env_file(const char *env_file_path, bool log_warnings, environment &env)
{
int env_file = open(env_file_path, O_RDONLY);
if (env_file == -1) {
return
}
//env_file.exceptions(std::ios::badbit);No, not necessarily, I meant replace use of iostream (including fstream etc) with a similar wrapper class which uses open and write/read internally. It is still good to use a class rather than use POSIX functions directly as it allows using the RAII idiom.
No, not necessarily, I meant replace use of iostream (including fstream etc) with a similar wrapper class which uses open and write/read internally. It is still good to use a class rather than use POSIX functions directly as it allows using the RAII idiom.
I worked on it a bit and I really enjoy, @davmac314 Please assign this issue to me if you wish. for example I have a very basic implantation for replacing cout and cerr with very better code generation:
#ifndef DINIT_IOSTREAM_H
#define DINIT_IOSTREAM_H
#include <baseproc-sys.h>
#include <cstring>
#include <cmath>
// ToDo: Desc
namespace dio {
class ostream
{
public:
ostream operator<<(const char *msg)
{
bp_sys::write(1, msg, strlen(msg));
// ToDo: Error handling
return *this;
}
ostream operator<<(std::string msg)
{
bp_sys::write(1, msg.c_str(), msg.size());
return *this;
}
ostream operator<<(int num)
{
char tmp[(int)((ceil(log10(num))+1)*sizeof(char))];
sprintf(tmp, "%d", num);
write(1, tmp, strlen(tmp));
return *this;
}
};
ostream cout;
// ToDo: cerr should print to fd 2 (stderr)
ostream cerr;
}
#endifand dinitcheck compiled and ran with no issues. We need buffer for stdio?
I worked on it a bit and I really enjoy, @davmac314 Please assign this issue to me if you wish. for example I have a very basic implantation for replacing cout and cerr with very better code generation:
#ifndef DINIT_IOSTREAM_H
#define DINIT_IOSTREAM_H
#include <baseproc-sys.h>
#include <cstring>
#include <cmath>
// ToDo: Desc
namespace dio {
class ostream
{
public:
ostream operator<<(const char *msg)
{
bp_sys::write(1, msg, strlen(msg));
// ToDo: Error handling
return *this;
}
ostream operator<<(std::string msg)
{
bp_sys::write(1, msg.c_str(), msg.size());
return *this;
}
ostream operator<<(int num)
{
char tmp[(int)((ceil(log10(num))+1)*sizeof(char))];
sprintf(tmp, "%d", num);
write(1, tmp, strlen(tmp));
return *this;
}
};
ostream cout;
// ToDo: cerr should print to fd 2 (stderr)
ostream cerr;
}
#endifand dinitcheck compiled and ran with no issues. We need buffer for stdio?
the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces
low level file descriptor funcs are too primitive for reasonable i/o though, so the implementation may get relatively complex
the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces
low level file descriptor funcs are too primitive for reasonable i/o though, so the implementation may get relatively complex
the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces
With something like fmt?
the shift operator overloads are a bunch of gross 90s cargo cult, so while replacing them i'd suggest actually making up reasonable interfaces
With something like fmt?
I disagree. put() (formerly _write()) only write on these situations:
Ok, I see I misread it. But:
- Message has \n (newline)
Why flush just because of newline? And:
- "endl" object be passed to write() (equals to writing \n and calling flush())
Why does it call flush() if writing '\n' already does flush?
Question: It's safe to assume that EINTR, EWOULDBLOCK and EAGAIN will be changed in one point? my flush() tries up to 10 times about those errnos. I afraid to they get stuck and the whole program get stuck on infinite while loop design.
The stream library doesn't have sufficient context to make a decision about how to handle these errors, so it should report them to the application. It should not try the operation again, it's for the application to decide whether that is the right thing to do.
It might have made sense to have a mode which ignores and retries on EINTR, but I wouldn't bother with that now. For EWOULDBLOCK/EAGAIN just repeating the operation is completely wrong, you will end up repeating the same operation (with the same error) in a tight loop and send CPU usage through the roof. These conditions can only be cleared by something external (eg another process on the other end of the pipe) so you can't ever rely on that happening in any timeframe at all.
I disagree. put() (formerly _write()) only write on these situations:
Ok, I see I misread it. But:
- Message has \n (newline)
Why flush just because of newline? And:
- "endl" object be passed to write() (equals to writing \n and calling flush())
Why does it call flush() if writing '\n' already does flush?
Question: It's safe to assume that EINTR, EWOULDBLOCK and EAGAIN will be changed in one point? my flush() tries up to 10 times about those errnos. I afraid to they get stuck and the whole program get stuck on infinite while loop design.
The stream library doesn't have sufficient context to make a decision about how to handle these errors, so it should report them to the application. It should not try the operation again, it's for the application to decide whether that is the right thing to do.
It might have made sense to have a mode which ignores and retries on EINTR, but I wouldn't bother with that now. For EWOULDBLOCK/EAGAIN just repeating the operation is completely wrong, you will end up repeating the same operation (with the same error) in a tight loop and send CPU usage through the roof. These conditions can only be cleared by something external (eg another process on the other end of the pipe) so you can't ever rely on that happening in any timeframe at all.
@mobin-2008 I will have a proper look over what you have when I get a chance (see comment above too; I think flush-on-newline probably isn't the right choice).
@mobin-2008 I will have a proper look over what you have when I get a chance (see comment above too; I think flush-on-newline probably isn't the right choice).
Sounds good, I'm going to clear some codes and documention in 30 minutes.
Sounds good, I'm going to clear some codes and documention in 30 minutes.
@davmac314 I worked on it a bit and here's the list for pending things:
istreamclass documention (global members are documented completely)fstreamlibrary documention (code is small and self explanatory but having documention is good)
I try to complete these things in 4 days when I get a chance
@davmac314 I worked on it a bit and here's the list for pending things:
istreamclass documention (global members are documented completely)fstreamlibrary documention (code is small and self explanatory but having documention is good)
I try to complete these things in 4 days when I get a chance
I try to complete these things in 4 days when I get a chance
Ok - please open a PR when you are ready. I had a quick look and the major concerns are gone, but I will still have a lot of comments/questions.
I try to complete these things in 4 days when I get a chance
Ok - please open a PR when you are ready. I had a quick look and the major concerns are gone, but I will still have a lot of comments/questions.
Do you mean we need to use
printf()and friends in all cases or replacing something likeifstreamwith pure C functions likeopen()or even both? for example: https://github.com/davmac314/dinit/blob/a6cff08795ffaea003c1e893c4b3ec96d81a6ae9/src/dinit-env.cc#L20-L26 to this?