Your general approach is faulty. You cannot do it recursively using a loop alone. Also, your approach of scanning the second directory for each match in the first is not only slow, but incomplete: what about files that only exist in the second directory?
Instead, I would recommend you write a function that compares the contents of two directories. For example, something along the lines of
Code:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
struct list *compare(const char *const directory1,
const char *const directory2,
const int flags)
{
/* ... */
}
You will need to scan both
directory1 and
directory2 once. Whichever you scan first, you'll find entries that exist in that one alone, and in both. (Just try to get statistics for an entry by that name, under both directories.) Whichever you scan last, you are concerned only with the entries that do not exist in the first; remember, you already found all those.
When recursing into directories, you'll need to construct the new directory names, since the directory entries only contain the final segment (name), not the full path. Here is a helper function that returns the full path needed, as a dynamically allocated string:
Code:
#include <stdlib.h>
#include <string.h>
#include <errno.h>
char *pathto(const char *const dir, const char *const name)
{
const size_t dirlen = (dir) ? strlen(dir) : 0;
const size_t namelen = (name) ? strlen(name) : 0;
const size_t size = dirlen + namelen + 2;
size_t len;
char *path;
if (size < 3) {
errno = EINVAL;
return NULL;
}
path = malloc(len);
if (!path) {
errno = ENOMEM;
return NULL;
}
len = dirlen;
if (dirlen > 0) {
memcpy(path, dir, dirlen);
if (path[len-1] != '/')
path[len++] = '/';
}
if (namelen > 0) {
memcpy(path + len, name, namelen);
len += namelen;
}
path[len] = '\0';
return path;
}
After the recursive call has been done, using the dynamically allocated directory names, you need to
free() them explicitly.
_ _ _ _ _ _ _ _ _ _
If this is part of a library or real application code, there is one deeply technical issue I'd like to bring up.
When handling directory structures, directories and files may be renamed at any point. Because of that, it is recommended that instead of relying solely on paths, applications should retain a descriptor to the directory, and use the
fstatat(dirfd, name...) function (POSIX.1-2008, i.e.
#define _POSIX_C_SOURCE 200809L). The descriptor will stay valid, even if the name of the underlying directory happened to change.
I understand that this is something that is totally new and alien to programmers with only Windows experience. Let me assure you: directory names are surprisingly volatile (and for useful reasons) in other OSes. Do not let Windows-isms drag down the quality of your code. (In Linux and BSDs and derivatives, you can rename or delete open files, even executables. I've found that many with only Windows experience expect having the file open be some kind of lock that should forbid such actions. I've never understood the reasoning for that.)
To open a directory for the purpose of scanning its contents, you can use
Code:
#define _POSIX_C_SOURCE 200809L
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
DIR *opendirat(int dirfd, const char *pathname)
{
DIR *dir;
int fd, result, saved_errno;
saved_errno = errno;
do {
#ifdef O_NOCTTY
#ifdef O_DIRECTORY
fd = openat(dirfd, pathname, O_RDONLY | O_DIRECTORY | O_NOCTTY);
#else
fd = openat(dirfd, pathname, O_RDONLY | O_NOCTTY);
#endif
#else
#ifdef O_DIRECTORY
fd = openat(dirfd, pathname, O_RDONLY | O_DIRECTORY);
#else
fd = openat(dirfd, pathname, O_RDONLY);
#endif
#endif
} while (fd == -1 && errno == EINTR);
if (fd == -1)
return NULL;
do {
dir = fdopendir(fd);
} while (!dir && errno == EINTR);
if (!dir) {
saved_errno = errno;
do {
result = close(fd);
} while (result == -1 && errno == EINTR);
errno = saved_errno;
return NULL;
}
/* fd is now incorporated into the dir handle;
* it will be closed when the dir is closed.
*/
errno = saved_errno;
return dir;
}
In the general case, a process can acquire a descriptor to a directory it cannot read, as long as it can enter it. In this case, that is not necessary, because you cannot get the listing from such a directory anyway. Therefore the above simplified version is perfectly adequate, but only for when you need to scan the contents of that directory. It is not sufficient if you don't necessarily
need to get a listing, but only need to enter said directory; although that sounds even simpler, for that you do need the general, rather complex version of the function.
In the most general case, a
opendirat() implementation requires a child process entering the desired directory, passing the descriptor back via an ancillary socket message, because otherwise all other threads and signal handlers (and library code) would see the current working directory flipping back and forth while the code is running. Because the current working directory is process-wide, you really need to use a separate process to enter the new directory, then pass back a reference to it. Fortunately, this mess can almost always be avoided. For example, you can use a mutex or an rwlock to protect any access that has to do with the current working directory.
The difference between the general case and the above implementation is that the above implementation will only work if the current user has read rights to the directory (i.e. can see the directory listing).