/* * iso.c * * Description: * Routines called from nfs_server that know all about the iso 9660 * file system format * * Note: * Thie file descriptor of a directory is The disc offset in * bytes of its self-referential directory entry. It used to be * the directory entry in the parent directory, but there are * some buggy CDs out there that have bogus ".." entries that * messed up that scheme. --rogerc 3/20/96 * * Caching: * Directory and extended attribute blocks are cached in the * cdrom module. Data file blocks are not cached; client NFS * takes care of this, so there's no need to use the memory. * This module controls which blocks are cached via the last * parameter to cd_read. * * History: * rogerc 12/21/90 Created * rogerc 01/25/91 Cache only directory blocks * rogerc 01/25/91 Changes for gfs * rogerc 04/02/91 Adding support for High Sierra * rogerc 02/12/92 Added support for block sizes other than * 2048 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iso.h" #include "util.h" #include "main.h" #define ROOT_DIR() (cd_voldesc(cd) + (int)&((struct p_vol_desc *)(0))->root) #define HS_ROOT_DIR() (cd_voldesc(cd) + (int)&((hsvol_t *)(0))->root) #define ISO_MAXNAMLEN (30 + 2 + 5) #define PERM_RALL 0444 #define PERM_XALL 0111 #define PERM_RXALL 0555 #define max(a,b) ((a) < (b) ? (b) : (a)) #define CHARSTOLONG(chars) ((chars)[0] << 24 | (chars)[1] << 16 \ | (chars)[2] << 8 | (chars)[3]) #define CHARSTOSHORT(chars) (chars[0] << 8 | chars[1]) #define DIRTOFILE(dirp,fp) {\ (fp)->block = CHARSTOLONG((dirp)->ext_loc_msb);\ (fp)->xattr_len = (dirp)->xattr_len;\ (fp)->int_gap = (dirp)->int_gap;\ (fp)->fu_size = (dirp)->fu_size;\ } #define ISLASTDIRENT(dirp,ent,contents,len) \ (!((char *)ent-((char *)contents) < len - dirp->xattr_len && ent->len_dir)) /* * Construct a file descriptor given the directory entry of its * parent, the address of its contents (base), the offset into the * contents of the directory entry (dirp), and the block size of the cd. */ #define MKFD(parent,base,dirp,blksize) \ (dirp && (((dir_t *)dirp)->flags & FFLAG_DIRECTORY) \ ? ((CHARSTOLONG(((dir_t *)dirp)->ext_loc_msb) \ + ((dir_t *)dirp)->xattr_len) * blksize) \ : ((CHARSTOLONG(parent->ext_loc_msb) + parent->xattr_len) * blksize \ + ((char *)dirp - (char *)base))) #ifdef DEBUG #define ISODBG(x) {if (isodebug) {x;}} #else #define ISODBG(x) #endif #define FLAGS(d) (fsType == ISO ? ((dir_t *)d)->flags : \ ((hsdir_t *)d)->flags) #define IS_LEAPYEAR(year) \ ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))) #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif /* * Special stuff so we can recognize SUN executables. This is the * format of the executable file header on a 68000 or SPARC based * SUN system. Their 386i line is handled by COFF. */ #define sun struct exec { #ifdef sun unsigned char a_dynamic:1; /* has a __DYNAMIC */ unsigned char a_toolversion:7;/* version of toolset used to create this file */ unsigned char a_machtype; /* machine type */ unsigned short a_magic; /* magic number */ #else unsigned long a_magic; /* magic number */ #endif unsigned long a_text; /* size of text segment */ unsigned long a_data; /* size of initialized data */ unsigned long a_bss; /* size of uninitialized data */ unsigned long a_syms; /* size of symbol table */ unsigned long a_entry; /* entry point */ unsigned long a_trsize; /* size of text relocation */ unsigned long a_drsize; /* size of data relocation */ }; #define OMAGIC 0407 /* old impure format */ #define NMAGIC 0410 /* read-only text */ #define ZMAGIC 0413 /* demand load format */ #define N_BADMAG(x) \ ((x).a_magic!=OMAGIC && (x).a_magic!=NMAGIC && (x).a_magic!=ZMAGIC) #undef sun /* * Sometimes we need to differentiate between iso9660 and high sierra */ typedef enum cdtype { ISO, HSFS } cdtype_t; /* * Structure that we parse rock ridge extensions into. The flags at * the beginning get set when we encounter the corresponding system * use record; we need this because not all fields are necessarily * present and we need to know to fall back onto base ISO 9660. */ typedef struct _ext { unsigned int gotName : 1; unsigned int gotPath : 1; unsigned int gotMode : 1; unsigned int gotATime : 1, gotMTime : 1, gotCTime : 1; unsigned int isRelocated : 1; unsigned int doSlash : 1; char name[NFS_MAXNAMLEN]; char path[NFS_MAXNAMLEN]; u_int mode; u_int nlink; u_int uid, gid; ftype type; nfstime atime, mtime, ctime; u_int rdev; int parent, child; } EXT; static char dot[] = "."; static char dotdot[] = ".."; /* * "notranslate" controls the "to_unixname" name translation. * * 'c' = the user will see raw, ugly file names * 'l' = the user will see lower-case, ugly file names * 'm' = the user will see upper-case names less the version # * 'x' = the user will see lower-case names less the version # */ static char notranslate = 'x'; /* The internal default, */ /* acts like 'l'+'m' */ /* * if setx == 1, we'll blindly set execute permissions for every file. */ static int setx = 0; static int def_num_blocks = 256; /* backup default */ static int useCache = 1; /* * By default, use rock ridge extensions if they're there. */ static int useExt = 1; /* * Whether or not CD has extensions */ static int hasExt = 0; /* * Number of bytes in each system use area to skip */ static int skipSysBytes = 0; /* * The location on the CD of the '.' entry of the root directory. * This is use in ext_first_field when reading Rock Ridge extensions * to determine whether or not to skip skipSysBytes. */ static int rootSelfEntry = 0; int isodebug = 0; /* * Stuff for the cdrom module */ static CDROM *cd; /* * Number of blocks in the mounted file system */ static int fsBlocks; /* * Type of file system - iso9660 or high sierra */ static cdtype_t fsType; /* * The root file handle of the mounted file system */ static fhandle_t rootfh; /* * The path name of the mount point of the file system */ static char *mountPoint; /* * Forward declarations of static functions */ static int set_blocks(); static int get_parent_fd(int fd, int *fdp); static void add_entry(entry *entries, int fd, char *name, int name_len, int cookie); static int read_dir(dir_t *dirp, int dirfd, dir_t **datap, int *len); static int read_dir_entry(int fd, dir_t **dpp); static void convert_time(struct nfstime *to, struct date_time *from, enum cdtype type); static int convert_to_seconds(int year, int month, int day, int hour, int min, int sec, int csec, int gw); static int valid_day(int day, int month, int year); static int days_so_far(int day, int month, int year); static char * to_unixname(char *name, int length); /* * Rock Ridge support functions */ static void check_for_extensions(void); static void ext_parse_extensions(dir_t *dirp, int fd, EXT *ext); static int ext_get_relo_dir(EXT *ext, dir_t *inDir, int infd, dir_t **outDir, int *outfd); /* * Support for keeping one directory entry privately "cached". */ static int read_dir_entry_cached(int fd, dir_t **dpp, multi_xtnt_t **mpp); static int dirc_hit = 0; static int dirc_miss = 0; static int dirc_last_fd = -1; static dir_t *dirc_dirp = NULL; static multi_xtnt_t *dirc_mep = NULL; /* * Level 3 (Multi-Extent) support data & functions */ /* * Must be a power of 2! */ #define MULTI_XTNT_HASH_SIZE 16 /* * Most (all that I've seen) fileid's are even numbers, * so drop the bottom bit in the hash function. */ #define MULTI_XTNT_HASH(Fd) ((Fd >> 1) & (MULTI_XTNT_HASH_SIZE-1)) static multi_xtnt_t *multi_extent_head[MULTI_XTNT_HASH_SIZE]; static multi_xtnt_t *multi_extent_tail[MULTI_XTNT_HASH_SIZE]; static void iso_me_add_xtnt(multi_xtnt_t *mep, int fileid, int blksize, int start_blk, int length) { one_xtnt_t *oep; oep = safe_malloc(sizeof(one_xtnt_t)); oep->oe_fileid = fileid; oep->oe_start_blk = start_blk; oep->oe_length = length; oep->oe_next = NULL; oep->oe_start_byte = mep->me_length; mep->me_length += length; mep->me_num_xtnts++; if (mep->me_xtnts_head == NULL) { mep->me_xtnts_head = oep; mep->me_xtnts_tail = oep; } else { mep->me_xtnts_tail->oe_next = oep; mep->me_xtnts_tail = oep; } } static multi_xtnt_t * iso_me_add_file(int fileid) { int hash; multi_xtnt_t *mep; hash = MULTI_XTNT_HASH(fileid); /* * Scan the chain to see if we already "know" this guy. */ for (mep = multi_extent_head[hash]; mep != NULL; mep = mep->me_next) { if (mep->me_fileid == fileid) return mep; } /* * Not found, so make a new one. */ mep = safe_malloc(sizeof(multi_xtnt_t)); mep->me_fileid = fileid; mep->me_next = NULL;; mep->me_xtnts_head = NULL; mep->me_xtnts_tail = NULL; mep->me_length = 0; mep->me_num_xtnts = 0; if (multi_extent_head[hash] == NULL) { multi_extent_head[hash] = mep; multi_extent_tail[hash] = mep; } else { multi_extent_tail[hash]->me_next = mep; multi_extent_tail[hash] = mep; } return mep; } static multi_xtnt_t * iso_me_search(int fileid) { int hash; multi_xtnt_t *mep; hash = MULTI_XTNT_HASH(fileid); /* * Scan the chain to see if we already "know" this guy. */ for (mep = multi_extent_head[hash]; mep != NULL; mep = mep->me_next) { if (mep->me_fileid == fileid) return mep; } return NULL; } void iso_me_flush() { int hash; multi_xtnt_t *mep; multi_xtnt_t *mep2free; one_xtnt_t *oep; one_xtnt_t *oep2free; ISODBG(fprintf(stderr, ">>>>iso: iso_me_flush\n")); /* * Scan the chains */ for (hash = 0; hash < MULTI_XTNT_HASH_SIZE; hash++) { mep = multi_extent_head[hash]; while (mep) { mep2free = mep; oep = mep->me_xtnts_head; while (oep) { oep2free = oep; oep = oep->oe_next; free(oep2free); } mep = mep->me_next; free(mep2free); } multi_extent_head[hash] = NULL; multi_extent_tail[hash] = NULL; } } void iso_me_dump() { int i; int hash; multi_xtnt_t *mep; one_xtnt_t *oep; /* * Scan the chains */ for (hash = 0; hash < MULTI_XTNT_HASH_SIZE; hash++) { for (mep = multi_extent_head[hash]; mep != NULL; mep = mep->me_next) { fprintf(stderr, ">>>>iso: iso_me_dump: MULTI[%d] fileid/%d numxtnts/%d length/%d\n", hash, mep->me_fileid, mep->me_num_xtnts, mep->me_length); i = 0; oep = mep->me_xtnts_head; while (oep) { fprintf(stderr, ">>>>iso: iso_me_dump: %d: fd/%d stblk/%d start/%d length/%d\n", i++, oep->oe_fileid, oep->oe_start_blk, oep->oe_start_byte, oep->oe_length); oep = oep->oe_next; } } } } /* * static dir_t * * skip_zero_dir(dir_t *dp, contents, len, int blksize) * * Description: * Skip past zero filled bits at the end of a block in a * directory. directory entries are supposed to end in the same * block in which they begin, so if there's some space left over * but not enough to hold an entire directory entry we need to * skip over that space to the beginning of the next block. * * Parameters: * dp Points to a directory entry * contents Pointer to the beginning of the directory into which * dp points * len len of the directory pointed to by contents * blksize Size of a block on this CD * * Returns: * dp if it fits */ static dir_t * skip_zero_dir(dir_t *dp, dir_t *contents, int len, int blksize) { int block; if (!dp->len_dir && (char *)dp - (char *)contents < len) { /* * Directories are supposed to end in the same block in * which they begin, so this starts at the next block. The * zero in dp->len_dir is zero fill from the unused portion of * a block in the middle of a directory. */ block = ((char *)dp - (char *)contents + blksize - 1) / blksize; if (block * blksize < len) { dp = (dir_t *) ((char *)contents + block * blksize); } } return dp; } /* * static dir_t * * first_dir(dir_t *dirp, dir_t *contents, int len, int cookie, int blksize) * * Description: * Return the first directory entry found in contents, or the * directory entry found cookie bytes into the contents. * * Parameters: * dirp dir_t structure of directory whose contents we're * looking at * contents Contents of the directory * len length of contents * cookie Byte offset into the directory to start. This * exists because of the way nfs does readdir. * blksize The blocksize used by the CD. This is needed for * calculating when we've reached the end of a block * * Returns: * Pointer to the first directory entry in contents */ static dir_t * first_dir(dir_t *dirp, dir_t *contents, int len, int cookie, int blksize) { dir_t *dp; int block; dp = (dir_t *)((char *)contents + cookie); dp = skip_zero_dir(dp, contents, len, blksize); return ISLASTDIRENT(dirp, dp, contents, len) ? NULL : dp; } /* * static dir_t * * next_dir(dir_t *dirp, dir_t *dp, dir_t *contents, int len, int blksize) * * Description: * Return a pointer to the next entry in a directory after dp, or * NULL if dp is the last entry. * * Parameters: * dirp directory whose contents we're searching * dp dp before the one we want * contents contents of the directory * len length of the directory contents * blksize The blocksize used by the CD. This is needed for * calculating when we've reached the end of a block * * Returns: * pointer to next directory, NULL if dp was the last one. */ static dir_t * next_dir(dir_t *dirp, dir_t *dp, dir_t *contents, int len, int blksize) { int block; dp = (dir_t *)((char *)dp + dp->len_dir); dp = skip_zero_dir(dp, contents, len, blksize); return ISLASTDIRENT(dirp, dp, contents, len) ? NULL : dp; } /* * void * iso_init(void) * * Description: * Initialization for the iso module */ void iso_init(int num_blocks) { int i; def_num_blocks = num_blocks; useCache = useCache && num_blocks > 0; for (i = 0; i < MULTI_XTNT_HASH_SIZE; i++) { multi_extent_head[i] = NULL; multi_extent_tail[i] = NULL; } } /* * int * iso_numblocks(unsigned int *blocks) * * Description: * Fill in blocks with the number of blocks in the file system * * Parameters: * blocks receives # of blocks * * Returns: * 0 if successful, error code otherwise */ int iso_numblocks(unsigned int *blocks) { int error, changed; error = cd_media_changed(cd, &changed); if (error) return error; if (changed) { cd_flush_cache(cd); iso_me_flush(); } error = set_blocks(); if (error) return error; *blocks = (unsigned int)fsBlocks; ISODBG(fprintf(stderr, ">>>>iso: iso_numblocks() == %d\n", *blocks)); return 0; } /* * int * iso_isfd(int fd) * * Description: * Determine whether fd is a file descriptor that we care about, so * that the caller knows not to close it * * Parameters: * fd file descriptor * * Returns: * 1 if we care, 0 if we don't */ int iso_isfd(int fd) { return cd_is_dsp_fd(cd, fd); } /* * int * iso_openfs(char *dev, char *mntpnt, int flags, int partition) * * Description: * Get ready to mount the dev on mntpnt * * Parameters: * dev Device for CD-ROM drive * mntpnt The mount point * root gets root file handle * * Returns: * 0 if successful, error code otherwise */ int iso_openfs(char *dev, char *mntpnt, fhandle_t *root) { int error; static dev_t fakedev = 1; struct stat sb; ISODBG(fprintf(stderr, ">>>>iso: iso_addfs(%s)\n", dev)); error = cd_open(dev, def_num_blocks, &cd); if (error) return error; error = cd_stat(cd, &sb); if (error) return error; rootfh.fh_dev = S_ISREG(sb.st_mode) ? 0xff00 | (fakedev++ & 0xff) : sb.st_rdev; mountPoint = strdup(mntpnt); error = set_blocks(); /* * We need to do this so that the user can eject. */ if (error) { cd_close(cd); } else { bcopy(&rootfh, root, sizeof *root); } return error; } /* * void * iso_removefs() * * Description: * ISO 9660 specific removal of a generic file system * * Parameters: */ void iso_removefs() { cd_close(cd); ISODBG( iso_me_dump() ); ISODBG(fprintf(stderr, ">>>>iso: read_dir_cached: hits/%d misses/%d\n", dirc_hit, dirc_miss)); } /* * int * iso_lookup(fhandle_t *fh, char *name, fhandle_t *fh_ret) * * Description: * Look for name in fh (a directory); if found, fill in fh_ret * * Parameters: * fh file handle of directory in which to look * name name of file we're looking for * fh_ret Receive handle for name * * Returns: * 0 if successful, error code otherwise */ int iso_lookup(fhandle_t *fh, char *name, fhandle_t *fh_ret) { int blksize; int multi_seen = 0; int error, fd, tmpfd, len, dirfd, newfd; char *uname; dir_t *dirp; /* directory entry for dir */ dir_t *contents; /* contents of dir */ dir_t *dp; /* pointer into contents */ dir_t *newdirp; EXT ext; ISODBG(fprintf(stderr, ">>>>iso: iso_lookup(%d, %s)\n", fh->fh_fno, name)); blksize = cd_get_blksize(cd); if (strcmp(name, dot) == 0) { *fh_ret = *fh; return 0; } else if (strcmp(name, dotdot) == 0) { error = get_parent_fd(fh->fh_fno, &fd); if (error) return error; bzero(fh_ret, sizeof *fh_ret); fh_ret->fh_dev = fh->fh_dev; fh_ret->fh_fid.lfid_fno = fd; fh_ret->fh_fid.lfid_len = sizeof fh_ret->fh_fid - sizeof fh_ret->fh_fid.lfid_len; return 0; } error = read_dir_entry(fh->fh_fno, &dirp); if (error) return error; dirfd = fh->fh_fno; if (hasExt && useExt) { ext_parse_extensions(dirp, dirfd, &ext); error = ext_get_relo_dir(&ext, dirp, dirfd, &newdirp, &newfd); if (error) { free(dirp); return error; } if (dirp != newdirp) { free(dirp); dirp = newdirp; dirfd = newfd; } } error = read_dir(dirp, dirfd, &contents, &len); if (error) { free(dirp); return error; } fd = 0; for (dp = first_dir(dirp, contents, len, 0, blksize); dp; dp = next_dir(dirp, dp, contents, len, blksize)) { tmpfd = MKFD(dirp, contents, dp, blksize); /* * Skip associated files */ if (dp->flags & FFLAG_ASSOC) continue; if (useExt && hasExt) { ext_parse_extensions(dp, tmpfd, &ext); /* * Skip relocated directories */ if (ext.isRelocated) { continue; } uname = ext.gotName ? ext.name : to_unixname(dp->file_id, dp->len_fi); } else { uname = to_unixname(dp->file_id, dp->len_fi); } if (strcmp(name, uname) == 0) { /* * Only "remember" the fd of the 1st of * a series for Multi-Extent files. */ /* * Do the mapping and return the inode number of the relocated child * as opposed to the original number. Why? Places like getcwd() * do keep track of the inode numbers to make sure that child directories * inode numbers show up in a read of the parent's directory, which isn't * the case if the mapping is not done. */ if (! fd) { fd = (useExt && hasExt && ext.child) ? ext.child * blksize : tmpfd; } if (dp->flags & FFLAG_MULTI) { multi_xtnt_t *mep; /* * Check to see if we already know the extents for this file. */ if (! multi_seen) { /* 1st extent? */ mep = iso_me_search(fd); /* Try to find them */ if (mep) { /* If we found them: */ ISODBG(fprintf(stderr, ">>>>iso: iso_lookup: MULTI already built fd/%d\n", fh->fh_fno)); break; /* that's all we need */ } } multi_seen++; /* Into multi-extents */ /* mode */ mep = iso_me_add_file(fd); iso_me_add_xtnt(mep, tmpfd, blksize, CHARSTOLONG(dp->ext_loc_msb), CHARSTOLONG(dp->data_len_msb)); ISODBG(fprintf(stderr, ">>>>iso: iso_lookup: MULTI=1 fd/%d tmpfd/%d ext_loc_msb/%d xattr_len/%d size/%d\n", fd, tmpfd, CHARSTOLONG(dp->ext_loc_msb), dp->xattr_len, CHARSTOLONG(dp->data_len_msb))); continue; /* Read the next directory entry */ } else { multi_xtnt_t *mep; /* * Are we scanning a set of multi-extent entries? */ if (! multi_seen) break; mep = iso_me_add_file(fd); iso_me_add_xtnt(mep, tmpfd, blksize, CHARSTOLONG(dp->ext_loc_msb), CHARSTOLONG(dp->data_len_msb)); ISODBG(fprintf(stderr, ">>>>iso: iso_lookup: MULTI=0 fd/%d tmpfd/%d ext_loc_msb/%d xattr_len/%d size/%d\n", fd, tmpfd, CHARSTOLONG(dp->ext_loc_msb), dp->xattr_len, CHARSTOLONG(dp->data_len_msb))); break; /* We've read enough */ } } } free(dirp); free(contents); if (fd) { bzero(fh_ret, sizeof *fh_ret); fh_ret->fh_dev = fh->fh_dev; fh_ret->fh_fid.lfid_fno = fd; fh_ret->fh_fid.lfid_len = sizeof fh_ret->fh_fid - sizeof fh_ret->fh_fid.lfid_len; return 0; } return ENOENT; } /* * int * iso_read(fhandle_t *fh, int offset, int count, char *data, * unsigned int *amountRead) * * Description: * Read count bytes from file fh into buffer data * * Parameters: * fh file handle of file from which to read * offset offset into file to start reading * count number of bytes to read * data buffer to read bytes into * amountRead Amount of data actually read * * Returns: * 0 if successful, error code otherwise */ int iso_read(fhandle_t *fh, int offset, int count, char *data, unsigned int *amountRead) { int error; long fileSize; dir_t *dirp; multi_xtnt_t *mep; CD_FILE f; ISODBG(fprintf(stderr, ">>>>iso: iso_read(fd/%d, offset/%d, count/%d)\n", fh->fh_fno, offset, count)); /* * We use the privately "cached" directory entry because NFS reads * files in 8K chunks; if we're reading a large file sequentially, * we don't want to waste time re-reading the directory entry each time. */ error = read_dir_entry_cached(fh->fh_fno, &dirp, &mep); if (error) return error; /* * Figure out where the end of the file is, and adjust count * accordingly. * * If this is a Multi-Extent file, and we found the extent * list information collected by lookup, use it. */ if (mep) fileSize = mep->me_length; else fileSize = CHARSTOLONG(dirp->data_len_msb); if (offset >= fileSize) { *amountRead = 0; return 0; } DIRTOFILE(dirp, &f); count = MIN(count, fileSize - offset); /* * If this is a Multi-Extent file, and we found the extent * list information collected by lookup, use it to cycle * through the extents to find the right ones. */ if (mep) { int rem_count; int now_count; int now_offset; int xtnt_num = 0; char *buf; one_xtnt_t *oep; buf = data; rem_count = count; *amountRead = 0; oep = mep->me_xtnts_head; while (oep && rem_count) { long xtnt_strt; long xtnt_end; xtnt_strt = oep->oe_start_byte; xtnt_end = xtnt_strt + oep->oe_length; if (offset >= xtnt_strt && offset < xtnt_end) { now_count = MIN(rem_count, xtnt_end - offset); f.block = oep->oe_start_blk; now_offset = offset - oep->oe_start_byte; ISODBG(fprintf(stderr, ">>>>iso: iso_read(%d) MULTI:%d offset/%d noff/%d cnt/%d blk/%d\n", fh->fh_fno, xtnt_num, offset, now_offset, now_count, f.block)); error = cd_read_file(cd, &f, now_offset, now_count, buf); if (error) return error; rem_count -= now_count; offset += now_count; buf += now_count; *amountRead += now_count; } oep = oep->oe_next; xtnt_num++; } } else { /* * Single extent logic. */ error = cd_read_file(cd, &f, offset, count, data); if (! error) *amountRead = count; } return error; } /* * int * iso_readdir(fhandle_t *fh, entry *entries, int count, int cookie, * bool_t *eof, int *nread) * * Description: * Read up to count byes worth of directory entries from the directory * described by fd * * Parameters: * fh file handle of directory whose entries we want * entries Buffer to receive entries * count Maximum # of bytes to read into entries * cookie offset into directory entry from which to start * eof set to 1 if we've read the last entrie * nread set to the number of entries read * * Returns: * 0 if successful, error code otherwise */ int iso_readdir(fhandle_t *fh, entry *entries, int count, int cookie, bool_t *eof, int *nread) { int blksize; int error, fileid, len, dirfd, newfd; int free_dirp = 0; char *uname; dir_t *dirp, *contents, *dp, *newdirp; entry *lastentry = 0, *entrybase = entries; /* REFERENCED */ multi_xtnt_t *mep; EXT ext; ISODBG(fprintf(stderr, ">>>>iso: iso_readdir(%d, %d) == \n", fh->fh_fno, cookie)); *nread = 0; blksize = cd_get_blksize(cd); dirfd = fh->fh_fno; /* * We use the privately "cached" directory entry because there's a * high probability that this same file handle was just the subject * of a "get_attr", which followed a lookup and we don't want to waste * time re-reading the directory entry if we don't need to. */ error = read_dir_entry_cached(dirfd, &dirp, &mep); if (error) return error; if (hasExt && useExt) { ext_parse_extensions(dirp, dirfd, &ext); error = ext_get_relo_dir(&ext, dirp, dirfd, &newdirp, &newfd); if (error) return error; if (dirp != newdirp) { dirp = newdirp; dirfd = newfd; free_dirp = 1; } } error = read_dir(dirp, dirfd, &contents, &len); if (error) { if (free_dirp) free(dirp); return error; } /* * We won't even try if there isn't at least a self and parent * reference. */ if (len < 2 * sizeof *dp) { if (free_dirp) free(dirp); free(contents); return EIO; } /* * We do this to clue the nfs_server module into the fact that there * are no entries left to be read. It turns out that the NFS client * won't listen to us when we set *eof == 0; the nfs_server module * has to actually return a NULL entry pointer in order to convince * the client that there are no more entries. */ if (cookie >= len - dirp->xattr_len) { if (free_dirp) free(dirp); free(contents); return 0; } /* * Take care of self and parent entries */ dp = first_dir(dirp, contents, len, cookie, blksize); if (dp == NULL) { if (free_dirp) free(dirp); return EIO; } if (cookie == 0) { (*nread)++; add_entry(entries, fh->fh_fno, dot, strlen (dot), ((char *)dp) - ((char *)contents) + dp->len_dir); lastentry = entries; entries = entries->nextentry; dp = next_dir(dirp, dp, contents, len, blksize); if (dp == NULL) { if (free_dirp) free(dirp); return EIO; } } if (cookie < 2 * sizeof (dir_t)) { (*nread)++; error = get_parent_fd(fh->fh_fno, &fileid); if (error) { if (free_dirp) free(dirp); free(contents); return error; } add_entry(entries, fileid, dotdot, strlen (dotdot), ((char *)dp) - ((char *)contents) + dp->len_dir); lastentry = entries; entries = entries->nextentry; dp = next_dir(dirp, dp, contents, len, blksize); } /* * Now do the rest */ while (dp && (char *)entries - (char *)entrybase + sizeof (entry) + NFS_MAXNAMLEN + 1 < count) { /* * Skip associated files * & all but the first multi-extent directory entry. */ if ( ! (dp->flags & (FFLAG_ASSOC) )) { fileid = MKFD(dirp, contents, dp, blksize); if (useExt && hasExt) { ext_parse_extensions(dp, fileid, &ext); if (ext.isRelocated) { /* * Banking on the fact here that we don't have multi-extent * files that are relocated. This seems a safe assumption * since relocation seems to be a property of directories * which we know can only be a single extent. * (ISO 9660 6.8.1) */ dp = next_dir(dirp, dp, contents, len, blksize); continue; } /* * If the directory is really mapped to another location, * return the fileid (viewed as the inode number on the outside * world) of the new location to the NFS layer. Otherwise, it * doesn't look like the new directory is a child of the parent. * getcwd() does indeed ensure that the current directory is * a child of the parent, which isn't the case unless you return * the new inode number. */ if (ext.child) { fileid = ext.child * blksize; } uname = ext.gotName ? ext.name : to_unixname(dp->file_id, dp->len_fi); } else { uname = to_unixname(dp->file_id, dp->len_fi); } (*nread)++; add_entry(entries, fileid, uname, strlen (uname), ((char *)dp) - ((char *)contents) + dp->len_dir); ISODBG(fprintf(stderr, ">>>>iso: iso_readdir add_entry MULTI=%d(%d): %s\n", (dp->flags & FFLAG_MULTI) ? 1 : 0, fileid, uname)); lastentry = entries; entries = entries->nextentry; /* * Is this the 1st of a series of directory entries * for a multi-extent file? */ if (dp->flags & FFLAG_MULTI) { /* * Skip all the entries with FFLAG_MULTI, and the next * one where it's not set (last entry of a series). */ do { dp = next_dir(dirp, dp, contents, len, blksize); /* * Update the "cookie" field in the last entry * to reflect that we've skipped an entry. */ if (dp) *(int *)lastentry->cookie = ((char *)dp) - ((char *)contents) + dp->len_dir; } while (dp && (dp->flags & FFLAG_MULTI)); if (dp == NULL) break; } } dp = next_dir(dirp, dp, contents, len, blksize); } *eof = ! dp || ! dp->len_dir || ((char *)dp - (char *)contents) + dp->len_dir >= len; if (lastentry) lastentry->nextentry = 0; ISODBG(fprintf(stderr, ">>>>iso: iso_readdir: eof = %d\n", *eof)); if (free_dirp) free(dirp); free(contents); return 0; } /* * int * iso_readraw(unsigned long block, void *buf, unsigned long count) * * Description: * Read raw data from the CD. This is here for debugging support * for the testcd test program. * * Parameters: * block block on the CD to read from * buf memory to read into * count number of bytes to read * * Returns: * 0 if successful, error code if error */ int iso_readraw(unsigned long offset, void *buf, unsigned long count) { /* * If the test program is trying to read the volume descriptor, * the block number will be 16, and we should use CDROM_BLKSIZE. * Otherwise, it's asking for data, and we should use the * block size we got from the volume descriptor. */ return cd_read(cd, offset == 16 ? 16 * CDROM_BLKSIZE : offset * cd_get_blksize(cd), buf, count, 0, "iso_readraw", __LINE__); } /* * void * iso_disable_name_translations(char) * * Description: * Modify the way we muck with the filenames on the CD. * 'c' == No translation, this results in very ugly file names in * all caps, sometimes ending with a semi-colon and then a * version number. * 'l' == Translate to lower case. * 'm' == Suppress the version #. * 'x' == (Default) Translate to lower case & suppress the version #. */ void iso_disable_name_translations(char xchar) { notranslate = xchar; } void iso_disable_extensions(void) { useExt = 0; } /* * void * iso_setx(void) * * Description: * Make it so we automatically set execute permission on every * file. This speeds up attribute checking and enables execution * of files that our algorithm may not give execute permission to. */ void iso_setx(void) { setx = 1; } /* * int * iso_getfs(char *path, fhandle_t *fh, struct svc_req *rq ) * * Description: * Get a file handle given its mount point. Also, if rq is * non-NULL, perform check to see if path should be exported to * the requestor represented by rq. * * Parameters: * path Mount point in which we're interested * fh receives file handle * rq NFS service request * * Returns: * 0 if successful, error code otherwise */ int iso_getfs(char *path, fhandle_t *fh, struct svc_req *rq) { FILE *fp; struct exportent *xent; int error; struct sockaddr_in *sin; struct hostent *hp; char *access, **aliases, *host; /* * Make sure they're asking about the file system that we've got * mounted. */ if (strcmp(path, mountPoint) != 0) { return ENOENT; } /* * Check to see if letting the requestor see this file system is * allowed (someone must run exportfs(1M). * * Note: this code is derived from code found in cmd/sun/rpc.mountd.c */ fp = setexportent(); if (!fp) { return EACCES; } error = EACCES; while (error && (xent = getexportent(fp))) { if (strcmp(path, xent->xent_dirname) == 0) { sin = svc_getcaller(rq->rq_xprt); hp = gethostbyaddr(&sin->sin_addr, sizeof sin->sin_addr, AF_INET); /* * Derived from cmd/sun/rpc.mountd.c */ access = getexportopt(xent, ACCESS_OPT); if (!access) { error = 0; } else { while ((host = strtok(access, ":")) != NULL) { access = NULL; if (strcasecmp(host, hp->h_name) == 0 || innetgr(host, hp->h_name, NULL, _yp_domain)) error = 0; else for (aliases = hp->h_aliases; *aliases && error; aliases++) if (strcasecmp(host, *aliases) == 0 || innetgr(access, *aliases, NULL, _yp_domain)) error = 0; } } if (error) { access = getexportopt(xent, ROOT_OPT); } if (access) { while ((host = strtok(access, ":")) != NULL) { access = NULL; if (strcasecmp(host, hp->h_name) == 0) error = 0; } } if (error) { access = getexportopt(xent, RW_OPT); } if (access) { while ((host = strtok(access, ":")) != NULL) { access = NULL; if (strcasecmp(host, hp->h_name) == 0) error = 0; } } } } endexportent(fp); if (!error) { bcopy(&rootfh, fh, sizeof *fh); } return error; } /* * int * iso_getblksize(void) * * Description: * Get the CD block size for the nfs_server module (which does * not have access to the cd pointer) * * Returns: * block size of our file system */ int iso_getblksize(void) { return cd_get_blksize(cd); } /* * int * iso_getattr(fhandlet_t *fh, fattr *fa) * * Description: * Fill in fa with the attributes of fh * * Parameters: * fh file handle whose attributes we want * fa Buffer to receive attributes * * Returns: * 0 if successful, error code otherwise */ int iso_getattr(fhandle_t *fh, fattr *fa) { int error, blksize; int gotTimes = 0, gotMode = 0, newfd, fileid; int free_dirp = 0; dir_t *dirp, *newDirp; hsxattr_t *hsxattr; /* REFERENCED */ multi_xtnt_t *mep; xattr_t xattr; EXT ext; ISODBG(fprintf(stderr, ">>>>iso: iso_getattr(%d)\n", fh->fh_fno)); fileid = fh->fh_fno; /* * We use the privately "cached" directory entry because there's a * high probability that this same file handle was just the subject * of a lookup and we don't want to waste time re-reading the directory * entry if we don't need to. */ error = read_dir_entry_cached(fileid, &dirp, &mep); if (error) return error; if (hasExt && useExt) { ext_parse_extensions(dirp, fileid, &ext); error = ext_get_relo_dir(&ext, dirp, fileid, &newDirp, &newfd); if (error) return error; if (newDirp != dirp) { dirp = newDirp; fileid = newfd; free_dirp = 1; ext_parse_extensions(dirp, fileid, &ext); } gotMode = ext.gotMode; if (ext.gotMode) { fa->mode = ext.mode; fa->nlink = ext.nlink; fa->uid = ext.uid; fa->gid = ext.gid; fa->type = ext.type; } fa->rdev = ext.rdev; gotTimes = ext.gotATime + ext.gotMTime + ext.gotCTime; /* * Take care of the situation in which only one or two of create, * modify, and access were available. Set the missing one(s) * according to the following order of preference: create, modify, * access. */ if (0 < gotTimes && gotTimes < 3) { if (ext.gotCTime) { if (!ext.gotMTime) { ext.mtime = ext.ctime; } if (!ext.gotATime) { ext.atime = ext.ctime; } } else if (!ext.gotMTime) { if (!ext.gotCTime) { ext.ctime = ext.mtime; } if (!ext.gotATime) { ext.atime = ext.mtime; } } else { if (!ext.gotCTime) { ext.ctime = ext.atime; } if (!ext.gotMTime) { ext.mtime = ext.atime; } } } if (gotTimes) { fa->atime = ext.atime; fa->mtime = ext.mtime; fa->ctime = ext.ctime; } } /* * These attributes are the same no matter what, but they can't be * set until after we've read the relocated directory. */ fa->blocksize = blksize = cd_get_blksize(cd); fa->fsid = fh->fh_dev; fa->fileid = fh->fh_fno; /* * If this is a Multi-Extent file, use the extent * list information collected by lookup. */ if (mep) fa->size = mep->me_length; else fa->size = CHARSTOLONG(dirp->data_len_msb); fa->blocks = (fa->size + blksize - 1) / blksize; /* * Now fall back on the extended attributes if we didn't find what * we wanted in the Rock Ridge extensions. If there are no * extended attributes, we'll do the best we can with the info in * the directory entry and some good guesses. * * If there's an extended attribute record for this file, * we'll use it. Otherwise, we'll use mode PERM_RXALL and * use the recording date for all dates. */ if (! gotMode) { fa->nlink = 1; fa->type = (FLAGS(dirp) & FFLAG_DIRECTORY) ? NFDIR : NFREG; if (dirp->xattr_len) { ISODBG(fprintf(stderr, ">>>>iso: iso_getattr: fh has xattr's\n")); error = cd_read(cd, CHARSTOLONG(dirp->ext_loc_msb) * blksize, &xattr, sizeof xattr, useCache, "iso_getattr", __LINE__); hsxattr = (hsxattr_t *)&xattr; if (error) { if (free_dirp) free(dirp); return error; } ISODBG(fprintf(stderr, ">>>>iso: iso_getattr: perm = 0x%x\n", xattr.perm)); fa->mode = ((fa->type == NFDIR) ? S_IFDIR : 0) | ((~xattr.perm & PERM_OREAD) ? S_IRUSR : 0) | ((~xattr.perm & PERM_OEXE) ? S_IXUSR : 0) | ((~xattr.perm & PERM_GREAD) ? S_IRGRP : 0) | ((~xattr.perm & PERM_GEXE) ? S_IXGRP : 0) | ((~xattr.perm & PERM_WREAD) ? S_IROTH : 0) | ((~xattr.perm & PERM_WEXE) ? S_IXOTH : 0); fa->uid = xattr.owner_id; fa->gid = xattr.group_id; } else { fa->uid = 0; fa->gid = 0; if (fa->type == NFDIR) fa->mode = S_IFDIR | PERM_RXALL; else if (setx) fa->mode = PERM_RXALL; else { char buf[CDROM_BLKSIZE]; struct filehdr *file; struct exec *e; Elf32_Ehdr *elf; ISODBG(fprintf(stderr, ">>>>iso: iso_getattr: non-xattr case ext_loc_msb/%d xattr_len/%d size/%d\n", CHARSTOLONG(dirp->ext_loc_msb), dirp->xattr_len, fa->size)); /* * Figure out what permissons to give the file. * * Always set read permission for all. * * Never set write permission. * * Set execute permissions on COFF files (we may be * exporting this to other systems), SUN executables, * ELF executables, or shell scripts. */ fa->mode = PERM_RALL; /* * Don't try to "sniff" zero-length files, * ext_loc_msb may take us out into the "weeds". */ if (fa->blocks) { error = cd_read(cd, CHARSTOLONG(dirp->ext_loc_msb) * blksize + dirp->xattr_len, buf, sizeof buf, useCache, "iso_getattr", __LINE__); if (error) { if (free_dirp) free(dirp); return error; } file = (struct filehdr *)buf; e = (struct exec *)buf; elf = (Elf32_Ehdr *)buf; if ((ISCOFF(file->f_magic) && file->f_flags & F_EXEC) || !N_BADMAG(*e) || IS_ELF(*elf) || (buf[0] == '#' && buf[1] == '!')) fa->mode |= PERM_XALL; } } } } if (gotTimes == 0) { if (dirp->xattr_len) { convert_time(&fa->atime, fsType == ISO ? &xattr.modify : (struct date_time *)&hsxattr->modify, fsType); convert_time(&fa->mtime, fsType == ISO ? &xattr.modify : (struct date_time *)&hsxattr->modify, fsType); convert_time(&fa->ctime, fsType == ISO ? &xattr.create : (struct date_time *)&hsxattr->create, fsType); } else { fa->atime.seconds = convert_to_seconds(dirp->recording.year +1900, dirp->recording.month, dirp->recording.day, dirp->recording.hour, dirp->recording.minute, dirp->recording.second, 0, fsType == ISO ? dirp->recording.greenwich : 0); fa->atime.useconds = 0; fa->mtime = fa->ctime = fa->atime; } } if (free_dirp) free(dirp); return 0; } /* * int * iso_readlink(fhandle_t *fh, char **link) * * Description: * Get the value of a symbolic link from the Rock Ridge * extensions. * * Parameters: * fh File handle to get link for * link Gets link value * * Returns: * 0 if successful, errno otherwise. */ int iso_readlink(fhandle_t *fh, char **link) { static EXT ext; int error; dir_t *dirp; /* REFERENCED */ multi_xtnt_t *mep; if (!useExt || !hasExt) return ENXIO; /* * We use the privately "cached" directory entry because there's a * high probability that this same file handle was just the subject * of a lookup and we don't want to waste time re-reading the directory * entry if we don't need to. */ error = read_dir_entry_cached(fh->fh_fno, &dirp, &mep); if (error) return error; ext_parse_extensions(dirp, fh->fh_fno, &ext); if (!ext.gotPath) return ENXIO; *link = ext.path; return 0; } void dump_block(char *cp, int sz) { int i; for (i = 0; i < sz; i++) { if (! (i % 16)) fprintf(stderr, "\n>>>>iso: "); if (isprint(cp[i])) fprintf(stderr, " %c", cp[i]); else if (iscntrl(cp[i])) fprintf(stderr, " \\%c", cp[i] | ' '); else fprintf(stderr, " %3o", cp[i]); } fprintf(stderr, "\n"); } /* * static int * set_blocks() * * Description: * Set the number of blocks in the generic file system. This gets * called at startup, and also every time a change in media is detected. * * Returns: * 0 if successful, error code otherwise */ static int set_blocks() { int error; struct p_vol_desc vol; hsvol_t *hsvol; int blksize; error = cd_read(cd, cd_voldesc(cd), &vol, sizeof vol, 0, "set_blocks", __LINE__); if (error) return error; hsvol = (hsvol_t *)&vol; if (strncmp(vol.id, "CD001", 5) == 0) fsType = ISO; else if (strncmp((const char *)hsvol->ident, "CDROM", 5) == 0) fsType = HSFS; else { ISODBG(fprintf(stderr, ">>>>iso: Disc is Unknown format\n")); ISODBG(dump_block((char *)&vol, sizeof vol)); return EIO; } ISODBG(fprintf(stderr, ">>>>iso: Disc is in %s format\n", (fsType == ISO) ? "ISO 9660" : "High Sierra")); fsBlocks = (fsType == ISO) ? vol.vol_space_size_msb : CHARSTOLONG(hsvol->vol_space_size_msb); rootfh.fh_fid.lfid_len = sizeof rootfh.fh_fid - sizeof rootfh.fh_fid.lfid_len; blksize = (fsType == ISO) ? vol.blksize_msb : CHARSTOSHORT(hsvol->blksize_msb); rootfh.fh_fno = (fsType == ISO ? (CHARSTOLONG(vol.root.ext_loc_msb) + vol.root.xattr_len) : (CHARSTOLONG(hsvol->root.ext_loc_msb) + hsvol->root.xattr_len)) * blksize; error = cd_set_blksize(cd, blksize); if (error) { return error; } if (fsType == ISO) { check_for_extensions(); } return 0; } /* * static int * get_parent_fd(int dir, int *fdp) * * Description: * Get the file descriptor of this directory's parent * * Parameters: * dir File descriptor of directory we want parent of * fdp receives file descriptor of parent * * Returns: * 0 if successful, error code otherwise */ static int get_parent_fd(int dir, int *fdp) { dir_t *this_dir, *this_contents, *parent; int error, blksize, len; int parent_block; blksize = cd_get_blksize(cd); error = read_dir_entry(dir, &this_dir); if (error) return error; error = read_dir(this_dir, dir, &this_contents, &len); if (error) { free(this_dir); return error; } parent = (dir_t *)((char *)this_contents + this_contents->len_dir); parent_block = CHARSTOLONG(parent->ext_loc_msb) + parent->xattr_len; if (hasExt && useExt) { EXT ext_info; int parent_fd; parent_fd = MKFD(this_dir, this_contents, parent, blksize); ext_parse_extensions(parent, parent_fd, &ext_info); if (ext_info.parent) { parent_block = ext_info.parent; } } *fdp = parent_block * blksize; free(this_dir); free(this_contents); return 0; } /* * static void * add_entry(entry *entries, int fd, char *name, int name_len, int cookie) * * Description: * Helper function for iso_readdir(). Puts fd and name into an entry, * and sets entries->nextentry to point to the place for the next * entry. * * Parameters: * entries buffer for entries * fd file descriptor to add to entries * name name of file to add to entries * name_len lengthe of name * cookie cookie for next entry */ static void add_entry(entry *entries, int fd, char *name, int name_len, int cookie) { char *ptr; entries->fileid = fd; /* * We copy the name to the memory immediately following this entry. * We'll set entries->nextentry to the first byte afterwards that we * can use, taking alignment into consideration. */ entries->name = (char *)(entries + 1); strncpy(entries->name, name, name_len); entries->name[name_len] = '\0'; *(int *)entries->cookie = cookie; entries->nextentry = (entry *)((char *)(entries + 1) + name_len + 1); ptr = (char *)entries->nextentry; /* * Fix alignment problems */ if ((int)ptr % 4) { ptr = ptr + 4 - ((int)ptr % 4); entries->nextentry = (entry *)ptr; } } /* * static int * read_dir(dir_t *dirp, int dirfd, dir_t **datap, int *len) * * Description: * Read a directory into memory * * Parameters: * dirp pointer to directory structure of the directory we want to read * dirfd fd of dirp * datap Make this point to the directory's contents * len Make this point to the length of the directory * * Returns: * 0 if successful, error code otherwise */ static int read_dir(dir_t *dirp, int dirfd, dir_t **datap, int *len) { dir_t *dp_ret, *newdirp; int loc, error, blksize, toRead, newfd; EXT ext; void *toFree = 0; blksize = cd_get_blksize(cd); if (hasExt && useExt) { ext_parse_extensions(dirp, dirfd, &ext); /* * If dirp points to a relocated directory or the parent of a * relocated directory, then use the child/parent pointer in * the extensions rather than the ISO pointer. What we do is * then replace dirp with the appropriate self-referential * directory entry (the self-referential dir entry is always * the first on in the directory, and we're assuming that it's * never relocated). */ if ((error = ext_get_relo_dir(&ext, dirp, dirfd, &newdirp, &newfd)) != 0) { return error; } if (newdirp != dirp) { /* * Set this up to be freed later so we don't leak. */ toFree = dirp = newdirp; } } if (!(FLAGS(dirp) & FFLAG_DIRECTORY)) return ENOTDIR; loc = (CHARSTOLONG(dirp->ext_loc_msb) + dirp->xattr_len) * blksize; *len = CHARSTOLONG(dirp->data_len_msb); if (toFree) { free(toFree); } dp_ret = safe_malloc(*len); error = cd_read(cd, loc, dp_ret, *len, useCache, "read_dir", __LINE__); if (error) { free(dp_ret); return error; } *datap = dp_ret; return 0; } /* * static int * read_dir_entry_cached(int fd, dir_t **dpp, multi_xtnt_t **mpp) * * Description: * Read the directory entry for the file descriptor fd into memory * if we don't already have it there. Keep it there until a request * for a different arrives. If it's already there, skip the read. * * Parameters: * fd file descriptor of directory to read entry of * dpp make this point to the directory entry * mep make this point to the multi extent entry, if it exists * * Returns: * 0 if successful, error code otherwise */ static int read_dir_entry_cached(int fd, dir_t **dpp, multi_xtnt_t **mpp) { int error; if (!useCache || fd != dirc_last_fd || !dirc_dirp) { if (dirc_dirp) { free(dirc_dirp); dirc_mep = NULL; dirc_dirp = NULL; dirc_last_fd = -1; } error = read_dir_entry(fd, &dirc_dirp); if (error) return error; dirc_last_fd = fd; /* * If this is a Multi-Extent file, find the extent * list information collected by lookup. */ if (dirc_dirp->flags & FFLAG_MULTI) { dirc_mep = iso_me_search(fd); ISODBG(fprintf(stderr, ">>>>iso: read_dir_entry_cached: me_search for %d = 0x%x\n", fd, dirc_mep)); } dirc_miss++; } else { dirc_hit++; } *dpp = dirc_dirp; *mpp = dirc_mep; return 0; } /* * static int * read_dir_entry(int fd, dir_t **dpp) * * Description: * Read the directory entry for the file descriptor fd into memory * * Parameters: * fd file descriptor of directory to read entry of * dpp make this point to the directory entry * * Returns: * 0 if successful, error code otherwise */ static int read_dir_entry(int fd, dir_t **dpp) { dir_t *dirp; int error, changed, blksize; /* * Here we check to see if the user has ejected the disc and inserted * another. This is not fatal for us for the reason that the root * file handle of any ISO 9660 file system is the same the way * we've defined it. The user is thus free to switch CD's at * will; the only complications will be to processes that have * the CD as their current directoriess. */ error = cd_media_changed(cd, &changed); if (error) return error; if (changed) { cd_flush_cache(cd); iso_me_flush(); error = set_blocks(); if (error) return error; } /* * Read one block's worth of data. We don't know how big a * directory entry will be without reading it first, and since we * might be using the system use area we can't even guess, except * that we do know that directory entries can't span blocks. So * we just read a block. * * Should just read the block that dir entry is in, but then dirp * won't point to the beginning of malloced memory so caller can't * free it. */ blksize = cd_get_blksize(cd); dirp = safe_malloc(blksize); error = cd_read(cd, fd, dirp, blksize, useCache, "read_dir_entry", __LINE__); if (error) { free (dirp); return error; } /* * Make sure that the data read in makes sense when interpreted * as a directory entry */ if (dirp->len_dir < sizeof (dir_t) || dirp->len_fi > dirp->len_dir - sizeof (dir_t) + 1) { free (dirp); return ENOTDIR; } /* * I've seen discs that don't have the directory bit set in * the root directory. So we'll set it if it isn't. */ if ((fd == ROOT_DIR() || fd == HS_ROOT_DIR()) && !(dirp->flags & FFLAG_DIRECTORY)) { dirp->flags |= FFLAG_DIRECTORY; #ifdef WEIRD_DMA_DISC /* * The Defense Mapping Agency put out a number of bogus * discs, with 0 in the length field of the root directory. * Apparently, they ship special drivers so these discs can * be read on PC's. It seems that enough of our customers * have these discs that including this stuff is warranted; * as of 02/13/92 the Makefile defined WEIRD_DMA_DISC so * that this code would go in. */ if (CHARSTOLONG(dirp->data_len_msb) == 0) { dirp->data_len_msb[3] = (char)CDROM_BLKSIZE & 0xff; dirp->data_len_msb[2] = (char)(CDROM_BLKSIZE >> 8) & 0xff; dirp->data_len_msb[1] = (char)(CDROM_BLKSIZE >> 16) & 0xff; dirp->data_len_msb[0] = (char)(CDROM_BLKSIZE >> 24) & 0xff; } #endif } *dpp = dirp; return 0; } /* * static void * convert_time(struct nfstime *to, struct date_time *from) * * Description: * Convert from a date_time struct to an nfs time struct. This involves * converting from (year, month, day, hour, minute, second) to * (seconds since 1/1/1970) * * Parameters: * to the nfs version * from the ISO 9660 version */ static void convert_time(struct nfstime *to, struct date_time *from, enum cdtype type) { #define TWODECDIGITSTOINT(dd) ((dd[1] - '0') + (dd[0] - '0') * 10) int year, month, day, min, hour, sec, csec; csec = TWODECDIGITSTOINT(from->centiseconds); to->useconds = csec * 10; year = (from->year[3] - '0') + 10 * (from->year[2] - '0') + 100 * (from->year[1] - '0') + 1000 * (from->year[0] - '0'); month = TWODECDIGITSTOINT(from->month); day = TWODECDIGITSTOINT(from->day); hour = TWODECDIGITSTOINT(from->hour); min = TWODECDIGITSTOINT(from->minute); sec = TWODECDIGITSTOINT(from->second); to->seconds = convert_to_seconds(year, month, day, hour, min, sec, csec, type == ISO ? from->greenwich : 0); #undef TWODECDIGITSTOINT } /* * static int * convert_to_seconds(int year, int month, int day, int hour, * int min, int sec, int csec, int gw) * * Description: * Turn (year, month, day, hour, minute, second) into the number of * seconds since 1/1/1970 * * Parameters: * year * month * day * hour * min * sec * csec hundredths of a second (ignored) * gw offset from greenwich time in # of 15 minute intervals * * Returns: * The number of seconds since 1970 of (year, month...) */ static int convert_to_seconds(int year, int month, int day, int hour, int min, int sec, int csec, int gw) { if (year < 1970) year = 1970; if (1 > month || 12 < month) month = 1; if (!valid_day(day, month, year)) day = 1; if (hour < 0 || hour > 23) hour = 0; if (min < 0 || min > 59) min = 0; if (sec < 0 || sec > 59) sec = 0; return (((((((year - 1970) * 365 + ((year - 1969) / 4) + days_so_far(day, month, year)) * 24) + hour) * 60) + min - gw * 15) * 60 + sec); } /* * static int * valid_day(int day, int month, int year) * * Description: * Determine if there is, was, or will be a date month/day/year * * Parameters: * day * month * year * * Returns: * 1 if this is a valid date, 0 if not */ static int valid_day(int day, int month, int year) { static int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; /* * Special case February in a leap year */ if (IS_LEAPYEAR(year) == 0 && month == 2) if (day >= 1 && day <= 29) return 1; if (day < 1 || day > days[month - 1]) return 0; return 1; } /* * static int * days_so_far(int day, int month, int year) * * Description: * Figure out how may days have gone by in the year as of month/day * * Parameters: * day * month * year * * Returns: * The number of days so far */ static int days_so_far(int day, int month, int year) { static int days[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; return day - 1 + days[month - 1] + (IS_LEAPYEAR(year) && month > 2 ? 1 : 0); } /* * static char * * to_unixname(char *name, int length) * * Description: * Convert an ISO 9660 name to a UNIX name. We convert to all lower case * letters, and blow away any fluff of the form ";#". * * Parameters: * name * length * * Returns: * The unix name. Note: this is static, will be overwritten next * this function is called */ static char * to_unixname(char *name, int length) { #define min(a,b) ((a) < (b) ? (a) : (b)) #define tolower(c) (isupper(c) ? _tolower(c) : (c)) static char uname[NFS_MAXNAMLEN]; int i; char *dotp; length = min(sizeof uname - 1, length); if (*name == '\0') return dot; if (*name == '\1') return dotdot; /* * Check for "no translation at all" */ if (notranslate == 'c') { bcopy(name, uname, length); uname[length] = '\0'; return uname; } /* * 1st check for the "fast" default mode name translation. * This behaves as if both 'l' & 'm' modes were in effect. */ if (notranslate == 'x') { for (i = 0; i < length; i++) { if (name[i] == ';') break; uname[i] = tolower(name[i]); } } else { for (i = 0; i < length; i++) { /* * Check to see if we're to "lop off" the version #. */ if (notranslate == 'm') { if (name[i] == ';') break; else uname[i] = name[i]; } /* * Check to see if we're to change to lower case. */ if (notranslate == 'l') uname[i] = tolower(name[i]); } } uname[i] = '\0'; /* * Custom dictates that if the translated 8.3 * name ends in just a ".", we can trim it off. */ dotp = strrchr(uname, '.'); if (dotp && *(dotp + 1) == '\0') *dotp = '\0'; return uname; #undef min #undef tolower } /* * Rock Ridge extensions */ #define EXT_VERSION 1 #define MKSYSUSE(byte1,byte2) ((byte1) | ((byte2) << 8)) #define EXT_CONT MKSYSUSE('C', 'E') #define EXT_PAD MKSYSUSE('P', 'D') #define EXT_SYSUSE MKSYSUSE('S', 'P') #define EXT_TERM MKSYSUSE('S', 'T') #define EXT_EXT MKSYSUSE('E', 'R') #define EXT_MODE MKSYSUSE('P', 'X') #define EXT_DEVNODE MKSYSUSE('P', 'N') #define EXT_SYMLINK MKSYSUSE('S', 'L') #define EXT_NAME MKSYSUSE('N', 'M') #define EXT_CHILD MKSYSUSE('C', 'L') #define EXT_PARENT MKSYSUSE('P', 'L') #define EXT_RELO MKSYSUSE('R', 'E') #define EXT_TIME MKSYSUSE('T', 'F') #define EXT_FLAGS MKSYSUSE('R', 'R') /* * Flags for NM and SL fields */ #define NM_CONT 1 #define NM_CUR 2 #define NM_PAR 4 #define NM_ROOT 8 #define NM_VOLROOT 0x10 #define NM_HOST 0x20 /* * Flags for TM field */ #define TM_CREATE 1 #define TM_MODIFY 2 #define TM_ACCESS 4 #define TM_ATTR 8 #define TM_BACKUP 0x10 #define TM_EXPIRE 0x20 #define TM_EFFECTIVE 0x40 #define TM_LONG 0x80 /* * Flags for the RR field */ #define FL_PX 1 #define FL_PN 2 #define FL_SL 4 #define FL_NM 8 #define FL_CL 0x10 #define FL_PL 0x20 #define FL_RE 0x40 #define FL_TF 0x80 /* * This structure is used to traverse the system used fields. cur * points to the current field, and base is the start of either the * system use area of dirp or to contBuf, depending on whether we're * going through the directory record or a continuation area. */ typedef struct tagSysUse { dir_t *dirp; /* directory */ char *cur; /* current offset */ char *base; /* base */ int length; /* current length */ char *contBuf; /* continutation buffer */ int cont; /* continuation block */ int contLen; /* continuation length */ int contOff; /* continuation offset */ } SYSUSE; /* * static void * ext_process_fields(SYSUSE *sys) * * Description: * Do generic Rock Ridge extension field processing. This takes * care of continuation, padding, and termination fields all in * one place. sys->cur is set to NULL when there are no more * fields to be processed. * * Parameters: * sys struct to maintain state while traversing extensions. */ static void ext_process_fields(SYSUSE *sys) { short field; int error; while (1) { /* * We check if we're at the end of this system use area. * If there was a CE, we read it in and keep going from there; * otherwise we set sys->cur = NULL indicating that we're done. */ if (sys->cur - sys->base >= sys->length || sys->cur[2] == 0) { if (sys->cont) { if (sys->contBuf) { free(sys->contBuf); } sys->contBuf = safe_malloc(sys->contLen); ISODBG(fprintf(stderr, ">>>>iso: Reading in a continuation!!\n")); error = cd_read(cd, sys->cont * cd_get_blksize(cd) + sys->contOff, sys->contBuf, sys->contLen, useCache, "ext_process_fields", __LINE__); if (error) { ISODBG(fprintf(stderr, ">>>>iso: Cont read error = %d\n", error)); sys->cur = NULL; return; } sys->base = sys->cur = sys->contBuf; sys->length = sys->contLen; sys->cont = 0; } else { sys->cur = NULL; return; } } /* * Ignore fields that have the wrong version */ if (sys->cur[3] != EXT_VERSION) { sys->cur += sys->cur[2]; continue; } field = MKSYSUSE(sys->cur[0], sys->cur[1]); switch (field) { case EXT_CONT: ISODBG(fprintf(stderr, ">>>>iso: Got a continuation!!\n")); /* * When we encounter a continuation field, we stash away * its values in our SYSUSE structure. The CE gives us * info for finding a continuation of the system use area. * There's only supposed to be one CE per system use area, * so it's safe to stash it for later and keep going (it * would be pretty braindamaged, but there could be more * fields after the CE field). */ sys->cont = CHARSTOLONG(sys->cur + 8); sys->contLen = CHARSTOLONG(sys->cur + 24); sys->contOff = CHARSTOLONG(sys->cur + 16); /* intentional fall-through */ case EXT_PAD: /* * After processing CE, or if we find PD, skip over it. */ ISODBG(fprintf(stderr, ">>>>iso: Skipping %c%c\n", sys->cur[0], sys->cur[1])); sys->cur += sys->cur[2]; break; case EXT_TERM: ISODBG(fprintf(stderr, ">>>>iso: Got an ST\n")); /* * ST tells us that this is the system use area is over. */ sys->cur = NULL; return; default: /* * Anything else, pass it back to the caller for deciphering. */ return; } } } /* * static void * ext_free_sys(SYSUSE *sys) * * Description: * Function to be called when we're all done traversing Rock * Ridge extension fields. This frees any memory we malloced for * reading in continuation blocks. * * Parameters: * sys struct for maintaining state while traversing extensions. */ static void ext_free_sys(SYSUSE *sys) { if (sys->contBuf) { free(sys->contBuf); sys->contBuf = NULL; } } /* * static char * * ext_first_field(dir_t *dirp, int dirfd, SYSUSE *sys) * * Description: * Get the first field in the Rock Ridge extensions of a * directory entry. The reason that we need dirfd here is to * differentiate the '.' entry of the root directory from all * others. If this is the '.' entry of the root directory, then * we don't skip skipSysBytes, as per the spec. For all other * directory entries, we do skip skipSysBytes. * * Parameters: * dirp directory entry to get extensions from * dirfd Descriptor for this directory * sys A structure that helps us traverse extensions * * Returns: * a pointer to the first Rock Ridge extension field, NULL if * there are none. */ static char * ext_first_field(dir_t *dirp, int dirfd, SYSUSE *sys) { char *sysUse; bzero(sys, sizeof *sys); sys->dirp = dirp; sys->length = dirp->len_dir - sizeof(dir_t) - dirp->len_fi + 1; if ((dirp->len_fi & 1) == 0) { sys->length--; } if (sys->length < 2) { return NULL; } /* * skipSysBytes gets set in check_for_extensions, below. * check_for_extensinons calls us before it ever sets this, which * is OK because the spec says that the '.' entry of the * root directory (which is what check_for_extensions looks at) * does not use the skip bytes. */ sys->cur = sys->base = (char *)dirp + dirp->len_dir - sys->length + (dirfd != rootSelfEntry ? skipSysBytes : 0); ext_process_fields(sys); return sys->cur; } /* * static char * * ext_next_field(SYSUSE *sys) * * Description: * Get the next field from the Rock Ridge extensions * * Parameters: * sys parameter that keeps state around about rock ridge * extensions * * Returns: * Pointer to the next field in the extensions, NULL if there are * none left. */ static char * ext_next_field(SYSUSE *sys) { sys->cur += sys->cur[2]; ext_process_fields(sys); return sys->cur; } /* * Globals to hold our hostname. These variables are shared between * the functions ext_name and iso_readlink, so that we don't have to * call gethostname more than once. */ static char hostName[100]; static int gotHost = 0; /* * char * * ext_get_time(char *tm, struct nfstime *to, int longForm) * * Description: * Convert Rock Ridge time to NFS time. Rock Ridge can be in * either xattr form (longForm == 1) or directory record form * (longForm == 0). * * Parameters: * tm pointer into a TF field * to NFS time * longForm whether to use xattr time (longForm == 1) or dir_t * time (longForm == 0) * * Returns: * tm advanced past this time record. */ char * ext_get_time(char *tm, struct nfstime *to, int longForm) { struct rec_date_time *shortTime; if (longForm) { convert_time(to, (struct date_time *)tm, ISO); tm += sizeof(struct date_time); } else { shortTime = (struct rec_date_time *)tm; to->useconds = 0; to->seconds = convert_to_seconds(shortTime->year + 1900, shortTime->month, shortTime->day, shortTime->hour, shortTime->minute, shortTime->second, 0, shortTime->greenwich); tm += sizeof(struct rec_date_time); } return tm; } /* * static void * check_for_extensions(void) * * Description: * Check to see if this is a Rock Ridge CD. If it is, we need to * set the global variable skipSysBytes to the number of bytes to * skip in each system use area. */ static void check_for_extensions(void) { int hasExtensions = 0, isRockRidge = 0, error, len, dirlen; static char *rripid = "RRIP_1991A"; char *sysUse; SYSUSE sys; dir_t *dirp, *contents; int entry_count; if (0 != read_dir_entry(ROOT_DIR(), &dirp)) { return; } rootSelfEntry = MKFD(dirp, 0, 0, cd_get_blksize(cd)); error = read_dir(dirp, rootSelfEntry, &contents, &dirlen); free(dirp); if (error) { return; } /* * Look at the system use area of the first entry of the root * directory. Make sure that the first entry is the EXT_SYSUSE * entry iff there are to be extensions. */ for (sysUse = ext_first_field(contents, rootSelfEntry, &sys), entry_count = 1; sysUse; sysUse = ext_next_field(&sys), entry_count++) { if (entry_count == 1 && MKSYSUSE(sysUse[0], sysUse[1]) == EXT_SYSUSE && sysUse[2] == 7 && sysUse[4] == 0xbe && sysUse[5] == 0xef) { hasExtensions++; skipSysBytes = sysUse[6]; } else if (hasExtensions && MKSYSUSE(sysUse[0], sysUse[1]) == EXT_EXT) { len = strlen(rripid); if (sysUse[4] == len && strncmp(sysUse + 8, rripid, len) == 0) { isRockRidge++; } else { free(contents); return; } } } hasExt = isRockRidge && hasExtensions; free(contents); ext_free_sys(&sys); } /* * static void ext_parse_symlink(char *sysUse, EXT *ext) * * Description: * Parse a rock ridge field pertaining to a symbolic link. Note * that the link name can span several fields; we concatenate * this round of names onto what was already there, so when * starting fresh ext->path[0] has to be '\0'. * * Parameters: * sysUse Pointer to the system use field with symlink info * ext extension struct to get info */ static void ext_parse_symlink(char *sysUse, EXT *ext) { dir_t *dirp; int rv, error, len, compLen; unsigned char flags, fieldFlags; static char path[NFS_MAXPATHLEN]; char *comp, *component; SYSUSE sys; for (comp = sysUse + 5; comp - sysUse < sysUse[2]; comp += comp[1] + 2) { flags = comp[0]; if (flags & NM_CUR) { component = dot; compLen = strlen(dot); } else if (flags & NM_PAR) { component = dotdot; compLen = strlen(dotdot); } else if (flags & NM_ROOT) { component = "/"; compLen = strlen(component); } else if (flags & NM_VOLROOT) { component = mountPoint; compLen = strlen(component); } else if (flags & NM_HOST) { if (!gotHost) { if (gethostname(hostName, sizeof hostName - 1) < 0) { continue; } gotHost = 1; } component = hostName; compLen = strlen(component); } else { component = comp + 2; compLen = comp[1]; } len = strlen(ext->path) + compLen + (ext->doSlash ? 1 : 0); if (len < sizeof ext->path) { if (ext->doSlash) { strcat(ext->path, "/"); } strncat(ext->path, component, compLen); ext->path[len] = '\0'; rv = 0; } /* * Put a slash after this component if there's another * one that's not a continuation of this one and if * this one is not already a slash. */ ext->doSlash = !(flags & NM_CONT) && !(flags & NM_ROOT); } } /* * static void ext_parse_name(char *sysUse, EXT *ext) * * Description: * Parse a NM rock ridge extension field. Note that like * symlinks, the name can span multiple fields, so we concatenate * what we find in the field with what was already there. To get * started, ext->name[0] must be set to '\0'. * * Parameters: * sysUse pointer to name system use field * ext extension struct that gets name info */ static void ext_parse_name(char *sysUse, EXT *ext) { char *component; unsigned char flags; int compLen; int len; flags = (unsigned char)sysUse[4]; if (flags & NM_CUR) { component = dot; compLen = strlen(dot); } else if (flags & NM_PAR) { component = dotdot; compLen = strlen(dotdot); } else if (flags & NM_ROOT) { component = "/"; compLen = strlen(component); } else if (flags & NM_VOLROOT) { component = mountPoint; compLen = strlen(component); } else if (flags & NM_HOST) { if (!gotHost) { if (gethostname(hostName, sizeof hostName - 1) < 0) { return; } gotHost = 1; } component = hostName; compLen = strlen(component); } else { component = sysUse + 5; compLen = sysUse[2] - 5; } len = strlen(ext->name) + compLen; if (len < sizeof ext->name - 1) { strncat(ext->name, component, compLen); ext->name[len] = '\0'; } } /* * static void ext_parse_mode(char *sysUse, EXT *ext) * * Description: * Parse the rock ridge PX field * * Parameters: * sysUse pointer to PX field * ext extensions struct to get mode info */ static void ext_parse_mode(char *sysUse, EXT *ext) { ext->mode = CHARSTOLONG(sysUse + 8); ext->nlink = CHARSTOLONG(sysUse + 16); ext->uid = CHARSTOLONG(sysUse + 24); ext->gid = CHARSTOLONG(sysUse + 32); if (S_ISDIR(ext->mode)) { ext->type = NFDIR; } else if (S_ISCHR(ext->mode)) { ext->type = NFCHR; } else if (S_ISBLK(ext->mode)) { ext->type = NFBLK; } else if (S_ISREG(ext->mode)) { ext->type = NFREG; } else if (S_ISFIFO(ext->mode)) { ext->type = NFFIFO; } else if (S_ISLNK(ext->mode)) { ext->type = NFLNK; } else if (S_ISSOCK(ext->mode)) { ext->type = NFSOCK; } else { ext->type = NFBAD; } } /* * static void ext_parse_devnode(char *sysUse, EXT *ext) * * Description: * Get device node from rock ridge extension field * * Parameters: * sysUse pointer to PN field * ext gets results */ static void ext_parse_devnode(char *sysUse, EXT *ext) { ext->rdev = makedev(CHARSTOLONG(sysUse + 8), CHARSTOLONG(sysUse + 16)); } /* * static void ext_parse_time(char *sysUse, EXT *ext) * * Description: * Parse the rock ridge TF field. Note that multiple TF fields * can be present in one system use area, so we have to be really * careful about keeping track of our state in here. All the * time related flags should be zeroed before processing a new * system use area. See comment below. * * Parameters: * sysUse pointer to TF field * ext gets results */ static void ext_parse_time(char *sysUse, EXT *ext) { char *tm; unsigned char flags; int longForm; /* * Parse the time field, setting flags as we go to * indicate what we've found. The spec allows for any * combinations of times to be specified in any number * of fields, so we must be careful to cover some very * weird cases, like modify and access are specified * in two different fields and create is not. */ flags = sysUse[4]; longForm = !!(flags & TM_LONG); tm = sysUse + 5; if ((flags & TM_CREATE) && !ext->gotCTime) { tm = ext_get_time(tm, &ext->ctime, longForm); ext->gotCTime = 1; } if (flags & TM_MODIFY && !ext->gotMTime) { tm = ext_get_time(tm, &ext->mtime, longForm); ext->gotMTime = 1; } if (flags & TM_ACCESS && !ext->gotATime) { ext_get_time(tm, &ext->atime, longForm); ext->gotATime = 1; } } /* * static void ext_parse_extensions(dir_t *dirp, int fd, EXT *ext) * * Description: * Parse all of the rock ridge extensions in a system use area * into an EXT structure. We bzero the thing first, because we * have to clear all of the flags to clear the state. Not all * extensions will always be present, so we've got to keep track * of what we see. * * Parameters: * dirp directory entry we're parsing sys use field of * fd file descriptor of fd (to check whether it's the * rootSelfEntry in ext_first_field * ext Gets results of parsing. */ static void ext_parse_extensions(dir_t *dirp, int fd, EXT *ext) { char *sysUse; SYSUSE sys; bzero(ext, sizeof *ext); for (sysUse = ext_first_field(dirp, fd, &sys); sysUse; sysUse = ext_next_field(&sys)) { switch (MKSYSUSE(sysUse[0], sysUse[1])) { case EXT_SYMLINK: ext_parse_symlink(sysUse, ext); ext->gotPath = 1; break; case EXT_NAME: ext_parse_name(sysUse, ext); ext->gotName = 1; break; case EXT_MODE: ext_parse_mode(sysUse, ext); ext->gotMode = 1; break; case EXT_DEVNODE: ext_parse_devnode(sysUse, ext); break; case EXT_TIME: ext_parse_time(sysUse, ext); break; case EXT_CHILD: ext->child = CHARSTOLONG(sysUse + 8); break; case EXT_PARENT: ext->parent = CHARSTOLONG(sysUse + 8); break; case EXT_RELO: ext->isRelocated = 1; break; } } ext_free_sys(&sys); } /* * static int ext_get_relo_dir(EXT *ext, dir_t *inDir, int infd, * dir_t **outDir, int *outfd) * * Description: * If inDir refers to a relocated directory, this fills in * *outDir with the self-referential directory entry for the * relocated directory, which can fill the same role in directory * entry processing. This means that we look at the rock ridge * extensions, and if there's a CL or a PL field we use that. * * In the normal case, where ext->parent == ext->child == 0, we * just set *outDir to indir and *outfd to infd so the caller can * detect that nothing weird happened. * * Parameters: * ext rock ridge extensions pointer * inDir The directory under consideration * infd fileid for inDir * outDir Gets replacement directory entry * outfd fileid for outfd * * Returns: * 0 if successful, error code if failure */ static int ext_get_relo_dir(EXT *ext, dir_t *inDir, int infd, dir_t **outDir, int *outfd) { int blksize = cd_get_blksize(cd); if (!ext->child && !ext->parent) { *outDir = inDir; *outfd = infd; return 0; } *outfd = (ext->child ? ext->child : ext->parent) * blksize; ISODBG(fprintf(stderr, ">>>>iso: ext_get_relo_dir: %d -> %d\n", infd, *outfd)); return read_dir_entry(*outfd, outDir); }