Is there any way to open a terminal device file in erlang ?
I am on Solaris and I am trying the following::
Erlang (BEAM) emulator version 5.6 [source] [64-bit] [async-threads:0] [kernel-poll:false]
/xlcabpuser1/xlc/abp/arunmu/Dolphin/ebin
Eshell V5.6 (abort with ^G)
1> file:open("/dev/pts/2",[write]).
{error,eisdir}
2> file:open("/dev/null",[write]).
{ok,}
3>
As can be seen above the erlang file driver has no problem in opening a null fle but does not open a terminal device file!!
Unable to come to a conclusion as the file driver is able to open a null file.
Is there any other way to open terminal device files ?
Thanks
Update: I was able to work around the limitation described below using a port. For example, here is a sample program that prints "hello world" to /dev/stdout:
-module(test).
-export([main/1]).
main(X) ->
P = open_port({spawn, "/bin/cat >/dev/stdout"}, [out]),
P ! {self(), {command, "hello world"}}.
This is a bit inconvenient because a port doesn't act like a regular file, but at least it's one way to get the job done.
In efile_openfile() (in erts/emulator/drivers/unix/unix_efile.c) there is the following code:
if (stat(name, &statbuf) >= 0 && !ISREG(statbuf)) {
#if !defined(VXWORKS) && !defined(OSE)
/*
* For UNIX only, here is some ugly code to allow
* /dev/null to be opened as a file.
*
* Assumption: The i-node number for /dev/null cannot be zero.
*/
static ino_t dev_null_ino = 0;
if (dev_null_ino == 0) {
struct stat nullstatbuf;
if (stat("/dev/null", &nullstatbuf) >= 0) {
dev_null_ino = nullstatbuf.st_ino;
}
}
if (!(dev_null_ino && statbuf.st_ino == dev_null_ino)) {
#endif
errno = EISDIR;
return check_error(-1, errInfo);
#if !defined(VXWORKS) && !defined(OSE)
}
#endif
}
This code (confusingly) returns the EISDIR error if the file is not a regular file (which is the ISREG(statbuf) check), unless the file specifically is /dev/null. The file(3) documentation states:
eisdir :
The named file is not a regular file. It may be a directory, a
fifo, or a device.
so it's actually documented to do that. I'm not sure why that restriction exists, though—perhaps it's got something to do with performance because device drivers might block for more time than an ordinary file generally will.
Related
I'm writing a small C library that reads gzip files passed as FILE *. I'm using zlib's gzdopen() to open the file file descriptor:
int zOpenCloseTest(FILE *const plainFile) {
gzFile file = gzdopen(fileno(plainFile), "rb");
if(file == NULL) {
goto error;
}
if(gzclose_r(file) != Z_OK) {
goto error;
}
return 0;
error:
// gzdopen does not close fd if it fails
fclose(plainFile);
return -1;
}
int main() {
FILE *const file = fopen("test.xp", "rb");
if(file == NULL) {
return -1;
}
if(zOpenCloseTest(file) < 0) {
return -2;
}
return 0;
}
The zlib manual states:
File descriptors are obtained from calls like open, dup, creat, pipe or fileno (in the file has been previously opened with fopen). [...] The next call of gzclose on the returned gzFile will also close the file descriptor fd, just like fclose(fdopen(fd), mode) closes the file descriptor fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd, mode);. [...] If you are using fileno() to get the file descriptor from a FILE *, then you will have to use dup() to avoid double-close()ing the file descriptor. Both gzclose() and fclose() will close the associated file descriptor, so they need to have different file descriptors.
I don't want to keep the file opened, so I create no duplicates.
The mentioned code creates a Leak_StillReachable error on line 21 (fopen) when checked with Valgrind memcheck (--leak-check=full --show-leak-kinds=all --track-origins=yes --vgdb=no --track-fds=yes).
On windows, it crashes with a "Debug assertion failed!" (_osfile(fh) & FOPEN) close.cpp line 49 error box.
Both are built in "debug mode".
The library is part of a bigger CMake project, and the user can choose whether to build it as a static or shared library. In either case, I want zlib to be statically linked to the library.
I initially thought the crash was due to mismatching run-time library link modes between the library and zlib, but the minimal example shows it's not the case?
If you inspect the CMake output, you can see zlib is being built with /MDd flags, as should be.
(modern) CMake in general defaults to "MultiThreaded$<$CONFIG:Debug:Debug>DLL" for CMAKE_MSVC_RUNTIME_LIBRARY.
I believe I forgot something important about how the file descriptor<->stream relation works.
The code is available on GitHub. The project uses Hunter to setup zlib automatically in a CMake-friendly way.
The source is cross platform, the mre provides definitions to quiet msvc warnings.
You are not doing what the zlib documentation says to do, which you yourself highlighted in the question. Use dup().
I think I'm running the same functionality in a C program as on the console, but I have permissions problems setting a GPIO direction.
These console commands, run as a normal user, work fine:
$ echo 436 > /sys/class/gpio/export
$ echo out > /sys/class/gpio/gpio436/direction
... while this code, in a C program (which I intend to have the same effect), fails:
...
int gpioNumber = 436;
/* Successful open... */
FILE *exportNode = fopen("/sys/class/gpio/export", "w");
if (exportNode == NULL) {
printf("Unable to open /sys/class/gpio/export\n");
return CT_GPIO_FAIL;
}
char buffer[100];
sprintf(buffer,"%i",mGpioNumber);
if (fprintf(exportNode, buffer) != (strlen(buffer)))
{
printf("Error writing to /sys/class/gpio/export\n");
return CT_GPIO_FAIL;
}
fclose(exportNode);
/* At this point the node /sys/class/gpio/gpio436 exists - I can see it in the console. */
// Set the pin to be input or output by writing "out" to /sys/class/gpio/gpio[xx]/direction
// I think this is the same file as gets opened in the second shell command.
printf("About to open direction.\n");
sprintf(buffer,"/sys/class/gpio/gpio%i/direction",mGpioNumber);
/* This line fails! */
FILE *directionNode = fopen(buffer, "w");
/* .. and the error is reported. */
if (directionNode == NULL) {
int errnum = errno;
printf("Unable to open %s. Last error: %s\n",buffer,strerror(errnum));
return CT_GPIO_FAIL;
}
This is the error report:
Unable to open /sys/class/gpio/gpio436/direction. Last error: Permission denied
If the direction node hasn't been created, I get a different error, so the reported permissions error seems to be accurately reported.
I run the program from the same console as the shell commands (after unexporting the GPIO, but without any other intervening actions. The program runs without error using sudo.
Why aren't the permissions issues the same inside this C program run from the console, as they are in shell commands? Am I missing a simple code error?
This is all running on a Jetson Xavier NX, Ubuntu 18.04.4 LTS
Well ... It does work as written, with the addition of a few milliseconds of sleep between "export" and trying to open the direction node. Apparently the creation of the direction node isn't synchronous in the write to export, at least on this system.
I apologize for the noise.
I've been dealing with a problem for a few weeks now updating 20 year code that needs to be system independent (work on both Linux and Windows). It involves Time-of-Check, Time-of-Use (TOCTOU) issues. I made a thread here, but it didn't go very far, and after ruminating on it for a while and searching deeper into the problem, I think I understand my question a bit better. Maybe I can ask it a bit better too...
From what I've read, the code needs to check if the file exists, if it is accessible, open the file, do some operations and finally close the file. It seems the best way to do this is a call to lstat(), a call to fopen(), a call to fstat() (to rule out the TOCTOU), and then the operations and closing the file.
However, I've been lead to believe that lstat() and fstat() are POSIX defined, not C Standard defined, ruling out their use for a system agnostic program, much in the same way open() shouldn't be used for cross-compatibility. How would you implement this?
If you look at my first post, you can see the developer from 20 years ago used the C preprocessor to cut the code into cross-compatible parts, but even if I did that, I wouldn't know what to replace lstat() or fstat() with (their windows counterparts).
Edit: Added abreviated code to this post; if something is unclear please go to the original post
#ifdef WIN32
struct _stat buf;
#else
struct stat buf;
#endif //WIN32
FILE *fp;
char data[2560];
// Make sure file exists and is readable
#ifdef WIN32
if (_access(file.c_str(), R_OK) == -1) {
#else
if (access(file.c_str(), R_OK) == -1) {
#endif //WIN32
char message[2560];
sprintf(message, "File '%s' Not Found or Not Readable", file.c_str());
throw message;
}
// Get the file status information
#ifdef WIN32
if (_stat(file.c_str(), &buf) != 0) {
#else
if (stat(file.c_str(), &buf) != 0) {
#endif //WIN32
char message[2560];
sprintf(message, "File '%s' No Status Available", file.c_str());
throw message;
}
// Open the file for reading
fp = fopen(file.c_str(), "r");
if (fp == NULL) {
char message[2560];
sprintf(message, "File '%s' Cound Not be Opened", file.c_str());
throw message;
}
// Read the file
MvString s, ss;
while (fgets(data, sizeof(data), fp) != (char *)0) {
s = data;
s.trimBoth();
if (s.compare( 0, 5, "GROUP" ) == 0) {
//size_t t = s.find_last_of( ":" );
size_t t = s.find( ":" );
if (t != string::npos) {
ss = s.substr( t+1 ).c_str();
ss.trimBoth();
ss = ss.substr( 1, ss.length() - 3 ).c_str();
group_list.push_back( ss );
}
}
}
// Close the file
fclose(fp);
}
The reliable way to check whether the file exists and can be opened is to try opening it. If it was opened, all was OK. If it was not opened, you can think about spending time to analyze what went wrong.
The access() function formally asks a different question from what you think; it asks 'can the real user ID or the real group ID access the file', but the program will use the effective user ID or the effective group ID to access the file. If your program is not running SUID or SGID, and was not launched from a program running SUID or SGID — and that's the normal case — then there's no difference. But the question is different.
The use of stat() or
lstat() doesn't seem helpful. In particular, lstat() only tells you whether you start at a symlink, but the code doesn't care about that.
Both the access() and the stat() calls provide you with TOCTOU windows of vulnerability; the file could be removed after they reported it was present, or created after they reported it was absent.
You should simply call fopen() and see whether it works; the code will be simpler and more resistant to TOCTOU problems. You might need to consider whether to use open() with all its extra controls (O_EXCL, etc), and then convert the file descriptor to a file pointer (fdopen()).
All of this applies to the Unix side.
The details will be different, but on the Windows side, you will still be best off trying to open the file and reacting appropriately to failure.
In both systems, make sure the options provided to the open function are appropriate.
Language: C
OS: Ubuntu
I'm simply trying to create a FIFO named pipe using the command:
state = mknod("pipe.txt", S_IFIFO | 0666, 0);
the problem is i always get the state's value to be -1 (meaning it has failed) instead of 0.
perror returns 'pipe.txt: File exists'
i have no idea how should i debug such issue or what could be the reason, hope anyone code guide me what's wrong.
(note: the file pipe.txt exist on same path as source file.)
Read: int mknod(const char *path, mode_t mode, rdev_t dev_identifier);
General Description:
Creates a new character special file or FIFO special file (named pipe), with the path name specified in the path argument.
If file already exists then it will fails with error: File exists
To avoid this error, remove(unlink()) the file, As I am doing in my below code(read comment):
int main() {
char* file="pipe.txt";
unlink(file); // Add before mknod()
int state = mknod(file, S_IFIFO | 0666, 0);
if(state < 0){
perror("mknod() error");
}
return 0;
}
You should examine errno to see what the error is but it's probably EEXIST since I believe that's what happens if the file already exists.
From the Linux documentation for mknod:
If pathname already exists, or is a symbolic link, this call fails with an EEXIST error.
However, if the file already exists and is the pipe you created in an earlier run, you can safely reopen it. All mknod (and its often preferred cousin, mkfifo) does is actually create the FIFO, you still have to open it at both ends to get the data transfer happening.
I have a simple task to accomplish with this routine where, all it has to do is, open the file, append data from a buffer & close.
I am using 'open' & 'write' for that purpose on a linux machine. Although the return code after 'write()' is positive, the file size does not increase and it is always empty. I ma pulling my hair to figure out what the issue with the below code. Thought some fresh eyes can shed some light.
#define BIT_Q_FILE ".\\bitq.dat"
int BQWrite(void *p)
{
int fd ;
int rc = -1 ;
fd = open(BIT_Q_FILE, O_RDWR | O_APPEND ) ;
if (fd < 0)
return -1;
memset(&BITQBuff,0,sizeof(typeBITQFile));
memcpy(&BITQBuff.pBitQueue,p,sizeof(typeBITQueue));
rc = write(fd, &BITQBuff,sizeof(typeBITQFile)) ;
close(fd) ;
if(rc!=sizeof(typeBITQFile))
{
return -1;
}
rc = sizeof(typeBITQueue);
return rc ;
}
I got your problem right here:
#define BIT_Q_FILE ".\\bitq.dat"
You've hit a trifecta of Windows-to-Unix porting gotchas:
The directory separator on Unix is /, not \.
But Unix is perfectly happy to let you put \ in the middle of a file name. (The only bytes — and I really mean bytes, not characters — that cannot appear in a pathname component are those with the values 0x2F and 0x00.)
Unix is also perfectly happy to let a file name begin with a dot; however, by default ls does not print any file names that begin with a dot.
So you are expecting data to be written to a file named bitq.dat in the current directory, but it is actually being written to a file named .\bitq.dat, still in the current directory. That file is hidden by default, so it looks like the data is disappearing into thin air. ls -a will reveal the hidden file, and rm .\\bitq.dat will delete it. To fix your code, just change the define to
#define BIT_Q_FILE "bitq.dat"
It is not necessary to put a leading ./ on the path passed to open.
This may not be the only problem with your code, but I don't see anything else obviously wrong. If you need more help, please post a new question with a complete, minimal test program that people can compile and run for themselves.
Try change
#define BIT_Q_FILE ".\\\bitq.dat"
to
#define BIT_Q_FILE "./bitq.dat"