|  | /* | 
|  | * To understand this code, see Rock Ridge Interchange Protocol | 
|  | * standard 1.12 and System Use Sharing Protocol version 1.12 | 
|  | * (search for rrip112.ps and susp112.ps on the web). | 
|  | * | 
|  | * Even better, go read something else. | 
|  | */ | 
|  |  | 
|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <bio.h> | 
|  | #include <libsec.h> | 
|  | #include "iso9660.h" | 
|  |  | 
|  | static long mode(Direc*, int); | 
|  | static long nlink(Direc*); | 
|  | static ulong suspdirflags(Direc*, int); | 
|  | static ulong CputsuspCE(Cdimg *cd, ulong offset); | 
|  | static int CputsuspER(Cdimg*, int); | 
|  | static int CputsuspRR(Cdimg*, int, int); | 
|  | static int CputsuspSP(Cdimg*, int); | 
|  | /*static int CputsuspST(Cdimg*, int); */ | 
|  | static int Cputrripname(Cdimg*, char*, int, char*, int); | 
|  | static int CputrripSL(Cdimg*, int, int, char*, int); | 
|  | static int CputrripPX(Cdimg*, Direc*, int, int); | 
|  | static int CputrripTF(Cdimg*, Direc*, int, int); | 
|  |  | 
|  | /* | 
|  | * Patch the length field in a CE record. | 
|  | */ | 
|  | static void | 
|  | setcelen(Cdimg *cd, ulong woffset, ulong len) | 
|  | { | 
|  | ulong o; | 
|  |  | 
|  | o = Cwoffset(cd); | 
|  | Cwseek(cd, woffset); | 
|  | Cputn(cd, len, 4); | 
|  | Cwseek(cd, o); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Rock Ridge data is put into little blockettes, which can be | 
|  | * at most 256 bytes including a one-byte length.  Some number | 
|  | * of blockettes get packed together into a normal 2048-byte block. | 
|  | * Blockettes cannot cross block boundaries. | 
|  | * | 
|  | * A Cbuf is a blockette buffer.  Len contains | 
|  | * the length of the buffer written so far, and we can | 
|  | * write up to 254-28. | 
|  | * | 
|  | * We only have one active Cbuf at a time; cdimg.rrcontin is the byte | 
|  | * offset of the beginning of that Cbuf. | 
|  | * | 
|  | * The blockette can be at most 255 bytes.  The last 28 | 
|  | * will be (in the worst case) a CE record pointing at | 
|  | * a new blockette.  If we do write 255 bytes though, | 
|  | * we'll try to pad it out to be even, and overflow. | 
|  | * So the maximum is 254-28. | 
|  | * | 
|  | * Ceoffset contains the offset to be used with setcelen | 
|  | * to patch the CE pointing at the Cbuf once we know how | 
|  | * long the Cbuf is. | 
|  | */ | 
|  | typedef struct Cbuf Cbuf; | 
|  | struct Cbuf { | 
|  | int len;	/* written so far, of 254-28 */ | 
|  | ulong ceoffset; | 
|  | }; | 
|  |  | 
|  | static int | 
|  | freespace(Cbuf *cp) | 
|  | { | 
|  | return (254-28) - cp->len; | 
|  | } | 
|  |  | 
|  | static Cbuf* | 
|  | ensurespace(Cdimg *cd, int n, Cbuf *co, Cbuf *cn, int dowrite) | 
|  | { | 
|  | ulong end; | 
|  |  | 
|  | if(co->len+n <= 254-28) { | 
|  | co->len += n; | 
|  | return co; | 
|  | } | 
|  |  | 
|  | co->len += 28; | 
|  | assert(co->len <= 254); | 
|  |  | 
|  | if(dowrite == 0) { | 
|  | cn->len = n; | 
|  | return cn; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * the current blockette is full; update cd->rrcontin and then | 
|  | * write a CE record to finish it.  Unfortunately we need to | 
|  | * figure out which block will be next before we write the CE. | 
|  | */ | 
|  | end = Cwoffset(cd)+28; | 
|  |  | 
|  | /* | 
|  | * if we're in a continuation blockette, update rrcontin. | 
|  | * also, write our length into the field of the CE record | 
|  | * that points at us. | 
|  | */ | 
|  | if(cd->rrcontin+co->len == end) { | 
|  | assert(cd->rrcontin != 0); | 
|  | assert(co == cn); | 
|  | cd->rrcontin += co->len; | 
|  | setcelen(cd, co->ceoffset, co->len); | 
|  | } else | 
|  | assert(co != cn); | 
|  |  | 
|  | /* | 
|  | * if the current continuation block can't fit another | 
|  | * blockette, then start a new continuation block. | 
|  | * rrcontin = 0 (mod Blocksize) means we just finished | 
|  | * one, not that we've just started one. | 
|  | */ | 
|  | if(cd->rrcontin%Blocksize == 0 | 
|  | || cd->rrcontin/Blocksize != (cd->rrcontin+256)/Blocksize) { | 
|  | cd->rrcontin = cd->nextblock*Blocksize; | 
|  | cd->nextblock++; | 
|  | } | 
|  |  | 
|  | cn->ceoffset = CputsuspCE(cd, cd->rrcontin); | 
|  |  | 
|  | assert(Cwoffset(cd) == end); | 
|  |  | 
|  | cn->len = n; | 
|  | Cwseek(cd, cd->rrcontin); | 
|  | assert(cd->rrcontin != 0); | 
|  |  | 
|  | return cn; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Put down the name, but we might need to break it | 
|  | * into chunks so that each chunk fits in 254-28-5 bytes. | 
|  | * What a crock. | 
|  | * | 
|  | * The new Plan 9 format uses strings of this form too, | 
|  | * since they're already there. | 
|  | */ | 
|  | Cbuf* | 
|  | Cputstring(Cdimg *cd, Cbuf *cp, Cbuf *cn, char *nm, char *p, int flags, int dowrite) | 
|  | { | 
|  | char buf[256], *q; | 
|  | int free; | 
|  |  | 
|  | for(; p[0] != '\0'; p = q) { | 
|  | cp = ensurespace(cd, 5+1, cp, cn, dowrite); | 
|  | cp->len -= 5+1; | 
|  | free = freespace(cp); | 
|  | assert(5+1 <= free && free < 256); | 
|  |  | 
|  | strncpy(buf, p, free-5); | 
|  | buf[free-5] = '\0'; | 
|  | q = p+strlen(buf); | 
|  | p = buf; | 
|  |  | 
|  | ensurespace(cd, 5+strlen(p), cp, nil, dowrite);	/* nil: better not use this. */ | 
|  | Cputrripname(cd, nm, flags | (q[0] ? NMcontinue : 0), p, dowrite); | 
|  | } | 
|  | return cp; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Write a Rock Ridge SUSP set of records for a directory entry. | 
|  | */ | 
|  | int | 
|  | Cputsysuse(Cdimg *cd, Direc *d, int dot, int dowrite, int initlen) | 
|  | { | 
|  | char buf[256], buf0[256], *nextpath, *p, *path, *q; | 
|  | int flags, free, m, what; | 
|  | ulong o; | 
|  | Cbuf cn, co, *cp; | 
|  |  | 
|  | assert(cd != nil); | 
|  | assert((initlen&1) == 0); | 
|  |  | 
|  | if(dot == DTroot) | 
|  | return 0; | 
|  |  | 
|  | co.len = initlen; | 
|  |  | 
|  | o = Cwoffset(cd); | 
|  |  | 
|  | assert(dowrite==0 || Cwoffset(cd) == o+co.len-initlen); | 
|  | cp = &co; | 
|  |  | 
|  | if (dot == DTrootdot) { | 
|  | m = CputsuspSP(cd, 0); | 
|  | cp = ensurespace(cd, m, cp, &cn, dowrite); | 
|  | CputsuspSP(cd, dowrite); | 
|  |  | 
|  | m = CputsuspER(cd, 0); | 
|  | cp = ensurespace(cd, m, cp, &cn, dowrite); | 
|  | CputsuspER(cd, dowrite); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * In a perfect world, we'd be able to omit the NM | 
|  | * entries when our name was all lowercase and conformant, | 
|  | * but OpenBSD insists on uppercasing (really, not lowercasing) | 
|  | * the ISO9660 names. | 
|  | */ | 
|  | what = RR_PX | RR_TF | RR_NM; | 
|  | if(d != nil && (d->mode & CHLINK)) | 
|  | what |= RR_SL; | 
|  |  | 
|  | m = CputsuspRR(cd, what, 0); | 
|  | cp = ensurespace(cd, m, cp, &cn, dowrite); | 
|  | CputsuspRR(cd, what, dowrite); | 
|  |  | 
|  | if(what & RR_PX) { | 
|  | m = CputrripPX(cd, d, dot, 0); | 
|  | cp = ensurespace(cd, m, cp, &cn, dowrite); | 
|  | CputrripPX(cd, d, dot, dowrite); | 
|  | } | 
|  |  | 
|  | if(what & RR_NM) { | 
|  | if(dot == DTiden) | 
|  | p = d->name; | 
|  | else if(dot == DTdotdot) | 
|  | p = ".."; | 
|  | else | 
|  | p = "."; | 
|  |  | 
|  | flags = suspdirflags(d, dot); | 
|  | assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen); | 
|  | cp = Cputstring(cd, cp, &cn, "NM", p, flags, dowrite); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Put down the symbolic link.  This is even more of a crock. | 
|  | * Not only are the individual elements potentially split, | 
|  | * but the whole path itself can be split across SL blocks. | 
|  | * To keep the code simple as possible (really), we write | 
|  | * only one element per SL block, wasting 6 bytes per element. | 
|  | */ | 
|  | if(what & RR_SL) { | 
|  | for(path=d->symlink; path[0] != '\0'; path=nextpath) { | 
|  | /* break off one component */ | 
|  | if((nextpath = strchr(path, '/')) == nil) | 
|  | nextpath = path+strlen(path); | 
|  | strncpy(buf0, path, nextpath-path); | 
|  | buf0[nextpath-path] = '\0'; | 
|  | if(nextpath[0] == '/') | 
|  | nextpath++; | 
|  | p = buf0; | 
|  |  | 
|  | /* write the name, perhaps broken into pieces */ | 
|  | if(strcmp(p, "") == 0) | 
|  | flags = NMroot; | 
|  | else if(strcmp(p, ".") == 0) | 
|  | flags = NMcurrent; | 
|  | else if(strcmp(p, "..") == 0) | 
|  | flags = NMparent; | 
|  | else | 
|  | flags = 0; | 
|  |  | 
|  | /* the do-while handles the empty string properly */ | 
|  | do { | 
|  | /* must have room for at least 1 byte of name */ | 
|  | cp = ensurespace(cd, 7+1, cp, &cn, dowrite); | 
|  | cp->len -= 7+1; | 
|  | free = freespace(cp); | 
|  | assert(7+1 <= free && free < 256); | 
|  |  | 
|  | strncpy(buf, p, free-7); | 
|  | buf[free-7] = '\0'; | 
|  | q = p+strlen(buf); | 
|  | p = buf; | 
|  |  | 
|  | /* nil: better not need to expand */ | 
|  | assert(7+strlen(p) <= free); | 
|  | ensurespace(cd, 7+strlen(p), cp, nil, dowrite); | 
|  | CputrripSL(cd, nextpath[0], flags | (q[0] ? NMcontinue : 0), p, dowrite); | 
|  | p = q; | 
|  | } while(p[0] != '\0'); | 
|  | } | 
|  | } | 
|  |  | 
|  | assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen); | 
|  |  | 
|  | if(what & RR_TF) { | 
|  | m = CputrripTF(cd, d, TFcreation|TFmodify|TFaccess|TFattributes, 0); | 
|  | cp = ensurespace(cd, m, cp, &cn, dowrite); | 
|  | CputrripTF(cd, d, TFcreation|TFmodify|TFaccess|TFattributes, dowrite); | 
|  | } | 
|  | assert(dowrite==0 || cp != &co || Cwoffset(cd) == o+co.len-initlen); | 
|  |  | 
|  | if(cp == &cn && dowrite) { | 
|  | /* seek out of continuation, but mark our place */ | 
|  | cd->rrcontin = Cwoffset(cd); | 
|  | setcelen(cd, cn.ceoffset, cn.len); | 
|  | Cwseek(cd, o+co.len-initlen); | 
|  | } | 
|  |  | 
|  | if(co.len & 1) { | 
|  | co.len++; | 
|  | if(dowrite) | 
|  | Cputc(cd, 0); | 
|  | } | 
|  |  | 
|  | if(dowrite) { | 
|  | if(Cwoffset(cd) != o+co.len-initlen) | 
|  | fprint(2, "offset %lud o+co.len-initlen %lud\n", Cwoffset(cd), o+co.len-initlen); | 
|  | assert(Cwoffset(cd) == o+co.len-initlen); | 
|  | } else | 
|  | assert(Cwoffset(cd) == o); | 
|  |  | 
|  | assert(co.len <= 255); | 
|  | return co.len - initlen; | 
|  | } | 
|  |  | 
|  | static char SUSPrrip[10] = "RRIP_1991A"; | 
|  | static char SUSPdesc[84] = "RRIP <more garbage here>"; | 
|  | static char SUSPsrc[135] = "RRIP <more garbage here>"; | 
|  |  | 
|  | static ulong | 
|  | CputsuspCE(Cdimg *cd, ulong offset) | 
|  | { | 
|  | ulong o, x; | 
|  |  | 
|  | chat("writing SUSP CE record pointing to %ld, %ld\n", offset/Blocksize, offset%Blocksize); | 
|  | o = Cwoffset(cd); | 
|  | Cputc(cd, 'C'); | 
|  | Cputc(cd, 'E'); | 
|  | Cputc(cd, 28); | 
|  | Cputc(cd, 1); | 
|  | Cputn(cd, offset/Blocksize, 4); | 
|  | Cputn(cd, offset%Blocksize, 4); | 
|  | x = Cwoffset(cd); | 
|  | Cputn(cd, 0, 4); | 
|  | assert(Cwoffset(cd) == o+28); | 
|  |  | 
|  | return x; | 
|  | } | 
|  |  | 
|  | static int | 
|  | CputsuspER(Cdimg *cd, int dowrite) | 
|  | { | 
|  | assert(cd != nil); | 
|  |  | 
|  | if(dowrite) { | 
|  | chat("writing SUSP ER record\n"); | 
|  | Cputc(cd, 'E');           /* ER field marker */ | 
|  | Cputc(cd, 'R'); | 
|  | Cputc(cd, 26);            /* Length          */ | 
|  | Cputc(cd, 1);             /* Version         */ | 
|  | Cputc(cd, 10);            /* LEN_ID          */ | 
|  | Cputc(cd, 4);             /* LEN_DESC        */ | 
|  | Cputc(cd, 4);             /* LEN_SRC         */ | 
|  | Cputc(cd, 1);             /* EXT_VER         */ | 
|  | Cputs(cd, SUSPrrip, 10);  /* EXT_ID          */ | 
|  | Cputs(cd, SUSPdesc, 4);   /* EXT_DESC        */ | 
|  | Cputs(cd, SUSPsrc, 4);    /* EXT_SRC         */ | 
|  | } | 
|  | return 8+10+4+4; | 
|  | } | 
|  |  | 
|  | static int | 
|  | CputsuspRR(Cdimg *cd, int what, int dowrite) | 
|  | { | 
|  | assert(cd != nil); | 
|  |  | 
|  | if(dowrite) { | 
|  | Cputc(cd, 'R');           /* RR field marker */ | 
|  | Cputc(cd, 'R'); | 
|  | Cputc(cd, 5);             /* Length          */ | 
|  | Cputc(cd, 1);		  /* Version number  */ | 
|  | Cputc(cd, what);          /* Flags           */ | 
|  | } | 
|  | return 5; | 
|  | } | 
|  |  | 
|  | static int | 
|  | CputsuspSP(Cdimg *cd, int dowrite) | 
|  | { | 
|  | assert(cd!=0); | 
|  |  | 
|  | if(dowrite) { | 
|  | chat("writing SUSP SP record\n"); | 
|  | Cputc(cd, 'S');           /* SP field marker */ | 
|  | Cputc(cd, 'P'); | 
|  | Cputc(cd, 7);             /* Length          */ | 
|  | Cputc(cd, 1);             /* Version         */ | 
|  | Cputc(cd, 0xBE);          /* Magic           */ | 
|  | Cputc(cd, 0xEF); | 
|  | Cputc(cd, 0); | 
|  | } | 
|  |  | 
|  | return 7; | 
|  | } | 
|  |  | 
|  | #ifdef NOTUSED | 
|  | static int | 
|  | CputsuspST(Cdimg *cd, int dowrite) | 
|  | { | 
|  | assert(cd!=0); | 
|  |  | 
|  | if(dowrite) { | 
|  | Cputc(cd, 'S');           /* ST field marker */ | 
|  | Cputc(cd, 'T'); | 
|  | Cputc(cd, 4);             /* Length          */ | 
|  | Cputc(cd, 1);             /* Version         */ | 
|  | } | 
|  | return 4; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static ulong | 
|  | suspdirflags(Direc *d, int dot) | 
|  | { | 
|  | uchar flags; | 
|  |  | 
|  | USED(d); | 
|  | flags = 0; | 
|  | switch(dot) { | 
|  | default: | 
|  | assert(0); | 
|  | case DTdot: | 
|  | case DTrootdot: | 
|  | flags |= NMcurrent; | 
|  | break; | 
|  | case DTdotdot: | 
|  | flags |= NMparent; | 
|  | break; | 
|  | case DTroot: | 
|  | flags |= NMvolroot; | 
|  | break; | 
|  | case DTiden: | 
|  | break; | 
|  | } | 
|  | return flags; | 
|  | } | 
|  |  | 
|  | static int | 
|  | Cputrripname(Cdimg *cd, char *nm, int flags, char *name, int dowrite) | 
|  | { | 
|  | int l; | 
|  |  | 
|  | l = strlen(name); | 
|  | if(dowrite) { | 
|  | Cputc(cd, nm[0]);                   /* NM field marker */ | 
|  | Cputc(cd, nm[1]); | 
|  | Cputc(cd, l+5);        /* Length          */ | 
|  | Cputc(cd, 1);                     /* Version         */ | 
|  | Cputc(cd, flags);                 /* Flags           */ | 
|  | Cputs(cd, name, l);    /* Alternate name  */ | 
|  | } | 
|  | return 5+l; | 
|  | } | 
|  |  | 
|  | static int | 
|  | CputrripSL(Cdimg *cd, int contin, int flags, char *name, int dowrite) | 
|  | { | 
|  | int l; | 
|  |  | 
|  | l = strlen(name); | 
|  | if(dowrite) { | 
|  | Cputc(cd, 'S'); | 
|  | Cputc(cd, 'L'); | 
|  | Cputc(cd, l+7); | 
|  | Cputc(cd, 1); | 
|  | Cputc(cd, contin ? 1 : 0); | 
|  | Cputc(cd, flags); | 
|  | Cputc(cd, l); | 
|  | Cputs(cd, name, l); | 
|  | } | 
|  | return 7+l; | 
|  | } | 
|  |  | 
|  | static int | 
|  | CputrripPX(Cdimg *cd, Direc *d, int dot, int dowrite) | 
|  | { | 
|  | assert(cd!=0); | 
|  |  | 
|  | if(dowrite) { | 
|  | Cputc(cd, 'P');             /* PX field marker */ | 
|  | Cputc(cd, 'X'); | 
|  | Cputc(cd, 36);              /* Length          */ | 
|  | Cputc(cd, 1);               /* Version         */ | 
|  |  | 
|  | Cputn(cd, mode(d, dot), 4); /* POSIX File mode */ | 
|  | Cputn(cd, nlink(d), 4);     /* POSIX st_nlink  */ | 
|  | Cputn(cd, d?d->uidno:0, 4);  /* POSIX st_uid    */ | 
|  | Cputn(cd, d?d->gidno:0, 4);  /* POSIX st_gid    */ | 
|  | } | 
|  |  | 
|  | return 36; | 
|  | } | 
|  |  | 
|  | static int | 
|  | CputrripTF(Cdimg *cd, Direc *d, int type, int dowrite) | 
|  | { | 
|  | int i, length; | 
|  |  | 
|  | assert(cd!=0); | 
|  | assert(!(type & TFlongform)); | 
|  |  | 
|  | length = 0; | 
|  | for(i=0; i<7; i++) | 
|  | if (type & (1<<i)) | 
|  | length++; | 
|  | assert(length == 4); | 
|  |  | 
|  | if(dowrite) { | 
|  | Cputc(cd, 'T');				/* TF field marker */ | 
|  | Cputc(cd, 'F'); | 
|  | Cputc(cd, 5+7*length);		/* Length		 */ | 
|  | Cputc(cd, 1);				/* Version		 */ | 
|  | Cputc(cd, type);					/* Flags (types)	 */ | 
|  |  | 
|  | if (type & TFcreation) | 
|  | Cputdate(cd, d?d->ctime:0); | 
|  | if (type & TFmodify) | 
|  | Cputdate(cd, d?d->mtime:0); | 
|  | if (type & TFaccess) | 
|  | Cputdate(cd, d?d->atime:0); | 
|  | if (type & TFattributes) | 
|  | Cputdate(cd, d?d->ctime:0); | 
|  |  | 
|  | /*	if (type & TFbackup) */ | 
|  | /*		Cputdate(cd, 0); */ | 
|  | /*	if (type & TFexpiration) */ | 
|  | /*		Cputdate(cd, 0); */ | 
|  | /*	if (type & TFeffective) */ | 
|  | /*		Cputdate(cd, 0); */ | 
|  | } | 
|  | return 5+7*length; | 
|  | } | 
|  |  | 
|  |  | 
|  | #define NONPXMODES  (DMDIR & DMAPPEND & DMEXCL & DMMOUNT) | 
|  | #define POSIXMODEMASK (0177777) | 
|  | #ifndef S_IFMT | 
|  | #define S_IFMT  (0170000) | 
|  | #endif | 
|  | #ifndef S_IFDIR | 
|  | #define S_IFDIR (0040000) | 
|  | #endif | 
|  | #ifndef S_IFREG | 
|  | #define S_IFREG (0100000) | 
|  | #endif | 
|  | #ifndef S_IFLNK | 
|  | #define S_IFLNK (0120000) | 
|  | #endif | 
|  | #undef  ISTYPE | 
|  | #define ISTYPE(mode, mask)  (((mode) & S_IFMT) == (mask)) | 
|  | #ifndef S_ISDIR | 
|  | #define S_ISDIR(mode) ISTYPE(mode, S_IFDIR) | 
|  | #endif | 
|  | #ifndef S_ISREG | 
|  | #define S_ISREG(mode) ISTYPE(mode, S_IREG) | 
|  | #endif | 
|  | #ifndef S_ISLNK | 
|  | #define S_ISLNK(mode) ISTYPE(mode, S_ILNK) | 
|  | #endif | 
|  |  | 
|  |  | 
|  | static long | 
|  | mode(Direc *d, int dot) | 
|  | { | 
|  | long mode; | 
|  |  | 
|  | if (!d) | 
|  | return 0; | 
|  |  | 
|  | if ((dot != DTroot) && (dot != DTrootdot)) { | 
|  | mode = (d->mode & ~(NONPXMODES)); | 
|  | if (d->mode & DMDIR) | 
|  | mode |= S_IFDIR; | 
|  | else if (d->mode & CHLINK) | 
|  | mode |= S_IFLNK; | 
|  | else | 
|  | mode |= S_IFREG; | 
|  | } else | 
|  | mode = S_IFDIR | (0755); | 
|  |  | 
|  | mode &= POSIXMODEMASK; | 
|  |  | 
|  | /* Botch: not all POSIX types supported yet */ | 
|  | assert(mode & (S_IFDIR|S_IFREG)); | 
|  |  | 
|  | chat("writing PX record mode field %ulo with dot %d and name \"%s\"\n", mode, dot, d->name); | 
|  |  | 
|  | return mode; | 
|  | } | 
|  |  | 
|  | static long | 
|  | nlink(Direc *d)   /* Trump up the nlink field for POSIX compliance */ | 
|  | { | 
|  | int i; | 
|  | long n; | 
|  |  | 
|  | if (!d) | 
|  | return 0; | 
|  |  | 
|  | n = 1; | 
|  | if (d->mode & DMDIR)   /* One for "." and one more for ".." */ | 
|  | n++; | 
|  |  | 
|  | for(i=0; i<d->nchild; i++) | 
|  | if (d->child[i].mode & DMDIR) | 
|  | n++; | 
|  |  | 
|  | return n; | 
|  | } | 
|  |  |