text: /*
   * 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, vlong 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, vlong woffset, ulong len)
  {
  	vlong 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 */
  	uvlong	ceoffset;
  };
  
  static int
  freespace(Cbuf *cp)
  {
  	return (254-28) - cp->len;
  }
  
  static Cbuf*
  ensurespace(Cdimg *cd, int n, Cbuf *co, Cbuf *cn, int dowrite)
  {
  	uvlong 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 = (vlong)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;
  	uvlong 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 %llud o+co.len-initlen %llud\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, vlong offset)
  {
  	vlong 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;
  }
  
  