hfsutils/hls.c

1030 lines
18 KiB
C

/*
* hfsutils - tools for reading and writing Macintosh HFS volumes
* Copyright (C) 1996-1998 Robert Leslie
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: hls.c,v 1.8 1998/09/28 23:21:50 rob Exp $
*/
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# ifdef HAVE_UNISTD_H
# include <unistd.h>
# endif
# ifdef HAVE_TERMIOS_H
# include <termios.h>
# endif
# ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
# else
int ioctl(int, int, ...);
# endif
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
# include <ctype.h>
# include <errno.h>
# include "hfs.h"
# include "hcwd.h"
# include "hfsutil.h"
# include "darray.h"
# include "dlist.h"
# include "dstring.h"
# include "hls.h"
# define HLS_ALL_FILES 0x0001
# define HLS_ESCAPE 0x0002
# define HLS_QUOTE 0x0004
# define HLS_QMARK_CTRL 0x0008
# define HLS_IMMEDIATE_DIRS 0x0010
# define HLS_CATIDS 0x0020
# define HLS_REVERSE 0x0040
# define HLS_SIZE 0x0080
# define HLS_INDICATOR 0x0100
# define HLS_RECURSIVE 0x0200
# define HLS_NAME 0x0400
# define HLS_SPACE 0x0800
# define F_MASK 0x0007
# define F_LONG 0x0000
# define F_ONE 0x0001
# define F_MANY 0x0002
# define F_HORIZ 0x0003
# define F_COMMAS 0x0004
# define T_MASK 0x0008
# define T_MOD 0x0000
# define T_CREATE 0x0008
# define S_MASK 0x0030
# define S_NAME 0x0000
# define S_TIME 0x0010
# define S_SIZE 0x0020
# define PATH(ent) ((ent).path ? (ent).path : (ent).dirent.name)
typedef struct _queueent_ {
char *path;
hfsdirent dirent;
void (*free)(struct _queueent_ *);
} queueent;
extern char *optarg;
extern int optind;
/*
* NAME: usage()
* DESCRIPTION: display usage message
*/
static
int usage(void)
{
fprintf(stderr, "Usage: %s [options] [hfs-path ...]\n", argv0);
return 1;
}
/*
* NAME: dpfree()
* DESCRIPTION: free a queue entry containing dynamically-allocated data
*/
static
void dpfree(queueent *ent)
{
free(ent->path);
}
/*
* NAME: qnew()
* DESCRIPTION: create a new queue array
*/
static
darray *qnew(void)
{
return darr_new(sizeof(queueent));
}
/*
* NAME: qfree()
* DESCRIPTION: free a queue array
*/
static
void qfree(darray *array)
{
int i, sz;
queueent *ents;
sz = darr_size(array);
ents = darr_array(array);
for (i = 0; i < sz; ++i)
if (ents[i].free)
ents[i].free(&ents[i]);
darr_free(array);
}
static
int reverse;
/*
* NAME: compare_names()
* DESCRIPTION: lexicographically compare two filenames
*/
static
int compare_names(const queueent *ent1, const queueent *ent2)
{
return reverse * strcasecmp(PATH(*ent1), PATH(*ent2));
}
/*
* NAME: compare_mtimes()
* DESCRIPTION: chronologically compare two modification dates
*/
static
int compare_mtimes(const queueent *ent1, const queueent *ent2)
{
return reverse * (ent2->dirent.mddate - ent1->dirent.mddate);
}
/*
* NAME: compare_ctimes()
* DESCRIPTION: chronologically compare two creation dates
*/
static
int compare_ctimes(const queueent *ent1, const queueent *ent2)
{
return reverse * (ent2->dirent.crdate - ent1->dirent.crdate);
}
/*
* NAME: compare_sizes()
* DESCRIPTION: compare two file sizes
*/
static
int compare_sizes(const queueent *ent1, const queueent *ent2)
{
return reverse *
((ent2->dirent.u.file.dsize + ent2->dirent.u.file.rsize) -
(ent1->dirent.u.file.dsize + ent1->dirent.u.file.rsize));
}
/*
* NAME: sortfiles()
* DESCRIPTION: arrange files in order according to sort selection
*/
static
void sortfiles(darray *files, int flags, int options)
{
int (*compare)(const queueent *, const queueent *);
switch (options & S_MASK)
{
case S_NAME:
compare = compare_names;
break;
case S_TIME:
switch (options & T_MASK)
{
case T_MOD:
compare = compare_mtimes;
break;
case T_CREATE:
compare = compare_ctimes;
break;
default:
abort();
}
break;
case S_SIZE:
compare = compare_sizes;
break;
default:
return;
}
reverse = (flags & HLS_REVERSE) ? -1 : 1;
darr_sort(files, (int (*)(const void *, const void *)) compare);
}
/*
* NAME: outpath()
* DESCRIPTION: modulate an output string given current flags
*/
static
int outpath(dstring *str, queueent *ent, int flags)
{
const char *path;
path = PATH(*ent);
dstr_shrink(str, 0);
if ((flags & HLS_QUOTE) &&
dstr_append(str, "\"", 1) == -1)
return -1;
if (flags & (HLS_ESCAPE | HLS_QUOTE | HLS_QMARK_CTRL))
{
const char *ptr;
for (ptr = path; *ptr; ++ptr)
{
const char *add;
char buf[5];
if (flags & HLS_ESCAPE)
{
switch (*ptr)
{
case '\\':
add = "\\\\";
break;
case '\n':
add = "\\n";
break;
case '\b':
add = "\\b";
break;
case '\r':
add = "\\r";
break;
case '\t':
add = "\\t";
break;
case '\f':
add = "\\f";
break;
case ' ':
add = "\\ ";
break;
case '\"':
add = "\\\"";
break;
default:
if (isgraph(*ptr))
{
sprintf(buf, "%c", *ptr);
add = buf;
}
else
{
sprintf(buf, "\\%03o", (unsigned char) *ptr);
add = buf;
}
}
}
else /* ! (flags & HLS_ESCAPE) */
{
if (isprint(*ptr) || ! (flags & HLS_QMARK_CTRL))
{
sprintf(buf, "%c", *ptr);
add = buf;
}
else
{
sprintf(buf, "?");
add = buf;
}
}
if (dstr_append(str, add, -1) == -1)
return -1;
}
}
else
{
if (dstr_append(str, path, -1) == -1)
return -1;
}
if ((flags & HLS_QUOTE) &&
dstr_append(str, "\"", 1) == -1)
return -1;
if (flags & HLS_INDICATOR)
{
char c = 0;
if (ent->dirent.flags & HFS_ISDIR)
c = ':';
else if (strcmp(ent->dirent.u.file.type, "APPL") == 0)
c = '*';
if (c && dstr_append(str, &c, 1) == -1)
return -1;
}
return 0;
}
/*
* NAME: misclen()
* DESCRIPTION: string length of miscellaneous section
*/
static
int misclen(int flags)
{
return ((flags & HLS_CATIDS) ? 8 : 0) +
((flags & HLS_SIZE) ? 5 : 0);
}
/*
* NAME: showmisc()
* DESCRIPTION: output miscellaneous numbers
*/
static
void showmisc(hfsdirent *ent, int flags)
{
unsigned long size;
size = ent->u.file.rsize + ent->u.file.dsize;
if (flags & HLS_CATIDS)
printf("%7lu ", ent->cnid);
if (flags & HLS_SIZE)
printf("%4lu ", size / 1024 + (size % 1024 != 0));
}
/*
* NAME: show_long()
* DESCRIPTION: output a list of files in long format
*/
static
void show_long(int sz, queueent *ents, char **strs,
int flags, int options, int width)
{
int i;
time_t now;
now = time(0);
for (i = 0; i < sz; ++i)
{
hfsdirent *ent;
time_t when;
char timebuf[26];
ent = &ents[i].dirent;
switch (options & T_MASK)
{
case T_MOD:
when = ent->mddate;
break;
case T_CREATE:
when = ent->crdate;
break;
default:
abort();
}
strcpy(timebuf, ctime(&when));
if (now > when + 6L * 30L * 24L * 60L * 60L ||
now < when - 60L * 60L)
strcpy(timebuf + 11, timebuf + 19);
timebuf[16] = 0;
showmisc(ent, flags);
if (ent->flags & HFS_ISDIR)
printf("d%c %9u item%c %s %s\n",
ent->fdflags & HFS_FNDR_ISINVISIBLE ? 'i' : ' ',
ent->u.dir.valence, ent->u.dir.valence == 1 ? ' ' : 's',
timebuf + 4, strs[i]);
else
printf("%c%c %4s/%4s %9lu %9lu %s %s\n",
ent->flags & HFS_ISLOCKED ? 'F' : 'f',
ent->fdflags & HFS_FNDR_ISINVISIBLE ? 'i' : ' ',
ent->u.file.type, ent->u.file.creator,
ent->u.file.rsize, ent->u.file.dsize,
timebuf + 4, strs[i]);
}
}
/*
* NAME: show_one()
* DESCRIPTION: output a list of files in single-column format
*/
static
void show_one(int sz, queueent *ents, char **strs,
int flags, int options, int width)
{
int i;
for (i = 0; i < sz; ++i)
{
showmisc(&ents[i].dirent, flags);
printf("%s\n", strs[i]);
}
}
/*
* NAME: show_many()
* DESCRIPTION: output a list of files in vertical-column format
*/
static
void show_many(int sz, queueent *ents, char **strs,
int flags, int options, int width)
{
int i, len, misc, maxlen = 0, rows, cols, row;
misc = misclen(flags);
for (i = 0; i < sz; ++i)
{
len = strlen(strs[i]) + misc;
if (len > maxlen)
maxlen = len;
}
maxlen += 2;
cols = width / maxlen;
if (cols == 0)
cols = 1;
rows = sz / cols + (sz % cols != 0);
for (row = 0; row < rows; ++row)
{
i = row;
while (1)
{
showmisc(&ents[i].dirent, flags);
printf("%s", strs[i]);
i += rows;
if (i >= sz)
break;
for (len = strlen(strs[i - rows]) + misc;
len < maxlen; ++len)
putchar(' ');
}
putchar('\n');
}
}
/*
* NAME: show_horiz()
* DESCRIPTION: output a list of files in horizontal-column format
*/
static
void show_horiz(int sz, queueent *ents, char **strs,
int flags, int options, int width)
{
int i, len, misc, maxlen = 0, cols;
misc = misclen(flags);
for (i = 0; i < sz; ++i)
{
len = strlen(strs[i]) + misc;
if (len > maxlen)
maxlen = len;
}
maxlen += 2;
cols = width / maxlen;
if (cols == 0)
cols = 1;
for (i = 0; i < sz; ++i)
{
if (i)
{
if (i % cols == 0)
putchar('\n');
else
{
for (len = strlen(strs[i - 1]) + misc;
len < maxlen; ++len)
putchar(' ');
}
}
showmisc(&ents[i].dirent, flags);
printf("%s", strs[i]);
}
if (i)
putchar('\n');
}
/*
* NAME: show_commas()
* DESCRIPTION: output a list of files in comma-delimited format
*/
static
void show_commas(int sz, queueent *ents, char **strs,
int flags, int options, int width)
{
int i, pos = 0;
for (i = 0; i < sz; ++i)
{
hfsdirent *ent;
int len;
ent = &ents[i].dirent;
len = strlen(strs[i]) + misclen(flags) + ((i < sz - 1) ? 2 : 0);
if (pos && pos + len >= width)
{
putchar('\n');
pos = 0;
}
showmisc(ent, flags);
printf("%s", strs[i]);
if (i < sz - 1)
{
putchar(',');
putchar(' ');
}
pos += len;
}
if (pos)
putchar('\n');
}
/*
* NAME: showfiles()
* DESCRIPTION: display a set of files
*/
static
int showfiles(darray *files, int flags, int options, int width)
{
dlist list;
int i, sz, result = 0;
queueent *ents;
dstring str;
char **strs;
void (*show)(int, queueent *, char **, int, int, int);
if (dl_init(&list) == -1)
{
fprintf(stderr, "%s: not enough memory\n", argv0);
return -1;
}
sz = darr_size(files);
ents = darr_array(files);
dstr_init(&str);
for (i = 0; i < sz; ++i)
{
if (outpath(&str, &ents[i], flags) == -1 ||
dl_append(&list, dstr_string(&str)) == -1)
{
result = -1;
break;
}
}
dstr_free(&str);
strs = dl_array(&list);
switch (options & F_MASK)
{
case F_LONG:
show = show_long;
break;
case F_ONE:
show = show_one;
break;
case F_MANY:
show = show_many;
break;
case F_HORIZ:
show = show_horiz;
break;
case F_COMMAS:
show = show_commas;
break;
default:
abort();
}
show(sz, ents, strs, flags, options, width);
dl_free(&list);
return result;
}
/*
* NAME: process()
* DESCRIPTION: sort and display results
*/
static
int process(hfsvol *vol, darray *dirs, darray *files,
int flags, int options, int width)
{
int i, dsz, fsz;
queueent *ents;
int result = 0;
dsz = darr_size(dirs);
fsz = darr_size(files);
if (fsz)
{
sortfiles(files, flags, options);
if (showfiles(files, flags, options, width) == -1)
result = -1;
flags |= HLS_NAME | HLS_SPACE;
}
else if (dsz > 1)
flags |= HLS_NAME;
ents = darr_array(dirs);
for (i = 0; i < dsz; ++i)
{
const char *path;
hfsdir *dir;
queueent ent;
darr_shrink(files, 0);
path = PATH(ents[i]);
dir = hfs_opendir(vol, path);
if (dir == 0)
{
hfsutil_perrorp(path);
result = -1;
continue;
}
while (hfs_readdir(dir, &ent.dirent) != -1)
{
if ((ent.dirent.fdflags & HFS_FNDR_ISINVISIBLE) &&
! (flags & HLS_ALL_FILES))
continue;
ent.path = 0;
ent.free = 0;
if (darr_append(files, &ent) == 0)
{
fprintf(stderr, "%s: not enough memory\n", argv0);
result = -1;
break;
}
if ((ent.dirent.flags & HFS_ISDIR) && (flags & HLS_RECURSIVE))
{
dstring str;
dstr_init(&str);
if (strchr(path, ':') == 0 &&
dstr_append(&str, ":", 1) == -1)
result = -1;
if (dstr_append(&str, path, -1) == -1)
result = -1;
if (path[strlen(path) - 1] != ':' &&
dstr_append(&str, ":", 1) == -1)
result = -1;
if (dstr_append(&str, ent.dirent.name, -1) == -1)
result = -1;
ent.path = strdup(dstr_string(&str));
if (ent.path)
ent.free = dpfree;
else
result = -1;
dstr_free(&str);
if (darr_append(dirs, &ent) == 0)
{
result = -1;
if (ent.path)
free(ent.path);
}
if (result)
{
fprintf(stderr, "%s: not enough memory\n", argv0);
break;
}
dsz = darr_size(dirs);
ents = darr_array(dirs);
}
}
hfs_closedir(dir);
if (result)
break;
if (flags & HLS_SPACE)
printf("\n");
if (flags & HLS_NAME)
printf("%s%s", path,
path[strlen(path) - 1] == ':' ? "\n" : ":\n");
sortfiles(files, flags, options);
if (showfiles(files, flags, options, width) == -1)
result = -1;
flags |= HLS_NAME | HLS_SPACE;
}
return result;
}
/*
* NAME: queuepath()
* DESCRIPTION: append a file or directory to the list to process
*/
static
int queuepath(hfsvol *vol, char *path, darray *dirs, darray *files, int flags)
{
queueent ent;
darray *array;
if (hfs_stat(vol, path, &ent.dirent) == -1)
{
hfsutil_perrorp(path);
return (errno == ENOENT) ? 0 : -1;
}
ent.path = path;
ent.free = 0;
array = ((ent.dirent.flags & HFS_ISDIR) &&
! (flags & HLS_IMMEDIATE_DIRS)) ? dirs : files;
if (darr_append(array, &ent) == 0)
{
fprintf(stderr, "%s: not enough memory\n", argv0);
return -1;
}
return 0;
}
/*
* NAME: hls->main()
* DESCRIPTION: implement hls command
*/
int hls_main(int argc, char *argv[])
{
hfsvol *vol;
int fargc, i;
char **fargv = 0;
int result = 0;
int flags, options, width;
char *ptr;
darray *dirs, *files;
options = T_MOD | S_NAME;
if (isatty(STDOUT_FILENO))
{
options |= F_MANY;
flags = HLS_QMARK_CTRL;
}
else
{
options |= F_ONE;
flags = 0;
}
if (strcmp(bargv0, "hdir") == 0)
options = (options & ~F_MASK) | F_LONG;
ptr = getenv("COLUMNS");
width = ptr ? atoi(ptr) : 80;
# ifdef TIOCGWINSZ
{
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
ws.ws_col != 0)
width = ws.ws_col;
}
# endif
while (1)
{
int opt;
opt = getopt(argc, argv, "1abcdfilmqrstxw:CFNQRSU");
if (opt == EOF)
break;
switch (opt)
{
case '?':
return usage();
case '1':
options = (options & ~F_MASK) | F_ONE;
break;
case 'a':
flags |= HLS_ALL_FILES;
break;
case 'b':
flags |= HLS_ESCAPE;
flags &= ~HLS_QMARK_CTRL;
break;
case 'c':
options = (options & ~(T_MASK | S_MASK)) | T_CREATE | S_TIME;
break;
case 'd':
flags |= HLS_IMMEDIATE_DIRS;
break;
case 'f':
flags |= HLS_ALL_FILES;
flags &= ~HLS_SIZE;
options &= ~S_MASK;
if ((options & F_MASK) == F_LONG)
options = (options & ~F_MASK) |
(isatty(STDOUT_FILENO) ? F_MANY : F_ONE);
break;
case 'i':
flags |= HLS_CATIDS;
break;
case 'l':
options = (options & ~F_MASK) | F_LONG;
break;
case 'm':
options = (options & ~F_MASK) | F_COMMAS;
break;
case 'q':
flags |= HLS_QMARK_CTRL;
flags &= ~HLS_ESCAPE;
break;
case 'r':
flags |= HLS_REVERSE;
break;
case 's':
flags |= HLS_SIZE;
break;
case 't':
options = (options & ~S_MASK) | S_TIME;
break;
case 'x':
options = (options & ~F_MASK) | F_HORIZ;
break;
case 'w':
width = atoi(optarg);
break;
case 'C':
options = (options & ~F_MASK) | F_MANY;
break;
case 'F':
flags |= HLS_INDICATOR;
break;
case 'N':
flags &= ~(HLS_ESCAPE | HLS_QMARK_CTRL);
break;
case 'Q':
flags |= HLS_QUOTE | HLS_ESCAPE;
flags &= ~HLS_QMARK_CTRL;
break;
case 'R':
flags |= HLS_RECURSIVE;
break;
case 'S':
options = (options & ~S_MASK) | S_SIZE;
break;
case 'U':
options &= ~S_MASK;
break;
}
}
vol = hfsutil_remount(hcwd_getvol(-1), HFS_MODE_RDONLY);
if (vol == 0)
return 1;
fargv = hfsutil_glob(vol, argc - optind, &argv[optind], &fargc, &result);
dirs = qnew();
files = qnew();
if (result == 0 && (dirs == 0 || files == 0))
{
fprintf(stderr, "%s: not enough memory\n", argv0);
result = 1;
}
if (result == 0)
{
if (fargc == 0)
{
if (queuepath(vol, ":", dirs, files, flags) == -1)
result = 1;
}
else
{
for (i = 0; i < fargc; ++i)
{
if (queuepath(vol, fargv[i], dirs, files, flags) == -1)
{
result = 1;
break;
}
}
}
}
if (result == 0 && process(vol, dirs, files, flags, options, width) == -1)
result = 1;
if (files)
qfree(files);
if (dirs)
qfree(dirs);
hfsutil_unmount(vol, &result);
if (fargv)
free(fargv);
return result;
}