Thread safe, reentrant, async-signal safe putenv - c

I apologise in advance for what will be a bit of a code dump, I've trimmed as much unimportant code as possible:
// Global vars / mutex stuff
extern char **environ;
pthread_mutex_t env_mutex = PTHREAD_MUTEX_INITIALIZER;
int
putenv_r(char *string)
{
int len;
int key_len = 0;
int i;
sigset_t block;
sigset_t old;
sigfillset(&block);
pthread_sigmask(SIG_BLOCK, &block, &old);
// This function is thread-safe
len = strlen(string);
for (int i=0; i < len; i++) {
if (string[i] == '=') {
key_len = i; // Thanks Klas for pointing this out.
break;
}
}
// Need a string like key=value
if (key_len == 0) {
errno = EINVAL; // putenv doesn't normally return this err code
return -1;
}
// We're moving into environ territory so start locking stuff up.
pthread_mutex_lock(&env_mutex);
for (i = 0; environ[i] != NULL; i++) {
if (strncmp(string, environ[i], key_len) == 0) {
// Pointer assignment, so if string changes so does the env.
// This behaviour is POSIX conformant, instead of making a copy.
environ[i] = string;
pthread_mutex_unlock(&env_mutex);
return(0);
}
}
// If we get here, the env var didn't already exist, so we add it.
// Note that malloc isn't async-signal safe. This is why we block signals.
environ[i] = malloc(sizeof(char *));
environ[i] = string;
environ[i+1] = NULL;
// This ^ is possibly incorrect, do I need to grow environ some how?
pthread_mutex_unlock(&env_mutex);
pthread_sigmask(SIG_SETMASK, &old, NULL);
return(0);
}
As the title says, I'm trying to code a thread safe, async-signal safe reentrant version of putenv. The code works in that it sets the environment variable like putenv would, but I do have a few concerns:
My method for making it async-signal safe feels a bit ham-handed, just blocking all signals (except SIGKILL/SIGSTOP of course). Or is this the most appropriate way to go about it.
Is the location of my signal blocking too conservative? I know strlen isn't guaranteed to be async-signal safe, meaning that my signal blocking has to occur beforehand, but perhaps I'm mistaken.
I'm fairly sure that it is thread safe, considering that all the functions are thread-safe and that I lock interactions with environ, but I'd love to be proven otherwise.
I'm really not too sure about whether it's reentrant or not. While not guaranteed, I imagine that if I tick the other two boxes it'll most likely be reentrant?
I found another solution to this question here, in which they just set up the appropriate signal blocking and mutex locking (sick rhymes) and then call putenv normally. Is this valid? If so, it's obviously far simpler than my approach.
Sorry about the large block of code, I hope I've established a MCVE. I'm missing a bit of error checking in my code for brevity's sake. Thanks!
Here is the rest of the code, including a main, if you wish to test the code yourself:
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
// Prototypes
static void thread_init(void);
int putenv_r(char *string);
int
main(int argc, char *argv[]) {
int ret = putenv_r("mykey=myval");
printf("%d: mykey = %s\n", ret, getenv("mykey"));
return 0;
}

This code is a problem:
// If we get here, the env var didn't already exist, so we add it.
// Note that malloc isn't async-signal safe. This is why we block signals.
environ[i] = malloc(sizeof(char *));
environ[i] = string;
It creates a char * on the heap, assigns the address of that char * to environ[i], then overwrites that value with the address contained in string. That's not going to work. It doesn't guarantee that environ is NULL-terminated afterwards.
Because char **environ is a pointer to an array of pointers. The last pointer in the array is NULL - that's how code can tell it's reached the end of the list of environment variables.
Something like this should work better:
unsigned int envCount;
for ( envCount = 0; environ[ envCount ]; envCount++ )
{
/* empty loop */;
}
/* since environ[ envCount ] is NULL, the environ array
of pointers has envCount + 1 elements in it */
envCount++;
/* grow the environ array by one pointer */
char ** newEnviron = realloc( environ, ( envCount + 1 ) * sizeof( char * ) );
/* add the new envval */
newEnviron[ envCount - 1 ] = newEnvval;
/* NULL-terminate the array of pointers */
newEnviron[ envCount ] = NULL;
environ = newEnviron;
Note that there's no error checking, and it assumes the original environ array was obtained via a call to malloc() or similar. If that assumption is wrong, the behavior is undefined.

Related

How am I supposed to successfully achieve buffer overflow?

I am currently tackling on an assignment, where I need to upload exploit.c and target.c onto a ubuntu server, and successfully achieve a buffer overflow attack with exploit onto target. I was provided a shellcode. Now, target.c is not to be altered, just exploit.c. I had to use GDB on exploit.c to force an external breakpoint on foo() from target.c, to figure out the return addresses using info frame.
I was provided with the working shellcode, and minimal instructions.
I am pretty sure I was able to successfully pull the return addresses, but my issue is that I cannot figure out what code to put into exploit.c to have it successfully perform a buffer overflow attack. I was also instructed that one of the return addresses must be input into the exploit code for it to function properly.
I understand that the exploit is trying to call back to the return address, to then push itself into the buffer, so I can obtain access to the shell.
Here is exploit.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "shellcode.h"
// replace this define environment to have the correct path of your own target code
#define TARGET "/*******************"
int main(void)
{
char *args[3];
char *env[2];
char *tmp = NULL;
// Creating an input buffer that can cause buffer overflow in strcpy function in the target.c executable code
int buffSize = 1000;
char buff[buffSize];
// Intialize buffer elements to 0x01
int i;
for (i=0; i < buffSize; i++) buff[i] = 0x01;
// write your code below to fill the 22 bytes shellcode into the buff variable, and
// at the correct location overwrite the return address correctly in order to achieve stack overflow
// Your own code starts here:
strcpy (buff[buffSize-22], shellcode);
// Your code ends here.
// prepare command line input to execute target code
args[0] = TARGET; // you must have already compiled and generated the target executable code first
args[1] = buff; // the first input parameter to the target code (artfully crafted buffer overflow string)
args[2] = NULL;
env[0] = "FOO=bar";
env[1] = NULL;
if (0 > execve(TARGET, args, env))
fprintf(stderr, "execve failed.\n");
return 0;
}
Here is the target.c code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int foo(char* arg)
{
char localBuf[240];
short len = 240;
float var1=2.4;
int *ptr = NULL;
strcpy(localBuf, arg);
printf("foo() finishes normally.\n");
return 0;
}
int kbhit(void)
{
struct timeval tv;
fd_set read_fd;
tv.tv_sec=0; tv.tv_usec=0;
FD_ZERO(&read_fd); FD_SET(0,&read_fd);
if(select(1, &read_fd, NULL, NULL, &tv) == -1)
return 0;
if(FD_ISSET(0,&read_fd))
return 1;
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "target: argc != 2\n");
exit(EXIT_FAILURE);
}
printf("Press any key to call foo function...\n");
while(!kbhit())
;
foo(argv[1]);
return 0;
}
I compiled both target and exploit. Then I ran GDB on exploit, and formed a breakpoint using "break target.c:10". Using Info Frame I was able to obtain the return addresses.
I used strcpy, because it is essentially the only line of code we were taught for this section involving overflow attacks, even though it clearly states in the document "Fill the shell executable code (in the string array shellcode[]) byte-by-
byte into the buff for your modified return address to execute, do not
use strcpy() because shellcode[] is not an ASCII string (and not
copying NULL byte, too)."
Exploit compiles fine, and it runs fine, but it does not give me access to a shell. I was instructed that I would know if it worked, if I was presented with two dollar signs ($$) instead of one ($).
I am a network engineer, and I am not entirely savvy with C, or attacking vulnerabilities in programs, any help would be appreciated. The entire lesson revolves around "stack overflow", but this assignment is called "buffer overflow attack".

Crazy macro hack for handling issues with thread cancellations and cleanup handlers

This is a really long question due to the code snippets and the detailed explanations. TL;DR, are there issues with the macros shown below, is this a reasonable solution, and if not then what is the most reasonable way to solve the issues presented below?
I am currently writing a C library which deals with POSIX threads, and must be able to handle thread cancellation cleanly. In particular, the library functions may be called from threads that were set to be cancellable (either PTHREAD_CANCEL_DEFFERED or PTHREAD_CANCEL_ASYNCHRONOUS canceltype) by the user.
Currently the library functions that interface with the user all begin with a call to pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate), and at each return point, I make sure that a call to pthread_setcancelstate(oldstate, &dummy) is made to restore whatever cancellation settings the thread had previously.
This basically prevents the thread from being cancelled while in the library code, thus ensuring that the global state remains consistent and resources were properly managed before returning.
This method unfortunately has a few drawbacks:
One must be sure to restore the cancelstate at every return point. This makes it somewhat hard to manage if the function has nontrivial control flow with multiple return points. Forgetting to do so may lead to threads that don't get cancelled even after return from the library.
We only really need to prevent cancellations at points where resources are being allocated or global state is inconsistent. A library function may in turn call other internal library functions that are cancel-safe, and ideally cancellations could occur at such points.
Here is a sample illustration of the issues:
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
static void do_some_long_computation(char *buffer, size_t len)
{
(void)buffer; (void)len;
/* This is really, really long! */
}
int mylib_function(size_t len)
{
char *buffer;
int oldstate, oldstate2;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
buffer = malloc(len);
if (buffer == NULL) {
pthread_setcancelstate(oldstate, &oldstate2);
return -1;
}
do_some_long_computation(buffer, len);
fd = open("results.txt", O_WRONLY);
if (fd < 0) {
free(buffer);
pthread_setcancelstate(oldstate, &oldstate2);
return -1;
}
write(fd, buffer, len); /* Normally also do error-check */
close(fd);
free(buffer);
pthread_setcancelstate(oldstate, &oldstate2);
return 0;
}
Here it is not so bad because there are only 3 return points. One could possibly even restructure the control flow in such a way as to force all paths to reach a single return point, perhaps with the goto cleanup pattern. But the second issue is still left unresolved. And imagine having to do that for many library functions.
The second issue may be resolved by wrapping each resource allocation with calls to pthread_setcancelstate that will only disable cancellations during resource allocation. While cancellations are disabled, we also push a cleanup handler (with pthread_cleanup_push). One could also move all resource allocations together (opening the file before doing the long computation).
While solving the second issue, it is still somewhat hard to maintain because each resource allocation needs to be wrapped under these pthread_setcancelstate and pthread_cleanup_[push|pop] calls. Also it might not always be possible to put all resource allocations together, for instance if they depend on the results of the computation. Moreover, the control flow needs to be changed because one cannot return between a pthread_cleanup_push and pthread_cleanup_pop pair (which would be the case if malloc returns NULL for example).
In order to solve both issues, I came up with another possible method that involves dirty hacks with macros. The idea is to simulate something like a critical section block in other languages, to insert a block of code in a "cancel-safe" scope.
This is what the library code would look like (compile with -c -Wall -Wextra -pedantic):
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include "cancelsafe.h"
static void do_some_long_computation(char *buffer, size_t len)
{
(void)buffer; (void)len;
/* This is really, really long! */
}
static void free_wrapper(void *arg)
{
free(*(void **)arg);
}
static void close_wrapper(void *arg)
{
close(*(int *)arg);
}
int mylib_function(size_t len)
{
char *buffer;
int fd;
int rc;
rc = 0;
CANCELSAFE_INIT();
CANCELSAFE_PUSH(free_wrapper, buffer) {
buffer = malloc(len);
if (buffer == NULL) {
rc = -1;
CANCELSAFE_BREAK(buffer);
}
}
do_some_long_computation(buffer, len);
CANCELSAFE_PUSH(close_wrapper, fd) {
fd = open("results.txt", O_WRONLY);
if (fd < 0) {
rc = -1;
CANCELSAFE_BREAK(fd);
}
}
write(fd, buffer, len);
CANCELSAFE_POP(fd, 1); /* close fd */
CANCELSAFE_POP(buffer, 1); /* free buffer */
CANCELSAFE_END();
return rc;
}
This resolves both issues to some extent. The cancelstate settings and cleanup push/pop calls are implicit in the macros, so the programmer only has to specify the sections of code that need to be cancel-safe and what cleanup handlers to push. The rest is done behind the scenes, and the compiler will make sure each CANCELSAFE_PUSH is paired with a CANCELSAFE_POP.
The implementation of the macros is as follows:
#define CANCELSAFE_INIT() \
do {\
int CANCELSAFE_global_stop = 0
#define CANCELSAFE_PUSH(cleanup, ident) \
do {\
int CANCELSAFE_oldstate_##ident, CANCELSAFE_oldstate2_##ident;\
int CANCELSAFE_stop_##ident;\
\
if (CANCELSAFE_global_stop)\
break;\
\
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_##ident);\
pthread_cleanup_push(cleanup, &ident);\
for (CANCELSAFE_stop_##ident = 0; CANCELSAFE_stop_##ident == 0 && CANCELSAFE_global_stop == 0; CANCELSAFE_stop_##ident = 1, pthread_setcancelstate(CANCELSAFE_oldstate_##ident, &CANCELSAFE_oldstate2_##ident))
#define CANCELSAFE_BREAK(ident) \
do {\
CANCELSAFE_global_stop = 1;\
pthread_setcancelstate(CANCELSAFE_oldstate_##ident, &CANCELSAFE_oldstate2_##ident);\
goto CANCELSAFE_POP_LABEL_##ident;\
} while (0)
#define CANCELSAFE_POP(ident, execute) \
CANCELSAFE_POP_LABEL_##ident:\
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_##ident);\
pthread_cleanup_pop(execute);\
pthread_setcancelstate(CANCELSAFE_oldstate_##ident, &CANCELSAFE_oldstate2_##ident);\
} while (0)
#define CANCELSAFE_END() \
} while (0)
This combines several macro tricks that I have encountered before.
The do { } while (0) pattern is used to have a multiline function-like macro (with semicolon required).
The CANCELSAFE_PUSH and CANCELSAFE_POP macros are forced to come in pairs by the use of the same trick as the pthread_cleanup_push and pthread_cleanup_pop using unmatched { and } braces respectively (here it is unmatched do { and } while (0) instead).
The usage of the for loops is somewhat inspired by this question. The idea is that we want to call the pthread_setcancelstate function after the macro body to restore cancellations after the CANCELSAFE_PUSH block. I use a stop flag that is set to 1 at the second loop iteration.
The ident is the name of the variable that will be released (this needs to be a valid identifier). The cleanup_wrappers will be given its address, which will always be valid in a cleanup handler scope according to this answer. This is done because the value of the variable is not yet initialized at the point of cleanup push (and also doesn't work if the variable is not of pointer type).
The ident is also used to avoid name collisions in the temporary variables and labels by appending it as a suffix with the ## concatenation macro, giving them unique names.
The CANCELSAFE_BREAK macro is used to jump out of the cancelsafe block and right into the corresponding CANCELSAFE_POP_LABEL. This is inspired by the goto cleanup pattern, as mentioned here. It also sets the global stop flag.
The global stop is used to avoid cases were there might be two PUSH/POP pairs in the same scope level. This seems like an unlikely situation, but if this happens then the content of the macros is basically skipped when the global stop flag is set to 1. The CANCELSAFE_INIT and CANCELSAFE_END macros aren't crucial, they just avoid the need to declare the global stop flag ourselves. These could be skipped if the programmer always does all the pushes and then all the pops consecutively.
After expanding the macros, we obtain the following code for the mylib_function:
int mylib_function(size_t len)
{
char *buffer;
int fd;
int rc;
rc = 0;
do {
int CANCELSAFE_global_stop = 0;
do {
int CANCELSAFE_oldstate_buffer, CANCELSAFE_oldstate2_buffer;
int CANCELSAFE_stop_buffer;
if (CANCELSAFE_global_stop)
break;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_buffer);
pthread_cleanup_push(free_wrapper, &buffer);
for (CANCELSAFE_stop_buffer = 0; CANCELSAFE_stop_buffer == 0 && CANCELSAFE_global_stop == 0; CANCELSAFE_stop_buffer = 1, pthread_setcancelstate(CANCELSAFE_oldstate_buffer, &CANCELSAFE_oldstate2_buffer)) {
buffer = malloc(len);
if (buffer == NULL) {
rc = -1;
do {
CANCELSAFE_global_stop = 1;
pthread_setcancelstate(CANCELSAFE_oldstate_buffer, &CANCELSAFE_oldstate2_buffer);
goto CANCELSAFE_POP_LABEL_buffer;
} while (0);
}
}
do_some_long_computation(buffer, len);
do {
int CANCELSAFE_oldstate_fd, CANCELSAFE_oldstate2_fd;
int CANCELSAFE_stop_fd;
if (CANCELSAFE_global_stop)
break;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_fd);
pthread_cleanup_push(close_wrapper, &fd);
for (CANCELSAFE_stop_fd = 0; CANCELSAFE_stop_fd == 0 && CANCELSAFE_global_stop == 0; CANCELSAFE_stop_fd = 1, pthread_setcancelstate(CANCELSAFE_oldstate_fd, &CANCELSTATE_oldstate2_fd)) {
fd = open("results.txt", O_WRONLY);
if (fd < 0) {
rc = -1;
do {
CANCELSAFE_global_stop = 1;
pthread_setcancelstate(CANCELSAFE_oldstate_fd, &CANCELSAFE_oldstate2_fd);
goto CANCELSAFE_POP_LABEL_fd;
} while (0);
}
}
write(fd, buffer, len);
CANCELSAFE_POP_LABEL_fd:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_fd);
pthread_cleanup_pop(1);
pthread_setcancelstate(CANCELSAFE_oldstate_fd, &CANCELSAFE_oldstate2_fd);
} while (0);
CANCELSAFE_POP_LABEL_buffer:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &CANCELSAFE_oldstate_buffer);
pthread_cleanup_pop(1);
pthread_setcancelstate(CANCELSAFE_oldstate_buffer, &CANCELSAFE_oldstate2_buffer);
} while (0);
} while (0);
return rc;
}
Now, this set of macros is horrendous to look at and it is somewhat tricky to understand how they work exactly. On the other hand, this is a one-time task, and once written, they can be left and the rest of the project can benefit from their nice benefits.
I would like to know if there are any issues with the macros that I may have overlooked, and whether there could be a better way to implement similar functionality. Also, which of the solutions proposed do you think would be the most reasonable? Are there other ideas that could work better to resolve these issues (or perhaps, are they really non-issues)?
Unless you use asynchronous cancellation (which is always very problematic), you do not have to disable cancellation around malloc and free (and many other POSIX functions). Synchronous cancellation only happens at cancellation points, and these functions are not.
You are abusing the POSIX cancellation handling facilities to implement a scope exit hook. In general, if you find yourself doing things like this in C, you should seriously consider using C++ instead. This will give you a much more polished version of the feature, with ample documentation, and programmers will already have experience with it.

How use atexit() function for free up memory? [duplicate]

I am developing a project in C, and I need to free the allocated memory and also close all the open files before it exits.
I decided to implement a clean function that will do all this stuff and call it with atexit because there are a lot of possible exit scenarios.
The problem is that atexit doesn't allow me to set functions with parameters, so I can't send to clean the pointers that need to be freed in the end of the process.
So I need to declare as global variables every pointer that may need to be freed, and every file that may remaining open in the program? (I already did that but doesn't looks good) or does exist a similar function to atexit that allows to send parameters? or more probably there is another way that I am missing?
Using a static pointer inside a function:
#include <stdio.h>
#include <stdlib.h>
void atexit_clean(void *data);
static void clean(void)
{
atexit_clean(NULL);
}
void atexit_clean(void *data)
{
static void *x;
if (data) {
x = data;
atexit(clean);
} else {
free(x);
}
}
int main(void)
{
int *a = malloc(sizeof(int));
atexit_clean(a);
return 0;
}
Another method using a single global variable: you can store all objects to be freed in an array of pointers or a linked list, this example uses realloc (doesn't check (m/re)alloc for brevity):
#include <stdio.h>
#include <stdlib.h>
static void **vclean;
static size_t nclean;
void atexit_add(void *data)
{
vclean = realloc(vclean, sizeof(void *) * (nclean + 1));
vclean[nclean++] = data;
}
void clean(void)
{
size_t i;
for (i = 0; i < nclean; i++) {
free(vclean[i]);
}
free(vclean);
}
int main(void)
{
int *a, *b, *c;
double *d;
int e = 1;
atexit(clean);
a = &e;
b = malloc(sizeof(int));
atexit_add(b);
c = malloc(sizeof(int));
atexit_add(c);
d = malloc(sizeof(double));
atexit_add(d);
return 0;
}
There is no way to pass any parameters to atexit(), so you're stuck using global variables.
When your program terminates normally, through exit() or by returning from main(), it will automatically flush and close any open streams and (under most operating systems) free allocated memory. However, it is good practice to explicitly clean up your resources before the program terminates, because it typically leads to a more structured program. Sometimes the cleanest way to write your program is to just exit and leave the cleanup to the implementation.
But be warned that you should always check the return value of fclose(). See "What are the reasons to check for error on close()?" for an anecdote about what could happen when you don't.

Exists a way to free memory in atexit or similar without using global variables?

I am developing a project in C, and I need to free the allocated memory and also close all the open files before it exits.
I decided to implement a clean function that will do all this stuff and call it with atexit because there are a lot of possible exit scenarios.
The problem is that atexit doesn't allow me to set functions with parameters, so I can't send to clean the pointers that need to be freed in the end of the process.
So I need to declare as global variables every pointer that may need to be freed, and every file that may remaining open in the program? (I already did that but doesn't looks good) or does exist a similar function to atexit that allows to send parameters? or more probably there is another way that I am missing?
Using a static pointer inside a function:
#include <stdio.h>
#include <stdlib.h>
void atexit_clean(void *data);
static void clean(void)
{
atexit_clean(NULL);
}
void atexit_clean(void *data)
{
static void *x;
if (data) {
x = data;
atexit(clean);
} else {
free(x);
}
}
int main(void)
{
int *a = malloc(sizeof(int));
atexit_clean(a);
return 0;
}
Another method using a single global variable: you can store all objects to be freed in an array of pointers or a linked list, this example uses realloc (doesn't check (m/re)alloc for brevity):
#include <stdio.h>
#include <stdlib.h>
static void **vclean;
static size_t nclean;
void atexit_add(void *data)
{
vclean = realloc(vclean, sizeof(void *) * (nclean + 1));
vclean[nclean++] = data;
}
void clean(void)
{
size_t i;
for (i = 0; i < nclean; i++) {
free(vclean[i]);
}
free(vclean);
}
int main(void)
{
int *a, *b, *c;
double *d;
int e = 1;
atexit(clean);
a = &e;
b = malloc(sizeof(int));
atexit_add(b);
c = malloc(sizeof(int));
atexit_add(c);
d = malloc(sizeof(double));
atexit_add(d);
return 0;
}
There is no way to pass any parameters to atexit(), so you're stuck using global variables.
When your program terminates normally, through exit() or by returning from main(), it will automatically flush and close any open streams and (under most operating systems) free allocated memory. However, it is good practice to explicitly clean up your resources before the program terminates, because it typically leads to a more structured program. Sometimes the cleanest way to write your program is to just exit and leave the cleanup to the implementation.
But be warned that you should always check the return value of fclose(). See "What are the reasons to check for error on close()?" for an anecdote about what could happen when you don't.

C Pthreads Problem, Can't Pass the Info I Want?

So I'm trying to make it so the threads startup function opens a file that was given via commandline, one file for each thread, but I also need the startup function to get my results array. So basically I need to get a string (the filename) and a 2D array of results to my startup thread some how, I'm thoroughly confused.
Anyone have any tips or ideas? Thanks.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include "string.h"
void* func(void *args);
int main(int argc, const char * argv[])
{
int nthreads = 0;
int i = 0;
long **results;
printf("Enter number of threads to use:\n> ");
scanf("%d", nthreads);
pthread_t threadArray[nthreads];
// results 2d array; 3 rows by nthreads cols
results = malloc((nthreads*4) * sizeof(long *));
for(i = 0; i<nthreads; i++) {
pthread_create(&threadArray[i], NULL, wordcount, HELP!!!!);
}
for(i = 0; i<nthreads; i++) {
pthread_join(threadArray[i], NULL);
}
pthread_exit();
}
void * func(void *arguments)
{
FILE *infile = stdin;
infile = fopen(filename, "rb");
fclose (infile);
}
Generally a structure that contains the data for the thread is declared and initialized, and a pointer to that structure is passed as the thread argument.
The thread function then casts the void* back to the structure pointer and has at the data.
Just remember that the lifetime of that structure still needs to be valid when the thread gets scheduled (which means you need to be very careful if it's a local variable). And as Jonathan Leffler pointed out, pass each thread it's own instance of the structure, or be very careful how you reuse it. Otherwise a thread may read data intended for a different thread if the structure gets reused before the thread is finished with it.
Probably the simplest way to manage those issues is to malloc() a structure for each thread, initialize it, pass the pointer to the thread and let the thread free() it when it's done with the data.
The last parameter to pthread_create can be any object you want, so for example you could have:
struct ThreadArguments {
const char* filename;
// additional parameters
};
void* ThreadFunction(void* arg) {
CHECK_NOTNULL(arg);
ThreadArguments* thread_arg = (ThreadArguments*) arg;
// now you can access the other parameters through this thread_arg object
// ...
}
// ...
ThreadArguments* arg = (ThreadArguments*) malloc(sizeof(ThreadArguments));
ret = pthread_create(&thread_id, attributes, &ThreadFunction, arg);
// make sure to check ret
// ...
pthread_join(thread_id);
free(arg);

Resources