sub sort_entries {
    my ($objlist, $opts) = @_;
    die "undefined list to sort" unless $objlist;
    # sort by size
    my $sorted = [sort {get_size($a->[$MSTAT]) <=> get_size($b->[$MSTAT])} @$objlist];
    return $sorted;
}

# gets files in presentation order.
# each file is a [name, rel_dir, depth, stat, children]
# rel_dir has neither leading nor trailing slash, and is empty string for top-level files.
# name has no slashes. 
sub get_files {
    my ($input_dir, $opts) = @_;
    die "missing input_dir arg" unless defined($input_dir); 
    die "no such dir '$input_dir'" unless -d $input_dir;

    $input_dir =~ s,^\./,,;
    my $depth = 0;
    my $objstack = [];
    my $lastobj;
    my $topobj;
    find({
	# this is called *after* wanted is called on this directory, but *before* any of its children.
	# called with $File::Find::dir first thing after entering a dir, before calling wanted.
	# passes in list of children names, and returns a list of names
	preprocess => sub() {
	    $depth++; 
	    debug("starting dir $File::Find::dir, depth now $depth");
	    unshift(@$objstack, $lastobj);
	    return @_;
	},

	# no args, no return.
	# $File::Find::name is full path; $_ is the same if no_chdir, or just the name part if chdir
	# can set $File::Find::prune
	wanted => sub() {
	    my $full = $File::Find::name;
	    my $curdir = dirname($full);
	    my $name = basename($full);
	    debug("processing '$full' dir=$curdir");
	    my $is_d = -d $full;
	    my $children = undef;
	    if ($is_d) {
		$File::Find::prune = should_prune_dir($full, $opts);
		return if $File::Find::prune;
		$children = [];
	    }
	    return if !$is_d && should_skip_file($full, $name, $opts);
	    my $sb = stat($full); # from File::stat
	    my $obj = [$name, $curdir, $depth, $sb, $children];
	    $lastobj = $obj;
	    if ($depth) {
		my $curlist = $objstack->[0]->[$MCHILDREN];
		push(@$curlist, $obj);
	    }
	    else {
		$topobj = $obj;
	    }
	},

	# called at end of dir
	# no args, no return value
	postprocess => sub() {
	    $depth--;
	    debug("ending dir $File::Find::dir, depth now $depth");
	    shift(@$objstack); # pop off curobj
	    my $curobj = $objstack->[0];

	    my $curlist = $curobj->[$MCHILDREN];
	    if ($curlist) {
		debug("about to sort list of length ", scalar(@$curlist));
		my $sorted_list = sort_entries($curlist, $opts);
		$curobj->[$MCHILDREN] = $sorted_list;
	    }
	    elsif ($depth) {
		warning("no list to sort!");
	    }

	    debug("objstack has length ", scalar(@$objstack));
	},

	follow => $opts->{follow},
	no_chdir => 1,
    }, $input_dir);

    return $topobj;
}

