Running a command inside a lxc container using C - c

'm trying to use the lxc C API to execute a command inside a container, and hopefully later make it to also prints the results of the command on the stdout/stderr. But so far, I can't get further than creating a simple file in /root/a. Each time I run the code, it does nothing inside the container. I can manually create the file, and the container is defined and running during the execution of the code. And also I'm running it as root since I can't access the container otherwise.
#include <sys/wait.h>
#include <stdio.h>
#include <lxc/lxccontainer.h>
int lxc_exec(char *prog, char *argv[])
{
struct lxc_container *c;
lxc_attach_options_t opt = LXC_ATTACH_OPTIONS_DEFAULT;
lxc_attach_command_t cmd;
int ret = -1;
int status;
pid_t pid;
const char *lxc = "/var/lib/container_test/lxc";
const char *name = "container_test";
c = lxc_container_new(name, lxc);
cmd = (lxc_attach_command_t)
{
.program = prog,
.argv = argv
};
ret = c->attach(c, lxc_attach_run_command, &cmd, &opt, &pid);
// this evaluates to true
if (ret >= 0)
pid = waitpid(pid, &status, 0);
return ret;
}
int main(int argc, char *argv[])
{
char program = "/bin/touch";
char *argv[] = {"/home/a"};
int ret;
ret = lxc_exec(program, argv);
// Returns: Return code is 65280;
printf("Return code is %d", ret);
}

So I've found my answer in the end.
argv also needs to contain the program name. Even if I also tried that, there was another small thing I've missed. There needs to be a NULL element in the array to work.
I've edited my function lxc_exec to only accept an char array, and when I'm building the command struct I did:
command.program = argv[0];
command.argv = argv;
When calling the function it's now been called as
char *args[] = {'/bin/touch', '/home/a', NULL};
int ret = lxc_exec(args);

Related

How can I fork&exec bash shell in C?

Trying to create a new bash shell in C and bring it to the user, this is my code:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char* secretpass = "password";
char password[50];
printf("%s", "Password: ");
fgets(password, 50, stdin);
password[strcspn(password, "\n")] = 0;
if (!strcmp(password, secretpass)){
pid_t pid = fork();
if (pid == 0){
execl("/bin/bash", "bash", NULL);
}
}
return 0;
}
After running the code (ELF), i get a new bash shell in ps but it's not my shell because echo $$ brings the first shell, what can I do to get the new shell to screen? kernel module will help?
EDIT:
edited my code for more help, /dev/chardev is a char device that come up with the boot process, the driver is also 0666 (.rw.rw.rw.) writable for everyone, the system(cmd) says at there is no permission at console, even if I do the command myself after execve.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#define MAX 50
#define USERNAME 2
int main(int argc, char const *argv[])
{
// Declare variables.
const char* username = argv[USERNAME];
char* password = (char*)calloc(MAX, sizeof(char));
char* cmd = (char*)calloc(5 * MAX, sizeof(char));
char* secretpass = "password";
printf("%s", "Password: ");
fgets(password, MAX, stdin);
password[strcspn(password, "\n")] = 0;
if (!strcmp(password, secretpass)){
int err;
struct passwd* pw_user = getpwnam(username);
//printf("-%s-%s-%d-%d-%s-%s-%s-\n", pw_user->pw_name, pw_user->pw_passwd,
//pw_user->pw_uid, pw_user->pw_gid, pw_user->pw_gecos,
//pw_user->pw_dir, pw_user->pw_shell);
if ( (err = fchown(0, pw_user->pw_uid, pw_user->pw_gid) ) != 0)
printf("%s %d\n", "fchown error", err);
if ( (err = setpgid(0, 0) ) != 0)
printf("%s %d\n", "setpgid error", err);
if ( (err = tcsetpgrp(0, getpid()) ) != 0)
printf("%s %d\n", "tcsetpgrp error", err);
if ( (err = chdir(pw_user->pw_dir) ) != 0)
printf("%s %d\n", "chdir error", err);
if ( (err = setgid(pw_user->pw_gid) ) != 0)
printf("%s %d\n", "setgid error", err);
if ( (err = setuid(pw_user->pw_uid) ) != 0)
printf("%s %d\n", "setuid error", err);
sprintf(cmd, "%s \"%d %d %d\" %s", "echo", pw_user->pw_uid, pw_user->pw_gid, getpid(), "> /dev/chardev");
system(cmd);
const char *args[] = {"bash", "--rcfile", "/etc/bashrc", NULL};
char LOGNAME[MAX];
char HOME[MAX];
char USER[MAX];
sprintf(LOGNAME, "%s%s", "LOGNAME=", pw_user->pw_name);
sprintf(HOME, "%s%s", "HOME=",pw_user->pw_dir);
sprintf(USER, "%s%s", "USER=", pw_user->pw_name);
const char *env[] = {"SHELL=/bin/bash", LOGNAME, HOME, USER, "IFS= ","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", "TTY=tty1", NULL}; /* need to generate these; TTY is passed to you */
execve("/bin/bash", args, env);
}
else
execl("/bin/login", "login", NULL);
return 0;
}
always setpgid error and if username isn't root there are also setuid and chdir errors.
From the comments: you're trying to write a login program.
Ok. That's a bit more, and you're going about this all the wrong way. We don't want to fork at all. Let init worry about waiting. Anyway, we get to write a long sequence here:
int targetuid = ... ; /* You need a strategy for getting this */
int targetgid = ... ; /* You need a strategy for getting this */
const char *homdir = ... ; /* You need a strategy for getting this */
if (!strcmp(password, secretpass)){
/* Start up the user's shell */
fchown(0, targetuid, targetgid);
setpgid(0, 0);
tcsetpgrp(0, getpid());
chdir(homedir);
setgid(targetgid);
setuid(targetuid);
const char *args[] = {"-bash", NULL};
const char *env[] = {"SHELL=/bin/bash", "LOGNAME=...", "HOME=...", "USER=...", IFS="...", PATH=/bin:/usr/bin", "TERM=...", NULL }; /* need to generate these; TERM is passed to you */
execve("/bin/bash", args, env);
}
This is very much involved and I actually don't recommend this unless you really have to. I learned a ton when I tried this but it took forever to get it working right.
Particular subpoints: 1) The tty device needs to be owned by the user after a successful login. Thus the fchown(0, ...) call to give ownership to the user. 2) The chdir() first is traditional; you could reverse the order if you wanted to but I don't see why. 3) Starting the shell with a leading - in argv0 tells the shell that it's a login shell. Check in ps -f and you can see this.
I picked up your new code; it actually looks pretty good. The only mistake I can spot is my own; the variable is TERM not TTY (now corrected in my sample above) and the best place to get its value is getenv(). On running your code I only had to make only one correction; that is putting the -bash back. The only error it spits out is the one about chardev; what is chardev?
I guess your failures aren't in this code at all but rather in your kernel.
Info from chat: OP has a custom kernel with a custom /dev/chardev; I can't explain the failures as the code works for me. There may or may not be other changes to the kernel.

Pipe "bad address" on pipe open

So, im trying to start a webserver that uses pipes to comunicate between process.
I was thinking to make a struct named ctx to send other info also.
My code looks like this:
webserver.h
typedef struct
{
int pipefd[2];
} ctx_t;
webserver.c
int main(int argc, char *argv[]){
ctx_t *ctx = {0};
if(pipe(ctx->pipefd) == -1){
perror("ctx pipe error");
exit(EXIT_FAILURE);
}
...
...
}
Output: "ctx pipe error: Bad address"
if instead i declare my program like this, i have no error and the programs continue
webserver.h
int pipefd[2];
webserver.c
int main(int argc, char *argv[]){
if(pipe(pipefd) == -1){
perror("ctx pipe error");
exit(EXIT_FAILURE);
}
...
...
}
Any ideas why i cant open the pipe inside a struct? I still havn't made any forks in the main program.
Thanks.
You're passing a null pointer to a function (system call) pipe() that doesn't accept null pointers. Don't do that!
ctx_t *ctx = {0};
That sets ctx to a null pointer, albeit somewhat verbosely (the braces are not necessary, though they're not harmful). You need to allocate the ctx_t structure somewhere before trying to use it.
Use:
cts_t ctx = { { 0, 0 } };
and:
if (pipe(ctx.pipefd) != 0)
…report error etc…
Using == -1 is also OK.

How to cast char* to char *const* in C

I am trying to use the execv() function.
I am trying to pass in my argument command to the left side.
execv(file,arguments);
I am using a char * to parse the incoming user input for my shell.
The second argument of execv takes a char * const*.
Is there a way I can cast a char * const to a char * const*?
I try this below,
char * arg;
char *const a[] = (char *const)arg;
error: invalid initializer
char *const a[] = (char *const)arg;
^
But it does not work and gives me errors.
Help would be apprecieated.
The error in char *const a[] = (char *const)arg; is not due to an improper conversion. It is because char *const a[] declares an array, and the initializers for an array must be in braces1, { … }, but you have specified just one initializer without braces.
Furthermore, the argv parameter to execv should be an array of pointers in which the first points to a string containing the file name of the program being executed (this is by convention, not required) and the last is a null pointer. Thus, your definition of a ought to be something like:
char * const a[] = { FileNameOfProgram, arg, NULL };
Footnote
1 Except when a string literal is used to initialize an array, but that is not the case here.
You're trying to initialize an array. Instead of doing this,
char * arg;
char *const a[] = (char *const)arg;
do this:
char * arg;
char *const a[] = {(char *const)arg};
It's quite normal to do an execv after eliminating the command name and some of the first parameters. For example, if you have some code like (you had better to post a complete and verifiable example) let's assume you are doing something like this (if you want an example, look for the xargs(1) manpage, you have a command, and after processing the options and their parameters, you want to eliminate all of them, and execute the rest as if it was a command line, e.g. I have a command to execute repeatedly a command, delaying some specified time, like:
cont -t 0.1 -- df -k .
I use <getopts.h> to process the options of my cont program, then execute repeatedly the command df -k. Options allow to show a version for the program, to specify timeout, be verbose, or the number of times to execute the command. I wrote it just now, to show you how to do it (the example includes fork(2) use, execvp(2) and redirection to capture the output of the command to be able to go back to the origin, once known the number of lines we have received, the program uses an ANSI escape to move the cursor back to the beginning.)
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define F(_fmt) "%s:%d: " _fmt, __FILE__, __LINE__
#define FLAG_VERBOSE (1<<0)
#define FLAG_VERSION (1<<1)
#define FLAG_DELAY (1<<2)
#define FLAG_NTIMES (1<<3)
int flags = 0;
useconds_t delay = 1000000;
size_t ntimes;
void doVersion(void)
{
fprintf(stderr,
"cont: v1.0\n"
"(C) Luis Colorado. All rights reserved.\n"
"License: BSD\n");
exit(EXIT_SUCCESS);
}
ssize_t loop(int argc_unused, char **argv)
{
int fd[2];
int res = pipe(fd);
res = fork();
if (res < 0) {
fprintf(stderr,
F("fork: ERROR %d: %s\n"),
errno,
strerror(errno));
return -1;
} else if (res == 0) { /* child */
close(fd[0]); /* not going to use it */
dup2(fd[1], 1); /* redirect output to pipe */
close(fd[1]);
execvp(argv[0], argv);
fprintf(stderr,
F("execv: ERROR %d: %s\n"),
errno, strerror(errno));
return -1;
} else { /* parent */
pid_t cld_pid = res;
close(fd[1]); /* no writing to the pipe */
FILE *f = fdopen(fd[0], "rt"); /* just reading */
int c;
size_t lines = 0;
while((c = fgetc(f)) != EOF) {
if (c == '\n') lines++;
putc(c, stdout);
}
wait(NULL);
return lines;
}
} /* loop */
int main(int argc, char **argv)
{
int opt;
float t;
while ((opt = getopt(argc, argv, "t:Vvn:")) >= 0) {
switch(opt) {
case 't': flags |= FLAG_DELAY;
t = atof(optarg);
break;
case 'V': flags |= FLAG_VERSION;
break;
case 'v': flags |= FLAG_VERBOSE;
break;
case 'n': flags |= FLAG_NTIMES;
ntimes = atoi(optarg);
break;
/* ... */
}
}
if (flags & FLAG_VERSION)
doVersion();
/* the next pair of sentences is like `shift optind' in the shell. */
/* trick, don't move the parameters, just move the pointer */
argc -= optind; /* adjust the number of parameters. */
argv += optind; /* advance the pointer to the proper place */
/* NOW, argc && argv are identical to the original ones, but lacking the
* first `optind' argument strings. As the original string array ended
* in a NULL, there's no need to construct it from allocating memory.
* Anyway, we're not going to use after it's consumed in main(). */
if (flags & FLAG_VERBOSE) {
char *sep = "About to execute: ";
int i;
for (i = 0; i < argc; i++) {
fprintf(stderr, "%s%s", sep, argv[i]);
sep = " ";
}
fprintf(stderr, "\n");
}
if (flags & FLAG_DELAY) {
delay = t * 1.0E6;
}
size_t total_lines = 0;
ssize_t n = 0;
while(!(flags & FLAG_NTIMES) || ntimes--) {
/* move up as many lines as input from subcommand */
if (n) printf("\r\033[%ldA#\b", n);
n = loop(argc, argv);
if (n < 0) {
/* we have already written the error */
exit(EXIT_FAILURE);
}
usleep(delay);
total_lines += n;
}
if (flags & FLAG_VERBOSE) {
fprintf(stderr,
F("Total lines: %lu\n"),
total_lines);
}
exit(EXIT_SUCCESS);
}
you can download a complete version of this program from Github

dup2 not switching to file?

I'm trying to learn dup2 and switch the stdout to a file rather than terminal. This is the example that works everywhere but not sure why it is not working for me. I don't think I need a fork() because I don't need a different process for execute just the print statement in file.
Where function is called:
int main(int argc, char **argv){
char *something = "hello";
saveHistoryToFile(something);
}
//This is the function. There is a file names history .txt
void saveHistoryToFile(char *history){
int fw = open("history.txt",O_WRONLY | O_APPEND);
dup2(fw, 1);
printf("%s", history);
}
THE ERROR: it prints into terminal not file!
Your code with error checking:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int saveHistoryToFile(char *history);
int main(int argc, char **argv){
char *something = "hello";
if(0>saveHistoryToFile(something)) return 1;
if(0>fclose(stdout)) return perror("fclose"),-1;
}
int saveHistoryToFile(char *history){
int fw = open("history.txt",O_WRONLY | O_APPEND /*|O_CREAT, 0640*/ );
if (0>fw) return perror("open"),-1;
if (0>dup2(fw, 1)) return perror("dup2"),-1;
if (0>(printf("%s", history))) return perror("printf"),-1;
}
On a first run, I get "open: No such file or directory" because I do not have "history.txt" in my current directory.
If I add it or uncomment the O_CREAT, 0640, it runs fine on my machine.
Of course, you might run into other problems (e.g, EPERM) but the perrors should give you a hint.

Segmentation fault from hook on open()

I am trying to create a hook on the system function open(). I've done this along the following lines.
I created a wrapper library with the following:
extern int mocked_open(const char* fn, int flags, va_list args);
int open(const char* fn, int flags, ...)
{
int r = -1;
va_list args;
va_start(args, flags);
r = mocked_open(fn, flags, args);
va_end(args);
return r;
}
I compile this into libwrapper.so, which I load using LD_PRELOAD.
The implementation of mocked_open() is as follows (I use the CPPUtest framework):
int mocked_open(const char* fn, int flags, va_list args)
{
if (strncmp(fn, test_device_id, 11) == 0)
{
return mock().actualCall("open").returnValue().getIntValue();
}
else
{
int r = -1;
int (*my_open)(const char*, int, ...);
void* fptr = dlsym(RTLD_NEXT, "open");
memcpy(&my_open, &fptr, sizeof(my_open));
if (flags & O_CREAT)
{
r = my_open(fn, flags, va_arg(args, mode_t));
}
else
{
r = my_open(fn, flags);
}
return r;
}
}
The test_device_id is a simple string ("test_device"), which I hope is not used elsewhere.
During running the tests the executable crashes with a segmentation fault. I've traced this down to the GCC profiling functionality, which wants to open/create a bunch of .gcda files and calls open() for this.
After some debugging with strace (per suggestion below), I found that the line r = my_open(fn, flags, va_arg(args, mode_t)); is indeed the culprit. It is being called recursively, or so it seems: I see a lot of calls to this line, without the function returning. Then a segfault. The file being opened is the corresponding .gcda file (for profiling). In fact, the segfault only occurs with profiling enabled.
Try this
typedef int (*OpenFunction)(const char* fn, int flags, ...);
then
OpenFunction function;
void **pointer;
pointer = (void **)&function;
*pointer = dlsym(RTLD_NEXT, "open");
this is a complete working example
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
typedef int (*OpenFunction)(const char* fn, int flags, ...);
int main(int argc, char **argv)
{
OpenFunction function;
void *dl;
int fd;
void **pointer;
if (argc < 2)
return -1;
pointer = (void **)&function;
*pointer = dlsym(RTLD_NEXT, "open");
fd = function(argv[1], O_RDONLY);
if (fd != -1)
{
printf("file opened succesfully\n");
close(fd);
}
else
{
printf("%s: cannot open the file\n", strerror(errno));
}
return 0;
}
When you compile with gcov profiling enabled, the compiler inserts extra code into your functions, to keep track of which code has been executed. In rough pseudocode, that inserted code will do (among other things):
if (!output_file_has_been_opened) {
fd = open(output_filename, ...);
check_ok(fd);
output_file_has_been_opened = TRUE;
track_coverage();
}
... so if the output file hasn't yet been successfully opened (as at the start of your program), it will attempt to open it. Unfortunately, in this case that will call your mocked open() function - which has the same inserted code; since the file still hasn't been successfully opened, and since the gcov code isn't aware that there's something unusual going on, it will attempt the open() call again - and this is what's causing the recursion (and eventual segfault, once the stack is exhausted).
You are passing va_list incorrectly. Try following hope it helps
r = my_open(fn, flags, args);
For more info
http://c-faq.com/varargs/handoff.html

Resources