[comp.unix.wizards] scandir

sam@uokmax.UUCP (Sam L Falkner) (06/30/88)

   Has anyone out there ever used the scandir() function?  I would
really appreciate a program fragment that uses scandir (maybe to
list all the filenames in a directory or something simple).  Be
sure to include the declarations - they're probably the reason I'm
messing up.  Please send me mail...
   Many thanks...
	- Sam Falkner

warner@scubed.UUCP (Ken Warner) (07/01/88)

In article <1445@uokmax.UUCP> sam@uokmax.UUCP (Sam L Falkner) writes:
>
>   Has anyone out there ever used the scandir() function?  I would
>really appreciate a program fragment that uses scandir (maybe to
>list all the filenames in a directory or something simple).  Be
>sure to include the declarations - they're probably the reason I'm
>messing up.  Please send me mail...
>   Many thanks...
>	- Sam Falkner

Here is a routine that uses scandir().  This is Sun 3-OS 3.4,5
specific.  The trick to using scandir() is to know how many entries are
in the directory before you make the call to scandir().  scandir()
just stuffs the array full of the entries into the the location pointed
to by the automatic and will blow away the stack if there isn't any
room.  Sort of a catch 22.  

Ken Warner
--------------------------------------------------------------------------------
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <string.h>
#include <ctype.h>

select(direntry)
struct direct *direntry;
{

	if(!strcmp(".",direntry->d_name))
	    return (0);
	if(!strcmp("..",direntry->d_name))
	    return (0);
#ifdef DEBUG
	printf("select(): name = %s\n",direntry->d_name);
	fflush(stdout);
#endif
	return(1);
}

myls(name)
char *name;
{
/* must know how many entries in the directory before you make call */
	struct direct *(*namelist[1024]);
	int (*foo)();
	int (*bar)();
	int num_dir,i,j;
	char pathname[MAXPATHLEN],*getwd(),temp[80];
	extern alphasort();
	struct stat stbuf;

	foo = select;
	bar = alphasort;
	if(stat(name,&stbuf) == -1)
	{
	    perror(name);
	    return (-1);
	}
	if((getwd(pathname)) == (char *)0)
	{
	    perror(pathname);
	    return (-1);
	}
	if(stbuf.st_mode & S_IFDIR)
	{
	    if((num_dir = scandir(name,namelist,foo,bar)) == -1)
		perror(name);
	    chdir(name);
	    for(i=0;i<num_dir;i++)
	    {
		/*printf("%s\n",(*namelist)[i]->d_name);*/
		if(stat((*namelist)[i]->d_name,&stbuf) == -1)
		{
		    perror(name);
		    return (-1);
		}
		else if(stbuf.st_mode & S_IFREG)
		    printf("%s\n",(*namelist)[i]->d_name);
		else if(stbuf.st_mode & S_IFDIR)
		    printf("%s/\n",(*namelist)[i]->d_name);
		else if(stbuf.st_mode & S_IFLNK)
		    printf("%s@\n",(*namelist)[i]->d_name);
		fflush(stdout);
	    }
	    chdir(pathname);
	}
	else if(stbuf.st_mode & S_IFREG)
		printf("%s\n",name);
}

mlandau@bbn.com (Matt Landau) (07/01/88)

In comp.unix.wizards, warner@scubed.UUCP (Ken Warner) writes:
>In article <1445@uokmax.UUCP> sam@uokmax.UUCP (Sam L Falkner) writes:
>>
>>   Has anyone out there ever used the scandir() function?  
>
>Here is a routine that uses scandir().  This is Sun 3-OS 3.4,5
>specific.  The trick to using scandir() is to know how many entries are
>in the directory before you make the call to scandir().  scandir()
>just stuffs the array full of the entries into the the location pointed
>to by the automatic and will blow away the stack if there isn't any
>room.  Sort of a catch 22.  

This is completely wrong -- scandir allocates its own storage.  Both the 
4.3 BSD and SunOS 3.4 manual pages say this quite explicitly.  

Here's a code fragment that uses scandir() and seems to work correctly.
Note the declaration of "struct direct **entries" and the use of &entries
as the second argument to scandir().  There's also a lot of ad hoc use
of pointers as arrays (and vice versa) in this code, but in this case 
it seems the clearest way to communicate what's going on.

clean_dir(dirname)
char   *dirname;
{
    extern char *getenv();
    extern int   needs_cleaning();
    extern int   is_directory();
    static char  old_name[MAXNAMLEN], new_name[MAXNAMLEN];

    register int    e;
    char	   *backup;
    int             n_entries;
    struct direct **entries;


    /* Figure out where to put things */
    if ((backup = getenv("TRASH")) == NULL)
	backup = "/tmp";
    if (!is_directory(backup))
    {
	fprintf(stderr, "clean: trash directory %s not found\n", backup);
	exit(-1);
    }

    /* Scan directory for files that are candidates for cleanup. */
    n_entries = scandir(dirname, &entries, needs_cleaning, (int (*)())NULL);

    /* If no valid entries were found, figure out why not. */
    if (n_entries < 0)
	perror("scandir");
    else
    {
	for (e = 0; e < n_entries; e++)
	{
	    sprintf(old_name, "%s/%s", dirname, entries[e]->d_name);
	    sprintf(new_name, "%s/%s", backup, entries[e]->d_name);
	    rename(old_name, new_name);
	}
    }
}

chris@mimsy.UUCP (07/01/88)

In article <799@scubed.UUCP> warner@scubed.UUCP (Ken Warner) writes:
>Here is a routine that uses scandir().  This is Sun 3-OS 3.4,5
>specific.  The trick to using scandir() is to know how many entries are
>in the directory before you make the call to scandir().  scandir()
>just stuffs the array full of the entries into the the location pointed
>to by the automatic and will blow away the stack if there isn't any
>room.  Sort of a catch 22.  

This is wrong (as was the code that called scandir).  If you had to
know the directory length first, scandir would be useless.  The manual
entry for scandir is correct but misleading (there is no array in
its declaration!).

f()
{
	struct direct **list;
	int i, n;

	/* important:      & */
	n = scandir("foo", &list, (int (*)())NULL, (int (*)())NULL);
	/* important:      & */

	if (n == -1) ... /* could not scan */

	for (i = 0; i < n; i++)
		printf("%s\n", list[i]->d_name);

	for (i = 0; i < n; i++)
		free((char *)list[i]);
	free((char *)list);
}

/*
 * The type of the second argument to scandir is `pointer to pointer
 * to pointer to struct direct', or `struct direct ***'.  As a parameter
 * this may (but never should) be written as `struct direct *(*namelist[])'.
 * The manual is misleading; namelist is neither an array nor a pointer to
 * an array.
 */
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

warner@scubed.UUCP (Ken Warner) (07/01/88)

In article <12267@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>In article <799@scubed.UUCP> warner@scubed.UUCP (Ken Warner) writes:
>>specific.  The trick to using scandir() is to know how many entries are
>>in the directory before you make the call to scandir(). 
>This is wrong (as was the code that called scandir).  If you had to
>know the directory length first, scandir would be useless.  The manual
>entry for scandir is correct but misleading (there is no array in
>its declaration!).
[stuff deleted]
>	struct direct **list;
>	int i, n;
>	/* important:      & */
>	n = scandir("foo", &list, (int (*)())NULL, (int (*)())NULL);
>	/* important:      & */
> * The type of the second argument to scandir is `pointer to pointer
> * to pointer to struct direct', or `struct direct ***'.  As a parameter
> * this may (but never should) be written as `struct direct *(*namelist[])'.
> * The manual is misleading; namelist is neither an array nor a pointer to
        ^^^^^^^^^^^^^^^^^^^^
You got that right. Sorry for my assertion that you needed to know how many
entries were in the directory etc.  I took the man page literally.  

SYNTAX
     #include <sys/types.h>
     #include <sys/dir.h>

     scandir(dirname, namelist, select, compar)
     char *dirname;
     struct direct *(*namelist[]);
     int (*select)();
     int (*compar)();

The only way I could make this declaration work was to declare a static array.
And now I don't know why it worked at all.  Must have been tired or stupid. 
My apologies to all.
Ken Warner

sam@uokmax.UUCP (Sam L Falkner) (07/02/88)

In article <801@scubed.UUCP>, warner@scubed.UUCP (Ken Warner) writes:
> In article <12267@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
> >	struct direct **list;
> >	n = scandir("foo", &list, (int (*)())NULL, (int (*)())NULL);
> > * The manual is misleading; namelist is neither an array nor a pointer to
>         ^^^^^^^^^^^^^^^^^^^^
> The only way I could make this declaration work was to declare a static array.
> And now I don't know why it worked at all.  Must have been tired or stupid. 

   Thanks to everyone who responded.  I also found the manual rather
confusing, but I got the program working the same way you did.  The
trouble was that as I started adding to the program, I began to get
segmentation faults, and realized what the problem was.  Then I
couldn't figure out what the correct declaration was (I also tried
using "struct direct *namelist[]" and calling it by "&namelist" to no
avail.  Oh well...)
   Again, thanks for all the help.
	- Sam Falkner, University of Oklahoma