a577b96977
new flags: F_ROOTDIR and F_RR (Rock Ridge present). - Cache the SUSP LEN_SKP parameter in struct file as well. - If we open() '/', then force a read of the directory's contents so we can examine the directory record of '.' to see if Rock Ridge is present. - If Rock Ridge extensions are present, lookup Rock Ridge names in readdir().
604 lines
15 KiB
C
604 lines
15 KiB
C
/* $NetBSD: cd9660.c,v 1.5 1997/06/26 19:11:33 drochner Exp $ */
|
|
|
|
/*
|
|
* Copyright (C) 1996 Wolfgang Solfrank.
|
|
* Copyright (C) 1996 TooLs GmbH.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by TooLs GmbH.
|
|
* 4. The name of TooLs GmbH may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* Stand-alone ISO9660 file reading package.
|
|
*
|
|
* Note: This doesn't support Rock Ridge extensions, extended attributes,
|
|
* blocksizes other than 2048 bytes, multi-extent files, etc.
|
|
*/
|
|
#include <sys/param.h>
|
|
#include <string.h>
|
|
#include <sys/dirent.h>
|
|
#include <isofs/cd9660/iso.h>
|
|
#include <isofs/cd9660/cd9660_rrip.h>
|
|
|
|
#include "stand.h"
|
|
|
|
#define SUSP_CONTINUATION "CE"
|
|
#define SUSP_PRESENT "SP"
|
|
#define SUSP_STOP "ST"
|
|
#define SUSP_EXTREF "ER"
|
|
#define RRIP_NAME "NM"
|
|
|
|
typedef struct {
|
|
ISO_SUSP_HEADER h;
|
|
u_char signature [ISODCL ( 5, 6)];
|
|
u_char len_skp [ISODCL ( 7, 7)]; /* 711 */
|
|
} ISO_SUSP_PRESENT;
|
|
|
|
static int buf_read_file(struct open_file *f, char **buf_p,
|
|
size_t *size_p);
|
|
static int cd9660_open(const char *path, struct open_file *f);
|
|
static int cd9660_close(struct open_file *f);
|
|
static int cd9660_read(struct open_file *f, void *buf, size_t size,
|
|
size_t *resid);
|
|
static int cd9660_write(struct open_file *f, void *buf, size_t size,
|
|
size_t *resid);
|
|
static off_t cd9660_seek(struct open_file *f, off_t offset, int where);
|
|
static int cd9660_stat(struct open_file *f, struct stat *sb);
|
|
static int cd9660_readdir(struct open_file *f, struct dirent *d);
|
|
static int dirmatch(struct open_file *f, const char *path,
|
|
struct iso_directory_record *dp, int use_rrip, int lenskip);
|
|
static int rrip_check(struct open_file *f, struct iso_directory_record *dp,
|
|
int *lenskip);
|
|
static char *rrip_lookup_name(struct open_file *f,
|
|
struct iso_directory_record *dp, int lenskip, size_t *len);
|
|
static ISO_SUSP_HEADER *susp_lookup_record(struct open_file *f,
|
|
const char *identifier, struct iso_directory_record *dp,
|
|
int lenskip);
|
|
|
|
struct fs_ops cd9660_fsops = {
|
|
"cd9660",
|
|
cd9660_open,
|
|
cd9660_close,
|
|
cd9660_read,
|
|
cd9660_write,
|
|
cd9660_seek,
|
|
cd9660_stat,
|
|
cd9660_readdir
|
|
};
|
|
|
|
#define F_ISDIR 0x0001 /* Directory */
|
|
#define F_ROOTDIR 0x0002 /* Root directory */
|
|
#define F_RR 0x0004 /* Rock Ridge on this volume */
|
|
|
|
struct file {
|
|
int f_flags; /* file flags */
|
|
off_t f_off; /* Current offset within file */
|
|
daddr_t f_bno; /* Starting block number */
|
|
off_t f_size; /* Size of file */
|
|
daddr_t f_buf_blkno; /* block number of data block */
|
|
char *f_buf; /* buffer for data block */
|
|
int f_susp_skip; /* len_skip for SUSP records */
|
|
};
|
|
|
|
struct ptable_ent {
|
|
char namlen [ISODCL( 1, 1)]; /* 711 */
|
|
char extlen [ISODCL( 2, 2)]; /* 711 */
|
|
char block [ISODCL( 3, 6)]; /* 732 */
|
|
char parent [ISODCL( 7, 8)]; /* 722 */
|
|
char name [1];
|
|
};
|
|
#define PTFIXSZ 8
|
|
#define PTSIZE(pp) roundup(PTFIXSZ + isonum_711((pp)->namlen), 2)
|
|
|
|
#define cdb2devb(bno) ((bno) * ISO_DEFAULT_BLOCK_SIZE / DEV_BSIZE)
|
|
|
|
/* XXX these should be in the system headers */
|
|
static __inline int
|
|
isonum_722(p)
|
|
u_char *p;
|
|
{
|
|
return (*p << 8)|p[1];
|
|
}
|
|
|
|
static __inline int
|
|
isonum_732(p)
|
|
u_char *p;
|
|
{
|
|
return (*p << 24)|(p[1] << 16)|(p[2] << 8)|p[3];
|
|
}
|
|
|
|
static ISO_SUSP_HEADER *
|
|
susp_lookup_record(struct open_file *f, const char *identifier,
|
|
struct iso_directory_record *dp, int lenskip)
|
|
{
|
|
static char susp_buffer[ISO_DEFAULT_BLOCK_SIZE];
|
|
ISO_SUSP_HEADER *sh;
|
|
ISO_RRIP_CONT *shc;
|
|
char *p, *end;
|
|
int error;
|
|
size_t read;
|
|
|
|
p = dp->name + isonum_711(dp->name_len) + lenskip;
|
|
/* Names of even length have a padding byte after the name. */
|
|
if ((isonum_711(dp->name_len) & 1) == 0)
|
|
p++;
|
|
end = (char *)dp + isonum_711(dp->length);
|
|
while (p + 3 < end) {
|
|
sh = (ISO_SUSP_HEADER *)p;
|
|
if (bcmp(sh->type, identifier, 2) == 0)
|
|
return (sh);
|
|
if (bcmp(sh->type, SUSP_STOP, 2) == 0)
|
|
return (NULL);
|
|
if (bcmp(sh->type, SUSP_CONTINUATION, 2) == 0) {
|
|
shc = (ISO_RRIP_CONT *)sh;
|
|
error = f->f_dev->dv_strategy(f->f_devdata, F_READ,
|
|
cdb2devb(isonum_733(shc->location)),
|
|
ISO_DEFAULT_BLOCK_SIZE, susp_buffer, &read);
|
|
|
|
/* Bail if it fails. */
|
|
if (error != 0 || read != ISO_DEFAULT_BLOCK_SIZE)
|
|
return (NULL);
|
|
p = susp_buffer + isonum_733(shc->offset);
|
|
end = p + isonum_733(shc->length);
|
|
} else
|
|
/* Ignore this record and skip to the next. */
|
|
p += isonum_711(sh->length);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static char *
|
|
rrip_lookup_name(struct open_file *f, struct iso_directory_record *dp,
|
|
int lenskip, size_t *len)
|
|
{
|
|
ISO_RRIP_ALTNAME *p;
|
|
|
|
if (len == NULL)
|
|
return (NULL);
|
|
|
|
p = (ISO_RRIP_ALTNAME *)susp_lookup_record(f, RRIP_NAME, dp, lenskip);
|
|
if (p == NULL)
|
|
return (NULL);
|
|
switch (*p->flags) {
|
|
case ISO_SUSP_CFLAG_CURRENT:
|
|
*len = 1;
|
|
return (".");
|
|
case ISO_SUSP_CFLAG_PARENT:
|
|
*len = 2;
|
|
return ("..");
|
|
case 0:
|
|
*len = isonum_711(p->h.length) - 5;
|
|
return ((char *)p + 5);
|
|
default:
|
|
/*
|
|
* We don't handle hostnames or continued names as they are
|
|
* too hard, so just bail and use the default name.
|
|
*/
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
static int
|
|
rrip_check(struct open_file *f, struct iso_directory_record *dp, int *lenskip)
|
|
{
|
|
ISO_SUSP_PRESENT *sp;
|
|
ISO_RRIP_EXTREF *er;
|
|
char *p;
|
|
|
|
/* First, see if we can find a SP field. */
|
|
p = dp->name + isonum_711(dp->name_len);
|
|
if (p > (char *)dp + isonum_711(dp->length))
|
|
return (0);
|
|
sp = (ISO_SUSP_PRESENT *)p;
|
|
if (bcmp(sp->h.type, SUSP_PRESENT, 2) != 0)
|
|
return (0);
|
|
if (isonum_711(sp->h.length) != sizeof(ISO_SUSP_PRESENT))
|
|
return (0);
|
|
if (sp->signature[0] != 0xbe || sp->signature[1] != 0xef)
|
|
return (0);
|
|
*lenskip = isonum_711(sp->len_skp);
|
|
|
|
/*
|
|
* Now look for an ER field. If RRIP is present, then there must
|
|
* be at least one of these. It would be more pedantic to walk
|
|
* through the list of fields looking for a Rock Ridge ER field.
|
|
*/
|
|
er = (ISO_RRIP_EXTREF *)susp_lookup_record(f, SUSP_EXTREF, dp, 0);
|
|
if (er == NULL)
|
|
return (0);
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
dirmatch(struct open_file *f, const char *path, struct iso_directory_record *dp,
|
|
int use_rrip, int lenskip)
|
|
{
|
|
size_t len;
|
|
char *cp;
|
|
int i, icase;
|
|
|
|
if (use_rrip)
|
|
cp = rrip_lookup_name(f, dp, lenskip, &len);
|
|
else
|
|
cp = NULL;
|
|
if (cp == NULL) {
|
|
len = isonum_711(dp->name_len);
|
|
cp = dp->name;
|
|
icase = 1;
|
|
} else
|
|
icase = 0;
|
|
for (i = len; --i >= 0; path++, cp++) {
|
|
if (!*path || *path == '/')
|
|
break;
|
|
if (*path == *cp)
|
|
continue;
|
|
if (!icase && toupper(*path) == *cp)
|
|
continue;
|
|
return 0;
|
|
}
|
|
if (*path && *path != '/')
|
|
return 0;
|
|
/*
|
|
* Allow stripping of trailing dots and the version number.
|
|
* Note that this will find the first instead of the last version
|
|
* of a file.
|
|
*/
|
|
if (i >= 0 && (*cp == ';' || *cp == '.')) {
|
|
/* This is to prevent matching of numeric extensions */
|
|
if (*cp == '.' && cp[1] != ';')
|
|
return 0;
|
|
while (--i >= 0)
|
|
if (*++cp != ';' && (*cp < '0' || *cp > '9'))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
cd9660_open(const char *path, struct open_file *f)
|
|
{
|
|
struct file *fp = 0;
|
|
void *buf;
|
|
struct iso_primary_descriptor *vd;
|
|
size_t buf_size, read, dsize, off;
|
|
daddr_t bno, boff;
|
|
struct iso_directory_record rec;
|
|
struct iso_directory_record *dp = 0;
|
|
int rc, first, use_rrip, lenskip;
|
|
|
|
/* First find the volume descriptor */
|
|
buf = malloc(buf_size = ISO_DEFAULT_BLOCK_SIZE);
|
|
vd = buf;
|
|
for (bno = 16;; bno++) {
|
|
twiddle();
|
|
rc = f->f_dev->dv_strategy(f->f_devdata, F_READ, cdb2devb(bno),
|
|
ISO_DEFAULT_BLOCK_SIZE, buf, &read);
|
|
if (rc)
|
|
goto out;
|
|
if (read != ISO_DEFAULT_BLOCK_SIZE) {
|
|
rc = EIO;
|
|
goto out;
|
|
}
|
|
rc = EINVAL;
|
|
if (bcmp(vd->id, ISO_STANDARD_ID, sizeof vd->id) != 0)
|
|
goto out;
|
|
if (isonum_711(vd->type) == ISO_VD_END)
|
|
goto out;
|
|
if (isonum_711(vd->type) == ISO_VD_PRIMARY)
|
|
break;
|
|
}
|
|
if (isonum_723(vd->logical_block_size) != ISO_DEFAULT_BLOCK_SIZE)
|
|
goto out;
|
|
|
|
rec = *(struct iso_directory_record *) vd->root_directory_record;
|
|
if (*path == '/') path++; /* eat leading '/' */
|
|
|
|
first = 1;
|
|
use_rrip = 0;
|
|
while (*path) {
|
|
bno = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
|
|
dsize = isonum_733(rec.size);
|
|
off = 0;
|
|
boff = 0;
|
|
|
|
while (off < dsize) {
|
|
if ((off % ISO_DEFAULT_BLOCK_SIZE) == 0) {
|
|
twiddle();
|
|
rc = f->f_dev->dv_strategy
|
|
(f->f_devdata, F_READ,
|
|
cdb2devb(bno + boff),
|
|
ISO_DEFAULT_BLOCK_SIZE,
|
|
buf, &read);
|
|
if (rc)
|
|
goto out;
|
|
if (read != ISO_DEFAULT_BLOCK_SIZE) {
|
|
rc = EIO;
|
|
goto out;
|
|
}
|
|
boff++;
|
|
dp = (struct iso_directory_record *) buf;
|
|
}
|
|
if (isonum_711(dp->length) == 0) {
|
|
/* skip to next block, if any */
|
|
off = boff * ISO_DEFAULT_BLOCK_SIZE;
|
|
continue;
|
|
}
|
|
|
|
/* See if RRIP is in use. */
|
|
if (first)
|
|
use_rrip = rrip_check(f, dp, &lenskip);
|
|
|
|
if (dirmatch(f, path, dp, use_rrip,
|
|
first ? 0 : lenskip)) {
|
|
first = 0;
|
|
break;
|
|
} else
|
|
first = 0;
|
|
|
|
dp = (struct iso_directory_record *)
|
|
((char *) dp + isonum_711(dp->length));
|
|
off += isonum_711(dp->length);
|
|
}
|
|
if (off >= dsize) {
|
|
rc = ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
rec = *dp;
|
|
while (*path && *path != '/') /* look for next component */
|
|
path++;
|
|
if (*path) path++; /* skip '/' */
|
|
}
|
|
|
|
/* allocate file system specific data structure */
|
|
fp = malloc(sizeof(struct file));
|
|
bzero(fp, sizeof(struct file));
|
|
f->f_fsdata = (void *)fp;
|
|
|
|
if ((isonum_711(rec.flags) & 2) != 0) {
|
|
fp->f_flags = F_ISDIR;
|
|
}
|
|
if (first) {
|
|
fp->f_flags |= F_ROOTDIR;
|
|
|
|
/* Check for Rock Ridge since we didn't in the loop above. */
|
|
bno = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
|
|
twiddle();
|
|
rc = f->f_dev->dv_strategy(f->f_devdata, F_READ, cdb2devb(bno),
|
|
ISO_DEFAULT_BLOCK_SIZE, buf, &read);
|
|
if (rc)
|
|
goto out;
|
|
if (read != ISO_DEFAULT_BLOCK_SIZE) {
|
|
rc = EIO;
|
|
goto out;
|
|
}
|
|
dp = (struct iso_directory_record *)buf;
|
|
use_rrip = rrip_check(f, dp, &lenskip);
|
|
}
|
|
if (use_rrip) {
|
|
fp->f_flags |= F_RR;
|
|
fp->f_susp_skip = lenskip;
|
|
}
|
|
fp->f_off = 0;
|
|
fp->f_bno = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
|
|
fp->f_size = isonum_733(rec.size);
|
|
free(buf);
|
|
|
|
return 0;
|
|
|
|
out:
|
|
if (fp)
|
|
free(fp);
|
|
free(buf);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
cd9660_close(struct open_file *f)
|
|
{
|
|
struct file *fp = (struct file *)f->f_fsdata;
|
|
|
|
f->f_fsdata = 0;
|
|
free(fp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
buf_read_file(struct open_file *f, char **buf_p, size_t *size_p)
|
|
{
|
|
struct file *fp = (struct file *)f->f_fsdata;
|
|
daddr_t blkno, blkoff;
|
|
int rc = 0;
|
|
size_t read;
|
|
|
|
blkno = fp->f_off / ISO_DEFAULT_BLOCK_SIZE + fp->f_bno;
|
|
blkoff = fp->f_off % ISO_DEFAULT_BLOCK_SIZE;
|
|
|
|
if (blkno != fp->f_buf_blkno) {
|
|
if (fp->f_buf == (char *)0)
|
|
fp->f_buf = malloc(ISO_DEFAULT_BLOCK_SIZE);
|
|
|
|
twiddle();
|
|
rc = f->f_dev->dv_strategy(f->f_devdata, F_READ,
|
|
cdb2devb(blkno), ISO_DEFAULT_BLOCK_SIZE, fp->f_buf, &read);
|
|
if (rc)
|
|
return (rc);
|
|
if (read != ISO_DEFAULT_BLOCK_SIZE)
|
|
return (EIO);
|
|
|
|
fp->f_buf_blkno = blkno;
|
|
}
|
|
|
|
*buf_p = fp->f_buf + blkoff;
|
|
*size_p = ISO_DEFAULT_BLOCK_SIZE - blkoff;
|
|
|
|
if (*size_p > fp->f_size - fp->f_off)
|
|
*size_p = fp->f_size - fp->f_off;
|
|
return (rc);
|
|
}
|
|
|
|
static int
|
|
cd9660_read(struct open_file *f, void *start, size_t size, size_t *resid)
|
|
{
|
|
struct file *fp = (struct file *)f->f_fsdata;
|
|
char *buf, *addr;
|
|
size_t buf_size, csize;
|
|
int rc = 0;
|
|
|
|
addr = start;
|
|
while (size) {
|
|
if (fp->f_off < 0 || fp->f_off >= fp->f_size)
|
|
break;
|
|
|
|
rc = buf_read_file(f, &buf, &buf_size);
|
|
if (rc)
|
|
break;
|
|
|
|
csize = size > buf_size ? buf_size : size;
|
|
bcopy(buf, addr, csize);
|
|
|
|
fp->f_off += csize;
|
|
addr += csize;
|
|
size -= csize;
|
|
}
|
|
if (resid)
|
|
*resid = size;
|
|
return (rc);
|
|
}
|
|
|
|
static int
|
|
cd9660_readdir(struct open_file *f, struct dirent *d)
|
|
{
|
|
struct file *fp = (struct file *)f->f_fsdata;
|
|
struct iso_directory_record *ep;
|
|
size_t buf_size, reclen, namelen;
|
|
int error = 0;
|
|
int lenskip;
|
|
char *buf, *name;
|
|
|
|
again:
|
|
if (fp->f_off >= fp->f_size)
|
|
return (ENOENT);
|
|
error = buf_read_file(f, &buf, &buf_size);
|
|
if (error)
|
|
return (error);
|
|
ep = (struct iso_directory_record *)buf;
|
|
|
|
if (isonum_711(ep->length) == 0) {
|
|
daddr_t blkno;
|
|
|
|
/* skip to next block, if any */
|
|
blkno = fp->f_off / ISO_DEFAULT_BLOCK_SIZE;
|
|
fp->f_off = (blkno + 1) * ISO_DEFAULT_BLOCK_SIZE;
|
|
goto again;
|
|
}
|
|
|
|
if (fp->f_flags & F_RR) {
|
|
if (fp->f_flags & F_ROOTDIR && fp->f_off == 0)
|
|
lenskip = 0;
|
|
else
|
|
lenskip = fp->f_susp_skip;
|
|
name = rrip_lookup_name(f, ep, lenskip, &namelen);
|
|
} else
|
|
name = NULL;
|
|
if (name == NULL) {
|
|
namelen = isonum_711(ep->name_len);
|
|
name = ep->name;
|
|
if (namelen == 1) {
|
|
if (ep->name[0] == 0)
|
|
name = ".";
|
|
else if (ep->name[0] == 1) {
|
|
namelen = 2;
|
|
name = "..";
|
|
}
|
|
}
|
|
}
|
|
reclen = sizeof(struct dirent) - (MAXNAMLEN+1) + namelen + 1;
|
|
reclen = (reclen + 3) & ~3;
|
|
|
|
d->d_fileno = isonum_733(ep->extent);
|
|
d->d_reclen = reclen;
|
|
if (isonum_711(ep->flags) & 2)
|
|
d->d_type = DT_DIR;
|
|
else
|
|
d->d_type = DT_REG;
|
|
d->d_namlen = namelen;
|
|
|
|
bcopy(name, d->d_name, d->d_namlen);
|
|
d->d_name[d->d_namlen] = 0;
|
|
|
|
fp->f_off += isonum_711(ep->length);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
cd9660_write(struct open_file *f, void *start, size_t size, size_t *resid)
|
|
{
|
|
return EROFS;
|
|
}
|
|
|
|
static off_t
|
|
cd9660_seek(struct open_file *f, off_t offset, int where)
|
|
{
|
|
struct file *fp = (struct file *)f->f_fsdata;
|
|
|
|
switch (where) {
|
|
case SEEK_SET:
|
|
fp->f_off = offset;
|
|
break;
|
|
case SEEK_CUR:
|
|
fp->f_off += offset;
|
|
break;
|
|
case SEEK_END:
|
|
fp->f_off = fp->f_size - offset;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return fp->f_off;
|
|
}
|
|
|
|
static int
|
|
cd9660_stat(struct open_file *f, struct stat *sb)
|
|
{
|
|
struct file *fp = (struct file *)f->f_fsdata;
|
|
|
|
/* only important stuff */
|
|
sb->st_mode = S_IRUSR | S_IRGRP | S_IROTH;
|
|
if (fp->f_flags & F_ISDIR)
|
|
sb->st_mode |= S_IFDIR;
|
|
else
|
|
sb->st_mode |= S_IFREG;
|
|
sb->st_uid = sb->st_gid = 0;
|
|
sb->st_size = fp->f_size;
|
|
return 0;
|
|
}
|