Why are GPIO permissions different in C program from console? - c

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.

Related

How to handle a c++ file compile inside a C server?

I need to make a server that:
1) compiles a c++ file and saves the errors in a file if they exist;
2) if there are no errors i must run the a.out file and extract the output in another file.
The problem resides in the first one.
In order to compile and extract errors i used more methods:
1) system("g++ file.cpp &> err.txt") - not working: it prints the errors in the console but the file remains empty
2) popen - Reference link: C: Run a System Command and Get Output? : the only difference is that i opened another file and instead of printing in the console i used fprintf to write in file.
I forgot to add that the first method works if written as command in console but inside the server is problematic.
// This code is to show what i have already tried and if you find any
// syntax errors like ; or ' pls ignore them as i couldn't copy the code
// from the docker console. Thank you very much!
//1
system("g++ file.cpp &> err.txt");
if( access( "a.out", F_OK ) != -1 ) {
system("./a.out > output.txt");
//2
FILE *f;
char buff[200];
f = popen("g++ file.cpp", "r");
if (f == NULL) {
printf("Failed to run command\n" );
exit(1);
}
FILE *o;
o = fopen("err.txt", "w");
while (fgets(buff, sizeof(buff)-1, f) != NULL) {
fprintf(o, "%s", buff);
}
fclose(o);
fclose(f);
I expected the errors to be written in the err.txt not to be printed in console and in all the above examples it prints in the console and err.txt remains empty.
You can accomplish it the old-school way:
fork() and in the child:
Open a file for storing standard output and another one for errors.
Use dup2(oldfd, newfd) to duplicate the two files' descriptors to stdout, and stderr respectively.
Invoke execlp with gcc and its arguments.
In the parent process you can add waitpid call to wait for the child to finish.
Ok so in the end apparently this one was pretty close: system("g++ file.cpp &> err.txt");
The solution is: system("g++ file.cpp > err.txt 2>&1");

How detect assigned terminal device for interactive work

I am writing pager pspg. There I have to solve following issue. After reading from stdin I should to reassign stdin from previous reading from pipe to reading from terminal.
I used
freopen("/dev/tty", "r", stdin)
But it doesn't work, when pager was used from command what was not executed directly
su - someuser -c 'export PAGER=pspg psql somedb'
In this case, I got a error: No such device or address.
I found a workaround - now, the code looks like:
if (freopen("/dev/tty", "r", stdin) == NULL)
{
/*
* try to reopen pty.
* Workaround from:
* https://cboard.cprogramming.com/c-programming/172533-how-read-pipe-while-keeping-interactive-keyboard-c.html
*/
if (freopen(ttyname(fileno(stdout)), "r", stdin) == NULL)
{
fprintf(stderr, "cannot to reopen stdin: %s\n", strerror(errno));
exit(1);
}
}
What is a correct way to detect assigned terminal device in this case?
But this workaround is not correct. It fixed one issue, but next is comming. When someuser is different than current user, then reopen fails with error Permission denied. So this workaround cannot be used for my purposes.
What less does in this situation is fall back to fd 2 (stderr). If stderr has been redirected away from the tty, it gives up on trying to get keyboard input, and just prints the whole input stream without paging.
The design of su doesn't allow for anything better. The new user is running a command on a tty owned by the original user, and that unpleasant fact can't be entirely hidden.
Here's a nice substitute for su that doesn't have this problem:
ssh -t localhost -l username sh -c 'command'
It has a little more overhead, of course.
On the end I used pattern that I found in less pager, but modified for using with ncurses:
First I try to reopen stdin to some tty related device:
if (!isatty(fileno(stdin)))
{
if (freopen("/dev/tty", "r", stdin) != NULL)
noatty = false;
/* when tty is not accessible, try to get tty from stdout */
else if (freopen(ttyname(fileno(stdout)), "r", stdin) != NULL)
noatty = false;
else
{
/*
* just ensure stderr is joined to tty, usually when reopen
* of fileno(stdout) fails - probably due permissions.
*/
if (!isatty(fileno(stderr)))
{
fprintf(stderr, "missing a access to terminal device\n");
exit(1);
}
noatty = true;
fclose(stdin);
}
}
else
noatty = false;
When I have not tty and cannot to use stdin, then I am using newterm functions, that allows to specify input stream:
if (noatty)
/* use stderr like stdin. This is fallback solution used by less */
newterm(termname(), stdout, stderr);
else
/* stdin is joined with tty, then use usual initialization */
initscr();

How to use the pulseaudio API as root?

I am currently trying to use the pulseaudio simple API to record microphone data from my USB sound card with my raspberry pi 3. I used the example program parec-simple from pulseaudio in my own program and it works quite nice.
The program i used this code for is accessing gpio's so i need to run this as root. However, when i try to execute the program as root, i get the following errors:
Home directory not accessible: Permission denied
W: [pulseaudio] core-util.c: Failed to open configuration file '/root/.config/pulse//daemon.conf': Permission denied
W: [pulseaudio] daemon-conf.c: Failed to open configuration file: Permission denied
pa_simple_new() failed: Connection refused
the code is used is the following:
static const pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE,
.rate = 44100,
.channels = 1
};
pa_simple *s = NULL;
int ret = 1;
int error;
/* Create the recording stream */
if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) {
fprintf(stderr, "pa_simple_new() failed: %s\n", pa_strerror(error));
goto finish;
}
while(1)
{
uint8_t buf[BUFSIZE];
/* Record some data ... */
if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
goto finish;
}
/* And write it to STDOUT */
if (loop_write(STDOUT_FILENO, buf, sizeof(buf)) != sizeof(buf)) {
fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno));
goto finish;
}
}
ret = 0;
finish:
if (s)
pa_simple_free(s);
return ret;
I already tried chown pi:pi /home/pi as suggested here to try to fix it but it doesn't work. changing the owner of /home/pi from pi to root didn't work for me either.
I also tried a clean reinstall of pulseaudio but unfortunately it didn't fix it.
So what can i do to fix these errors?
If you need to run your program as user root, then you must impersonate root. I don't know if pulseaudio looks at the username in order to find configuration files, or it looks at the $HOME variable. In the second case, maybe that by setting HOME to the home of a "working" user helps.
Anyway what you told about the situation is clear: pulseaudio does not find a file:
'/root/.config/pulse//daemon.conf'
Place a correct "daemon.conf" in that directory - probably you can copy it from somewhere (like /home/auser/.config/pulse/daemon.conf).
Consider that directories with name starting with a dot are normally hidden; if using a file manager you must enable "show hidden files", if you use the shell, ls -a can help.
Your first target is to confirm that the file is there, and your program should not complain about a missing/unreadable config file. Then, maybe other errors will show up but, one after another, you can eliminate them.
When you run process with sudo it does not change Home directory to /root - sudo echo $HOME # /home/username. You need to specify HOME directory with by running sudo HOME=/root executable.
When you want to access pulseaudio from root you need to run it system wide with command - sudo pulseaudio --system=true.
Then you will receive an error from pulseaudio:
W: [pulseaudio] protocol-native.c: Denied access to client with invalid authentication data.
Which can be solved by adding root user to audio-pulse group - sudo adduser root pulse-access.

Opening a device file using erlang

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.

Help with opening a file in C on Xcode

I'm trying to open a simple .rtf file called test in C. I'm using Xcode. My code is:
#include <stdio.h>
#include <stdlib.h>
int main (int argc, const char * argv[]) {
FILE *filePtr;
filePtr = fopen("test.rtf", "r");
if (filePtr == NULL) {
fprintf(stderr, "Can't open \"test\"\n");
exit(EXIT_FAILURE);
}
else {
printf("File open successful\n");
int x;
/* read one character at a time until EOF is reached */
while ((x = fgetc(filePtr)) != EOF) {
printf("%c", x);
}
}
fclose(filePtr);
return 0;
}
I have the test.rtf file in the same directory as my Xcode.proj directory. My output is "File open successful", however I do not get anything read from the file. Am I doing this right? Thanks.
There's nothing wrong with that code at all. I tested it (albeit not in Xcode) with a file and the transcript was:
pax> echo hello >test.rtf
pax> ./qq.exe
File open successful
hello
So the obvious think to ask is what happens when you examine test.rtf? Does it actually have any content? Because, when I do:
pax> rm test.rtf ; touch test.rtf
pax> ./qq.exe
File open successful
I get the same behaviour you observe.
Also try renaming it to test2.rtf temporarily and make sure you get the error. It's possible it may be opening a different copy of the file than what you think (this often happens in Visual C since the directory the program runs in is not always what developers think at first).
It looks right.
As for the lack of output, two possibilities:
Are you sure the file has some content? Maybe ls -l test.rtf or dir test.rft
Possibly it has some control characters which cause the terminal to which it is written to suppress output.
Try moving test.rtf to your build directory. If your project is named MyProject, move it to MyProject/build/Debug/.
I can think of two things that could cause this problem. Either there is an error when calling fgetc, or you are getting output that you don't recognize.
fgetc() will return EOF when the end of the file is reached, or an error occurs. To determine if it's an error, just after your while loop try:
if (ferror(filePtr) != 0) printf("error: %d.\n", errno);
A .rtf file is not a plain text file. It likely contains a bunch of formatting information. You are expecting to see "Hello . . . ". but what you may actually see is something like:
{\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf250
{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;}
\margl1440\margr1440\vieww9000\viewh8400\viewkind0
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040
\f0\fs24 \cf0 Hello . . .
And you are just assuming that is GDB output, not your program's output.
Based upon your recent comments, I think you have an empty file test.rtf in the directory your program is run in, and your real test.rtf file is in some other directory. Maybe your fopen() call at some point was fopen("test.rtf", "w"); instead of fopen("test.rtf", "r");, and you later modified it.
To see the directory your program is running in, add the following to your program after the FILE *filePtr; line:
char pwd[512];
if (getcwd(pwd, sizeof pwd) != -1)
printf("In directory %s\n", pwd);
else
fprintf(stderr, "Need bigger buffer, change '512' above\n");
Then, you can open a terminal, do cd <directory>, and test for yourself if the file you want is the file your program is opening.
You probably want this file to be plain text, not rich text. Rich text has a lot of formatting encoded into the file.

Resources