text: /*
   * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
   * Copyright (c) 1995 Martin Husemann
   * Some structure declaration borrowed from Paul Popelka
   * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <ctype.h>
  #include <unistd.h>
  #include <time.h>
  
  #include <sys/param.h>
  
  #include "ext.h"
  
  #define	SLOT_EMPTY	0x00		/* slot has never been used */
  #define	SLOT_E5		0x05		/* the real value is 0xe5 */
  #define	SLOT_DELETED	0xe5		/* file in this slot deleted */
  
  #define	ATTR_NORMAL	0x00		/* normal file */
  #define	ATTR_READONLY	0x01		/* file is readonly */
  #define	ATTR_HIDDEN	0x02		/* file is hidden */
  #define	ATTR_SYSTEM	0x04		/* file is a system file */
  #define	ATTR_VOLUME	0x08		/* entry is a volume label */
  #define	ATTR_DIRECTORY	0x10		/* entry is a directory name */
  #define	ATTR_ARCHIVE	0x20		/* file is new or modified */
  
  #define	ATTR_WIN95	0x0f		/* long name record */
  
  /*
   * This is the format of the contents of the deTime field in the direntry
   * structure.
   * We don't use bitfields because we don't know how compilers for
   * arbitrary machines will lay them out.
   */
  #define DT_2SECONDS_MASK	0x1F	/* seconds divided by 2 */
  #define DT_2SECONDS_SHIFT	0
  #define DT_MINUTES_MASK		0x7E0	/* minutes */
  #define DT_MINUTES_SHIFT	5
  #define DT_HOURS_MASK		0xF800	/* hours */
  #define DT_HOURS_SHIFT		11
  
  /*
   * This is the format of the contents of the deDate field in the direntry
   * structure.
   */
  #define DD_DAY_MASK		0x1F	/* day of month */
  #define DD_DAY_SHIFT		0
  #define DD_MONTH_MASK		0x1E0	/* month */
  #define DD_MONTH_SHIFT		5
  #define DD_YEAR_MASK		0xFE00	/* year - 1980 */
  #define DD_YEAR_SHIFT		9
  
  
  /* dir.c */
  static struct dosDirEntry *newDosDirEntry(void);
  static void freeDosDirEntry(struct dosDirEntry *);
  static struct dirTodoNode *newDirTodo(void);
  static void freeDirTodo(struct dirTodoNode *);
  static char *fullpath(struct dosDirEntry *);
  static u_char calcShortSum(u_char *);
  static int delete(int, struct bootblock *, struct fatEntry *, cl_t, int,
      cl_t, int, int);
  static int removede(int, struct bootblock *, struct fatEntry *, u_char *,
      u_char *, cl_t, cl_t, cl_t, char *, int);
  static int checksize(struct bootblock *, struct fatEntry *, u_char *,
      struct dosDirEntry *);
  static int readDosDirSection(int, struct bootblock *, struct fatEntry *,
      struct dosDirEntry *);
  
  /*
   * Manage free dosDirEntry structures.
   */
  static struct dosDirEntry *freede;
  
  static struct dosDirEntry *
  newDosDirEntry(void)
  {
  	struct dosDirEntry *de;
  
  	if (!(de = freede)) {
  		if (!(de = (struct dosDirEntry *)malloc(sizeof *de)))
  			return 0;
  	} else
  		freede = de->next;
  	return de;
  }
  
  static void
  freeDosDirEntry(struct dosDirEntry *de)
  {
  	de->next = freede;
  	freede = de;
  }
  
  /*
   * The same for dirTodoNode structures.
   */
  static struct dirTodoNode *freedt;
  
  static struct dirTodoNode *
  newDirTodo(void)
  {
  	struct dirTodoNode *dt;
  
  	if (!(dt = freedt)) {
  		if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt)))
  			return 0;
  	} else
  		freedt = dt->next;
  	return dt;
  }
  
  static void
  freeDirTodo(struct dirTodoNode *dt)
  {
  	dt->next = freedt;
  	freedt = dt;
  }
  
  /*
   * The stack of unread directories
   */
  static struct dirTodoNode *pendingDirectories = NULL;
  
  /*
   * Return the full pathname for a directory entry.
   */
  static char *
  fullpath(struct dosDirEntry *dir)
  {
  	static char namebuf[MAXPATHLEN + 1];
  	char *cp, *np;
  	int nl;
  
  	cp = namebuf + sizeof namebuf - 1;
  	*cp = '\0';
  	do {
  		np = dir->lname[0] ? dir->lname : dir->name;
  		nl = strlen(np);
  		if ((cp -= nl) <= namebuf + 1)
  			break;
  		memcpy(cp, np, nl);
  		*--cp = '/';
  	} while ((dir = dir->parent) != NULL);
  	if (dir)
  		*--cp = '?';
  	else
  		cp++;
  	return cp;
  }
  
  /*
   * Calculate a checksum over an 8.3 alias name
   */
  static u_char
  calcShortSum(u_char *p)
  {
  	u_char sum = 0;
  	int i;
  
  	for (i = 0; i < 11; i++) {
  		sum = (sum << 7)|(sum >> 1);	/* rotate right */
  		sum += p[i];
  	}
  
  	return sum;
  }
  
  /*
   * Global variables temporarily used during a directory scan
   */
  static char longName[DOSLONGNAMELEN] = "";
  static u_char *buffer = NULL;
  static u_char *delbuf = NULL;
  
  static struct dosDirEntry *rootDir;
  static struct dosDirEntry *lostDir;
  
  /*
   * Init internal state for a new directory scan.
   */
  int
  resetDosDirSection(struct bootblock *boot, struct fatEntry *fat)
  {
  	int b1, b2;
  	cl_t cl;
  	int ret = FSOK;
  	size_t len;
  
  	b1 = boot->bpbRootDirEnts * 32;
  	b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec;
  
  	if ((buffer = malloc(len = MAX(b1, b2))) == NULL) {
  		perr("No space for directory buffer (%zu)", len);
  		return FSFATAL;
  	}
  
  	if ((delbuf = malloc(len = b2)) == NULL) {
  		free(buffer);
  		perr("No space for directory delbuf (%zu)", len);
  		return FSFATAL;
  	}
  
  	if ((rootDir = newDosDirEntry()) == NULL) {
  		free(buffer);
  		free(delbuf);
  		perr("No space for directory entry");
  		return FSFATAL;
  	}
  
  	memset(rootDir, 0, sizeof *rootDir);
  	if (boot->flags & FAT32) {
  		if (boot->bpbRootClust < CLUST_FIRST ||
  		    boot->bpbRootClust >= boot->NumClusters) {
  			pfatal("Root directory starts with cluster out of range(%u)",
  			       boot->bpbRootClust);
  			return FSFATAL;
  		}
  		cl = fat[boot->bpbRootClust].next;
  		if (cl < CLUST_FIRST
  		    || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
  		    || fat[boot->bpbRootClust].head != boot->bpbRootClust) {
  			if (cl == CLUST_FREE)
  				pwarn("Root directory starts with free cluster\n");
  			else if (cl >= CLUST_RSRVD)
  				pwarn("Root directory starts with cluster marked %s\n",
  				      rsrvdcltype(cl));
  			else {
  				pfatal("Root directory doesn't start a cluster chain");
  				return FSFATAL;
  			}
  			if (ask(1, "Fix")) {
  				fat[boot->bpbRootClust].next = CLUST_FREE;
  				ret = FSFATMOD;
  			} else
  				ret = FSFATAL;
  		}
  
  		fat[boot->bpbRootClust].flags |= FAT_USED;
  		rootDir->head = boot->bpbRootClust;
  	}
  
  	return ret;
  }
  
  /*
   * Cleanup after a directory scan
   */
  void
  finishDosDirSection(void)
  {
  	struct dirTodoNode *p, *np;
  	struct dosDirEntry *d, *nd;
  
  	for (p = pendingDirectories; p; p = np) {
  		np = p->next;
  		freeDirTodo(p);
  	}
  	pendingDirectories = NULL;
  	for (d = rootDir; d; d = nd) {
  		if ((nd = d->child) != NULL) {
  			d->child = 0;
  			continue;
  		}
  		if (!(nd = d->next))
  			nd = d->parent;
  		freeDosDirEntry(d);
  	}
  	rootDir = lostDir = NULL;
  	free(buffer);
  	free(delbuf);
  	buffer = NULL;
  	delbuf = NULL;
  }
  
  /*
   * Delete directory entries between startcl, startoff and endcl, endoff.
   */
  static int
  delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl,
      int startoff, cl_t endcl, int endoff, int notlast)
  {
  	u_char *s, *e;
  	off_t off;
  	int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
  
  	s = delbuf + startoff;
  	e = delbuf + clsz;
  	while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) {
  		if (startcl == endcl) {
  			if (notlast)
  				break;
  			e = delbuf + endoff;
  		}
  		off = startcl * boot->bpbSecPerClust + boot->ClusterOffset;
  		off *= boot->bpbBytesPerSec;
  		if (lseek(f, off, SEEK_SET) != off
  		    || read(f, delbuf, clsz) != clsz) {
  			perr("Unable to read directory");
  			return FSFATAL;
  		}
  		while (s < e) {
  			*s = SLOT_DELETED;
  			s += 32;
  		}
  		if (lseek(f, off, SEEK_SET) != off
  		    || write(f, delbuf, clsz) != clsz) {
  			perr("Unable to write directory");
  			return FSFATAL;
  		}
  		if (startcl == endcl)
  			break;
  		startcl = fat[startcl].next;
  		s = delbuf;
  	}
  	return FSOK;
  }
  
  static int
  removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start,
      u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type)
  {
  	switch (type) {
  	case 0:
  		pwarn("Invalid long filename entry for %s\n", path);
  		break;
  	case 1:
  		pwarn("Invalid long filename entry at end of directory %s\n",
  		    path);
  		break;
  	case 2:
  		pwarn("Invalid long filename entry for volume label\n");
  		break;
  	}
  	if (ask(0, "Remove")) {
  		if (startcl != curcl) {
  			if (delete(f, boot, fat,
  				   startcl, start - buffer,
  				   endcl, end - buffer,
  				   endcl == curcl) == FSFATAL)
  				return FSFATAL;
  			start = buffer;
  		}
  		/* startcl is < CLUST_FIRST for !fat32 root */
  		if ((endcl == curcl) || (startcl < CLUST_FIRST))
  			for (; start < end; start += 32)
  				*start = SLOT_DELETED;
  		return FSDIRMOD;
  	}
  	return FSERROR;
  }
  
  /*
   * Check an in-memory file entry
   */
  static int
  checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p,
      struct dosDirEntry *dir)
  {
  	/*
  	 * Check size on ordinary files
  	 */
  	u_int32_t physicalSize;
  
  	if (dir->head == CLUST_FREE)
  		physicalSize = 0;
  	else {
  		if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters)
  			return FSERROR;
  		physicalSize = fat[dir->head].length * boot->ClusterSize;
  	}
  	if (physicalSize < dir->size) {
  		pwarn("size of %s is %u, should at most be %u\n",
  		      fullpath(dir), dir->size, physicalSize);
  		if (ask(1, "Truncate")) {
  			dir->size = physicalSize;
  			p[28] = (u_char)physicalSize;
  			p[29] = (u_char)(physicalSize >> 8);
  			p[30] = (u_char)(physicalSize >> 16);
  			p[31] = (u_char)(physicalSize >> 24);
  			return FSDIRMOD;
  		} else
  			return FSERROR;
  	} else if (physicalSize - dir->size >= boot->ClusterSize) {
  		pwarn("%s has too many clusters allocated\n",
  		      fullpath(dir));
  		if (ask(1, "Drop superfluous clusters")) {
  			cl_t cl;
  			u_int32_t sz, len;
  
  			for (cl = dir->head, len = sz = 0;
  			    (sz += boot->ClusterSize) < dir->size; len++)
  				cl = fat[cl].next;
  			clearchain(boot, fat, fat[cl].next);
  			fat[cl].next = CLUST_EOF;
  			fat[dir->head].length = len;
  			return FSFATMOD;
  		} else
  			return FSERROR;
  	}
  	return FSOK;
  }
  
  /*
   * Read a directory and
   *   - resolve long name records
   *   - enter file and directory records into the parent's list
   *   - push directories onto the todo-stack
   */
  static int
  readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat,
      struct dosDirEntry *dir)
  {
  	struct dosDirEntry dirent, *d;
  	u_char *p, *vallfn, *invlfn, *empty;
  	off_t off;
  	int i, j, k, last;
  	cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0;
  	char *t;
  	u_int lidx = 0;
  	int shortSum;
  	int mod = FSOK;
  #define	THISMOD	0x8000			/* Only used within this routine */
  
  	cl = dir->head;
  	if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
  		/*
  		 * Already handled somewhere else.
  		 */
  		return FSOK;
  	}
  	shortSum = -1;
  	vallfn = invlfn = empty = NULL;
  	do {
  		if (!(boot->flags & FAT32) && !dir->parent) {
  			last = boot->bpbRootDirEnts * 32;
  			off = boot->bpbResSectors + boot->bpbFATs *
  			    boot->FATsecs;
  		} else {
  			last = boot->bpbSecPerClust * boot->bpbBytesPerSec;
  			off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
  		}
  
  		off *= boot->bpbBytesPerSec;
  		if (lseek(f, off, SEEK_SET) != off
  		    || read(f, buffer, last) != last) {
  			perr("Unable to read directory");
  			return FSFATAL;
  		}
  		last /= 32;
  		/*
  		 * Check `.' and `..' entries here?			XXX
  		 */
  		for (p = buffer, i = 0; i < last; i++, p += 32) {
  			if (dir->fsckflags & DIREMPWARN) {
  				*p = SLOT_EMPTY;
  				continue;
  			}
  
  			if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {
  				if (*p == SLOT_EMPTY) {
  					dir->fsckflags |= DIREMPTY;
  					empty = p;
  					empcl = cl;
  				}
  				continue;
  			}
  
  			if (dir->fsckflags & DIREMPTY) {
  				if (!(dir->fsckflags & DIREMPWARN)) {
  					pwarn("%s has entries after end of directory\n",
  					      fullpath(dir));
  					if (ask(1, "Extend")) {
  						u_char *q;
  
  						dir->fsckflags &= ~DIREMPTY;
  						if (delete(f, boot, fat,
  							   empcl, empty - buffer,
  							   cl, p - buffer, 1) == FSFATAL)
  							return FSFATAL;
  						q = empcl == cl ? empty : buffer;
  						for (; q < p; q += 32)
  							*q = SLOT_DELETED;
  						mod |= THISMOD|FSDIRMOD;
  					} else if (ask(0, "Truncate"))
  						dir->fsckflags |= DIREMPWARN;
  				}
  				if (dir->fsckflags & DIREMPWARN) {
  					*p = SLOT_DELETED;
  					mod |= THISMOD|FSDIRMOD;
  					continue;
  				} else if (dir->fsckflags & DIREMPTY)
  					mod |= FSERROR;
  				empty = NULL;
  			}
  
  			if (p[11] == ATTR_WIN95) {
  				if (*p & LRFIRST) {
  					if (shortSum != -1) {
  						if (!invlfn) {
  							invlfn = vallfn;
  							invcl = valcl;
  						}
  					}
  					memset(longName, 0, sizeof longName);
  					shortSum = p[13];
  					vallfn = p;
  					valcl = cl;
  				} else if (shortSum != p[13]
  					   || lidx != (*p & LRNOMASK)) {
  					if (!invlfn) {
  						invlfn = vallfn;
  						invcl = valcl;
  					}
  					if (!invlfn) {
  						invlfn = p;
  						invcl = cl;
  					}
  					vallfn = NULL;
  				}
  				lidx = *p & LRNOMASK;
  				t = longName + --lidx * 13;
  				for (k = 1; k < 11 && t < longName +
  				    sizeof(longName); k += 2) {
  					if (!p[k] && !p[k + 1])
  						break;
  					*t++ = p[k];
  					/*
  					 * Warn about those unusable chars in msdosfs here?	XXX
  					 */
  					if (p[k + 1])
  						t[-1] = '?';
  				}
  				if (k >= 11)
  					for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) {
  						if (!p[k] && !p[k + 1])
  							break;
  						*t++ = p[k];
  						if (p[k + 1])
  							t[-1] = '?';
  					}
  				if (k >= 26)
  					for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) {
  						if (!p[k] && !p[k + 1])
  							break;
  						*t++ = p[k];
  						if (p[k + 1])
  							t[-1] = '?';
  					}
  				if (t >= longName + sizeof(longName)) {
  					pwarn("long filename too long\n");
  					if (!invlfn) {
  						invlfn = vallfn;
  						invcl = valcl;
  					}
  					vallfn = NULL;
  				}
  				if (p[26] | (p[27] << 8)) {
  					pwarn("long filename record cluster start != 0\n");
  					if (!invlfn) {
  						invlfn = vallfn;
  						invcl = cl;
  					}
  					vallfn = NULL;
  				}
  				continue;	/* long records don't carry further
  						 * information */
  			}
  
  			/*
  			 * This is a standard msdosfs directory entry.
  			 */
  			memset(&dirent, 0, sizeof dirent);
  
  			/*
  			 * it's a short name record, but we need to know
  			 * more, so get the flags first.
  			 */
  			dirent.flags = p[11];
  
  			/*
  			 * Translate from 850 to ISO here		XXX
  			 */
  			for (j = 0; j < 8; j++)
  				dirent.name[j] = p[j];
  			dirent.name[8] = '\0';
  			for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)
  				dirent.name[k] = '\0';
  			if (dirent.name[k] != '\0')
  				k++;
  			if (dirent.name[0] == SLOT_E5)
  				dirent.name[0] = 0xe5;
  
  			if (dirent.flags & ATTR_VOLUME) {
  				if (vallfn || invlfn) {
  					mod |= removede(f, boot, fat,
  							invlfn ? invlfn : vallfn, p,
  							invlfn ? invcl : valcl, -1, 0,
  							fullpath(dir), 2);
  					vallfn = NULL;
  					invlfn = NULL;
  				}
  				continue;
  			}
  
  			if (p[8] != ' ')
  				dirent.name[k++] = '.';
  			for (j = 0; j < 3; j++)
  				dirent.name[k++] = p[j+8];
  			dirent.name[k] = '\0';
  			for (k--; k >= 0 && dirent.name[k] == ' '; k--)
  				dirent.name[k] = '\0';
  
  			if (vallfn && shortSum != calcShortSum(p)) {
  				if (!invlfn) {
  					invlfn = vallfn;
  					invcl = valcl;
  				}
  				vallfn = NULL;
  			}
  			dirent.head = p[26] | (p[27] << 8);
  			if (boot->ClustMask == CLUST32_MASK)
  				dirent.head |= (p[20] << 16) | (p[21] << 24);
  			dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24);
  			if (vallfn) {
  				strlcpy(dirent.lname, longName,
  				    sizeof(dirent.lname));
  				longName[0] = '\0';
  				shortSum = -1;
  			}
  
  			dirent.parent = dir;
  			dirent.next = dir->child;
  
  			if (invlfn) {
  				mod |= k = removede(f, boot, fat,
  						    invlfn, vallfn ? vallfn : p,
  						    invcl, vallfn ? valcl : cl, cl,
  						    fullpath(&dirent), 0);
  				if (mod & FSFATAL)
  					return FSFATAL;
  				if (vallfn
  				    ? (valcl == cl && vallfn != buffer)
  				    : p != buffer)
  					if (k & FSDIRMOD)
  						mod |= THISMOD;
  			}
  
  			vallfn = NULL; /* not used any longer */
  			invlfn = NULL;
  
  			if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) {
  				if (dirent.head != 0) {
  					pwarn("%s has clusters, but size 0\n",
  					      fullpath(&dirent));
  					if (ask(1, "Drop allocated clusters")) {
  						p[26] = p[27] = 0;
  						if (boot->ClustMask == CLUST32_MASK)
  							p[20] = p[21] = 0;
  						clearchain(boot, fat, dirent.head);
  						dirent.head = 0;
  						mod |= THISMOD|FSDIRMOD|FSFATMOD;
  					} else
  						mod |= FSERROR;
  				}
  			} else if (dirent.head == 0
  				   && !strcmp(dirent.name, "..")
  				   && dir->parent			/* XXX */
  				   && !dir->parent->parent) {
  				/*
  				 *  Do nothing, the parent is the root
  				 */
  			} else if (dirent.head < CLUST_FIRST
  				   || dirent.head >= boot->NumClusters
  				   || fat[dirent.head].next == CLUST_FREE
  				   || (fat[dirent.head].next >= CLUST_RSRVD
  				       && fat[dirent.head].next < CLUST_EOFS)
  				   || fat[dirent.head].head != dirent.head) {
  				if (dirent.head == 0)
  					pwarn("%s has no clusters\n",
  					      fullpath(&dirent));
  				else if (dirent.head < CLUST_FIRST
  					 || dirent.head >= boot->NumClusters)
  					pwarn("%s starts with cluster out of range(%u)\n",
  					      fullpath(&dirent),
  					      dirent.head);
  				else if (fat[dirent.head].next == CLUST_FREE)
  					pwarn("%s starts with free cluster\n",
  					      fullpath(&dirent));
  				else if (fat[dirent.head].next >= CLUST_RSRVD)
  					pwarn("%s starts with cluster marked %s\n",
  					      fullpath(&dirent),
  					      rsrvdcltype(fat[dirent.head].next));
  				else
  					pwarn("%s doesn't start a cluster chain\n",
  					      fullpath(&dirent));
  				if (dirent.flags & ATTR_DIRECTORY) {
  					if (ask(0, "Remove")) {
  						*p = SLOT_DELETED;
  						mod |= THISMOD|FSDIRMOD;
  					} else
  						mod |= FSERROR;
  					continue;
  				} else {
  					if (ask(1, "Truncate")) {
  						p[28] = p[29] = p[30] = p[31] = 0;
  						p[26] = p[27] = 0;
  						if (boot->ClustMask == CLUST32_MASK)
  							p[20] = p[21] = 0;
  						dirent.size = 0;
  						mod |= THISMOD|FSDIRMOD;
  					} else
  						mod |= FSERROR;
  				}
  			}
  
  			if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters)
  				fat[dirent.head].flags |= FAT_USED;
  
  			if (dirent.flags & ATTR_DIRECTORY) {
  				/*
  				 * gather more info for directories
  				 */
  				struct dirTodoNode *n;
  
  				if (dirent.size) {
  					pwarn("Directory %s has size != 0\n",
  					      fullpath(&dirent));
  					if (ask(1, "Correct")) {
  						p[28] = p[29] = p[30] = p[31] = 0;
  						dirent.size = 0;
  						mod |= THISMOD|FSDIRMOD;
  					} else
  						mod |= FSERROR;
  				}
  				/*
  				 * handle `.' and `..' specially
  				 */
  				if (strcmp(dirent.name, ".") == 0) {
  					if (dirent.head != dir->head) {
  						pwarn("`.' entry in %s has incorrect start cluster\n",
  						      fullpath(dir));
  						if (ask(1, "Correct")) {
  							dirent.head = dir->head;
  							p[26] = (u_char)dirent.head;
  							p[27] = (u_char)(dirent.head >> 8);
  							if (boot->ClustMask == CLUST32_MASK) {
  								p[20] = (u_char)(dirent.head >> 16);
  								p[21] = (u_char)(dirent.head >> 24);
  							}
  							mod |= THISMOD|FSDIRMOD;
  						} else
  							mod |= FSERROR;
  					}
  					continue;
  				}
  				if (strcmp(dirent.name, "..") == 0) {
  					if (dir->parent) {		/* XXX */
  						if (!dir->parent->parent) {
  							if (dirent.head) {
  								pwarn("`..' entry in %s has non-zero start cluster\n",
  								      fullpath(dir));
  								if (ask(1, "Correct")) {
  									dirent.head = 0;
  									p[26] = p[27] = 0;
  									if (boot->ClustMask == CLUST32_MASK)
  										p[20] = p[21] = 0;
  									mod |= THISMOD|FSDIRMOD;
  								} else
  									mod |= FSERROR;
  							}
  						} else if (dirent.head != dir->parent->head) {
  							pwarn("`..' entry in %s has incorrect start cluster\n",
  							      fullpath(dir));
  							if (ask(1, "Correct")) {
  								dirent.head = dir->parent->head;
  								p[26] = (u_char)dirent.head;
  								p[27] = (u_char)(dirent.head >> 8);
  								if (boot->ClustMask == CLUST32_MASK) {
  									p[20] = (u_char)(dirent.head >> 16);
  									p[21] = (u_char)(dirent.head >> 24);
  								}
  								mod |= THISMOD|FSDIRMOD;
  							} else
  								mod |= FSERROR;
  						}
  					}
  					continue;
  				}
  
  				/* create directory tree node */
  				if (!(d = newDosDirEntry())) {
  					perr("No space for directory");
  					return FSFATAL;
  				}
  				memcpy(d, &dirent, sizeof(struct dosDirEntry));
  				/* link it into the tree */
  				dir->child = d;
  
  				/* Enter this directory into the todo list */
  				if (!(n = newDirTodo())) {
  					perr("No space for todo list");
  					return FSFATAL;
  				}
  				n->next = pendingDirectories;
  				n->dir = d;
  				pendingDirectories = n;
  			} else {
  				mod |= k = checksize(boot, fat, p, &dirent);
  				if (k & FSDIRMOD)
  					mod |= THISMOD;
  			}
  			boot->NumFiles++;
  		}
  
  		if (!(boot->flags & FAT32) && !dir->parent)
  			break;
  
  		if (mod & THISMOD) {
  			last *= 32;
  			if (lseek(f, off, SEEK_SET) != off
  			    || write(f, buffer, last) != last) {
  				perr("Unable to write directory");
  				return FSFATAL;
  			}
  			mod &= ~THISMOD;
  		}
  	} while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters);
  	if (invlfn || vallfn)
  		mod |= removede(f, boot, fat,
  				invlfn ? invlfn : vallfn, p,
  				invlfn ? invcl : valcl, -1, 0,
  				fullpath(dir), 1);
  
  	/* The root directory of non fat32 filesystems is in a special
  	 * area and may have been modified above without being written out.
  	 */
  	if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) {
  		last *= 32;
  		if (lseek(f, off, SEEK_SET) != off
  		    || write(f, buffer, last) != last) {
  			perr("Unable to write directory");
  			return FSFATAL;
  		}
  		mod &= ~THISMOD;
  	}
  	return mod & ~THISMOD;
  }
  
  int
  handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)
  {
  	int mod;
  
  	mod = readDosDirSection(dosfs, boot, fat, rootDir);
  	if (mod & FSFATAL)
  		return FSFATAL;
  
  	/*
  	 * process the directory todo list
  	 */
  	while (pendingDirectories) {
  		struct dosDirEntry *dir = pendingDirectories->dir;
  		struct dirTodoNode *n = pendingDirectories->next;
  
  		/*
  		 * remove TODO entry now, the list might change during
  		 * directory reads
  		 */
  		freeDirTodo(pendingDirectories);
  		pendingDirectories = n;
  
  		/*
  		 * handle subdirectory
  		 */
  		mod |= readDosDirSection(dosfs, boot, fat, dir);
  		if (mod & FSFATAL)
  			return FSFATAL;
  	}
  
  	return mod;
  }
  
  /*
   * Try to reconnect a FAT chain into dir
   */
  static u_char *lfbuf;
  static cl_t lfcl;
  static off_t lfoff;
  
  int
  reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
  {
  	struct dosDirEntry d;
  	int len;
  	u_char *p;
  
  	if (!ask(1, "Reconnect"))
  		return FSERROR;
  
  	if (!lostDir) {
  		for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) {
  			if (!strcmp(lostDir->name, LOSTDIR))
  				break;
  		}
  		if (!lostDir) {		/* Create LOSTDIR?		XXX */
  			pwarn("No %s directory\n", LOSTDIR);
  			return FSERROR;
  		}
  	}
  	if (!lfbuf) {
  		lfbuf = malloc(boot->ClusterSize);
  		if (!lfbuf) {
  			perr("No space for buffer");
  			return FSFATAL;
  		}
  		p = NULL;
  	} else
  		p = lfbuf;
  	while (1) {
  		if (p)
  			for (; p < lfbuf + boot->ClusterSize; p += 32)
  				if (*p == SLOT_EMPTY
  				    || *p == SLOT_DELETED)
  					break;
  		if (p && p < lfbuf + boot->ClusterSize)
  			break;
  		lfcl = p ? fat[lfcl].next : lostDir->head;
  		if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
  			/* Extend LOSTDIR?				XXX */
  			pwarn("No space in %s\n", LOSTDIR);
  			return FSERROR;
  		}
  		lfoff = lfcl * boot->ClusterSize
  		    + boot->ClusterOffset * boot->bpbBytesPerSec;
  		if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
  		    || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
  			perr("could not read LOST.DIR");
  			return FSFATAL;
  		}
  		p = lfbuf;
  	}
  
  	boot->NumFiles++;
  	/* Ensure uniqueness of entry here!				XXX */
  	memset(&d, 0, sizeof d);
  	/* worst case -1 = 4294967295, 10 digits */
  	len = snprintf(d.name, sizeof(d.name), "%u", head);
  	d.flags = 0;
  	d.head = head;
  	d.size = fat[head].length * boot->ClusterSize;
  
  	memcpy(p, d.name, len);
  	memset(p + len, ' ', 11 - len);
  	memset(p + 11, 0, 32 - 11);
  	p[26] = (u_char)d.head;
  	p[27] = (u_char)(d.head >> 8);
  	if (boot->ClustMask == CLUST32_MASK) {
  		p[20] = (u_char)(d.head >> 16);
  		p[21] = (u_char)(d.head >> 24);
  	}
  	p[28] = (u_char)d.size;
  	p[29] = (u_char)(d.size >> 8);
  	p[30] = (u_char)(d.size >> 16);
  	p[31] = (u_char)(d.size >> 24);
  	fat[head].flags |= FAT_USED;
  	if (lseek(dosfs, lfoff, SEEK_SET) != lfoff
  	    || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) {
  		perr("could not write LOST.DIR");
  		return FSFATAL;
  	}
  	return FSDIRMOD;
  }
  
  void
  finishlf(void)
  {
  	if (lfbuf)
  		free(lfbuf);
  	lfbuf = NULL;
  }
  