Locke
Locke

Reputation: 148

How can I get the total number of child processes called by fork() from this recursive function?

I've been messing around with this recursive function trying to get the total number of child processes created by fork(). I can't seem to get it right, though. When I tried to use WEXITSTATUS(), the program output became very erratic. Is there a way to sum the total number of child processes spawned in this function? Is piping the only way to do that, or is there an easier way?

It gets passed ".", to kick the function off in the current working directory. It traverses that directory and all subdirectories, forking whenever a subdirectory is found. verifyFileType() just checks if the file found is a CSV.

**Edited function for some more clarity

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>

int traverse(char* directory)
{
    struct dirent *currentDir;  
    DIR *traverser = opendir(directory);

    if (traverser == NULL)
    {
        printf("Error: Could not open directory.\n");
        return 0;
    }

    while ((currentDir = readdir(traverser)) != NULL)
    {       
        if (currentDir->d_type == DT_DIR && strcmp(currentDir->d_name, ".") != 0 && strcmp(currentDir->d_name, "..") != 0)
        {
            PID = fork();

            if (PID == 0)
            {       
                char pathBuffer[1024];
                snprintf(pathBuffer, sizeof(pathBuffer), "%s/%s", directory, currentDir->d_name);

                traverse(pathBuffer);
                //int childProc = traverse(pathBuffer);
                //return childProc + 1;
                exit(0);
            }
            else
            {
                //do parent stuff?
            }
        }
        else if (strcmp(currentDir->d_name, ".") != 0 && strcmp(currentDir->d_name, "..") != 0)
        {
            if (verifyFileType(currentDir->d_name) == 0)
            {
                //if directory = '.', only send file name as fopen() arg
                printf("%s%s\n", directory, currentDir->d_name);
            }
        }
    }

    if (PID > 0)
    {
        int status = 0;
        wait(&status);
        //int returned = WEXITSTATUS(status);
        //return returned + 1;
    }
    else if (PID == -1)
    {
        printf("Error waiting on children.  Aborting.\n");
        _exit(0);
    }

    closedir(traverser);
    return 0;
}

int main (int argc, char **argv)
{
    char* beginningDir = ".";
    rootPID = getpid();

    /*
    int procCount = 0;
    procCount = traverse(beginningDir);
    printf("Proc Count: %d\n", procCount);
    */

    traverse(beginningDir);
    return 0;
}

Upvotes: 2

Views: 516

Answers (3)

KamilCuk
KamilCuk

Reputation: 140990

I think you can only achieve that using an external entity, as forked processes have nothing in common (except parents).
Let's use a file. We can get a file using tmpfile(). We would need some locking, using flock(fileno(FILE *), ...). Each child would write a single byte into the temp file. After all childs are run, I can get the size of the file - thus I will get the number of childs:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

typedef FILE cnt_t;

/**
 * Create interprocess counter.
 * Should be created once (and only once) by the parent process.
 */
cnt_t *cnt_new(void)
{
    return tmpfile();
}

/**
 * Remove interprocess counter.
 * Should be called by all childs and parent
 */
void cnt_delete(cnt_t *t)
{
    fclose(t);
}

void _cnt_lock(cnt_t *t)
{
    for (int ret; (ret = flock(fileno(t), LOCK_EX)) != 0;) {
        assert(ret == EWOULDBLOCK);
    }
}

void _cnt_unlock(cnt_t *t)
{
    if (flock(fileno(t), LOCK_UN) != 0) {
        assert(0);
    }
}

/**
 * Increments counter by 1.
 */
void cnt_inc(cnt_t *t) {
    assert(t != NULL);
    _cnt_lock(t);

    if (fwrite((char[1]){'X'}, sizeof(char), 1, t) < 0) {
        assert(0);
    }

    if (fflush(t) != 0) {
        assert(0);
    }

    _cnt_unlock(t);
}

void cnt_println(cnt_t *t)
{
    _cnt_lock(t);

    if (fseek(t, 0L, SEEK_SET) < 0) {
        assert(0);
    }

    char buf[124];
    size_t cnt = fread(buf, sizeof(char), 124, t);
    printf("cnt(%p) = %ld  '%.*s'\n", cnt, (void*)t, cnt, buf);

    _cnt_unlock(t);
}

/**
 * Get's counter value.
 */
long cnt_getCount(cnt_t *t)
{
    assert(t != NULL);

    _cnt_lock(t);

    if (fseek(t, 0L, SEEK_END) < 0) {
        assert(0);
    }

    const long sz = ftell(t);
    if (sz < 0) {
        assert(0);
    }

    _cnt_unlock(t);

    return sz;
}

/* ----------------------------------------------------------- */

int main()
{
    srand(0);

    cnt_t *cntobj = cnt_new();

    bool child = false;
    for (int i = 0; i < 5; ++i) {
        const int ret = fork();
        switch (ret) {
        case 0:
            cnt_inc(cntobj);
            child = true;
            break;
        case -1:
            fprintf(stderr, "fork error!\n");
            exit(-1);
            break;
        default:
            fprintf(stderr, "%d -> %d\n", getpid(), ret);
            break;
        }
    }

    while (wait(NULL) != -1) continue;

    if (child) {
        cnt_delete(cntobj);
        exit(0);
    }

    const long cnt = cnt_getCount(cntobj);
    cnt_delete(cntobj);

    fprintf(stderr, "childs %ld\n", cnt);

    return 0;
}

Maybe I went too much on obfuscucation (typedef FILE cnt_t looks strange), but the code works and returns the correct number 31. Live version available at jdoodle.

And here's a solution just by using pipes:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

typedef struct {
    int p[2];
} cnt_t;

/**
 * Create interprocess counter.
 * Should be created once (and only once) by the parent process.
 */
cnt_t *cnt_new(void)
{
    cnt_t *t = malloc(sizeof(*t));
    assert(t != NULL);
    if (pipe(t->p) < 0) {
        assert(0);
    }
    if (fcntl(t->p[0], F_SETFL, O_NONBLOCK) < 0) {
        assert(0);
    }
    return t;
}

/**
 * Remove interprocess counter.
 * Should be called by all childs and parent
 */
void cnt_delete(cnt_t *t)
{
    close(t->p[0]);
    close(t->p[1]);
    t->p[0] = 0;
    t->p[1] = 0;
    free(t);
}

/**
 * Increments counter by 1.
 */
void cnt_inc(cnt_t *t) 
{
    assert(t != NULL);
    if (write(t->p[1], (char[1]){'X'}, 1 * sizeof(char)) < 0) {
        assert(0);
    }
}

/**
 * Get's counter value.
 */
long cnt_getCount(cnt_t *t)
{
    assert(t != NULL);

    char c;
    long cnt = 0;
    ssize_t tmp;
    errno = 0;
    while ((tmp = read(t->p[0], &c, 1)) == 1) {
        ++cnt;
    }
    if (tmp < 0 && errno != EWOULDBLOCK) {
        assert(0);
    }

    const long ret = cnt;

    while (cnt--) {
        if (write(t->p[1], (char[1]){'X'}, 1) < 0) {
            assert(0);
        }
    }

    return ret;
}

/* ----------------------------------------------------------- */

int main()
{
    srand(0);

    cnt_t *cntobj = cnt_new();

    bool child = false;
    for (int i = 0; i < 5; ++i) {
        const int ret = fork();
        switch (ret) {
        case 0:
            cnt_inc(cntobj);
            child = true;
            break;
        case -1:
            fprintf(stderr, "fork error!\n");
            exit(-1);
            break;
        default:
            fprintf(stderr, "%d -> %d\n", getpid(), ret);
            break;
        }
    }

    while (wait(NULL) != -1) continue;

    if (child) {
        cnt_delete(cntobj);
        exit(0);
    }

    const long cnt = cnt_getCount(cntobj);
    cnt_delete(cntobj);

    fprintf(stderr, "childs %ld\n", cnt);

    return 0;
}

Works as good and probably is way faster. Live version still at jdoodle.

These examples use a stupid error handling using assert and they serve just only to show the method and that the method works.

Probably we could also create a solution using posix shemaphores and some interprocess communication, some shmget and semop and for example queuing sempahore count.

Upvotes: 1

Ajay Brahmakshatriya
Ajay Brahmakshatriya

Reputation: 9203

You spawn a number of children in each process corresponding to the number of sub directories but you do not wait for all of them.

You only wait for the last child. You need to store all the PIDs in an array an wait on them in a loop. Then sum all the WEXITSTATUS from then and return that value (after adding one for your own).

Upvotes: 0

OznOg
OznOg

Reputation: 4722

You could try that, this is really close to what you did, but I just count the number of child I spawn and then wait for them.

#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>

int traverse(char* directory) {
    struct dirent *currentDir;
    DIR *traverser = opendir(directory);

    if (traverser == NULL) {
        printf("Error: Could not open directory.\n");
        return 0;
    }

    size_t nb_child = 0;
    while ((currentDir = readdir(traverser)) != NULL)
    {
        if (strcmp(currentDir->d_name, ".") == 0
            || strcmp(currentDir->d_name, "..") == 0)
            continue; // ignore . and ..

        // if subdirectory => fork to explore
        if (currentDir->d_type == DT_DIR) {
            pid_t PID = fork();

            if (PID == 0) {
                char pathBuffer[1024];
                snprintf(pathBuffer, sizeof(pathBuffer), "%s/%s",
                         directory, currentDir->d_name);

                return traverse(pathBuffer);
            } else {
                nb_child++; // keep track of the nomber of children
            }
        } else { // non directory "file"
                // Do you verify here
                printf("%s%s\n", directory, currentDir->d_name);
        }
    }

    // loop until we waited for all children
    for (size_t i = 0; i < nb_child; i++)
        wait(NULL);

    closedir(traverser);
    return 0;
}

int main() {
    return traverse(argv[1]);
}

Upvotes: 0

Related Questions