179455Sobrien/* 279455Sobrien * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank 379455Sobrien * Copyright (c) 1995 Martin Husemann 479455Sobrien * Some structure declaration borrowed from Paul Popelka 579455Sobrien * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference. 679455Sobrien * 779455Sobrien * Redistribution and use in source and binary forms, with or without 879455Sobrien * modification, are permitted provided that the following conditions 979455Sobrien * are met: 1079455Sobrien * 1. Redistributions of source code must retain the above copyright 1179455Sobrien * notice, this list of conditions and the following disclaimer. 1279455Sobrien * 2. Redistributions in binary form must reproduce the above copyright 1379455Sobrien * notice, this list of conditions and the following disclaimer in the 1479455Sobrien * documentation and/or other materials provided with the distribution. 1579455Sobrien * 1679455Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 1779455Sobrien * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1879455Sobrien * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 1979455Sobrien * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, 2079455Sobrien * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2179455Sobrien * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2279455Sobrien * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2379455Sobrien * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2479455Sobrien * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2579455Sobrien * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2679455Sobrien */ 2779455Sobrien 2879455Sobrien 2979455Sobrien#include <sys/cdefs.h> 3079455Sobrien#ifndef lint 31241806Suqs__RCSID("$NetBSD: dir.c,v 1.20 2006/06/05 16:51:18 christos Exp $"); 3279455Sobrienstatic const char rcsid[] = 3379455Sobrien "$FreeBSD$"; 3479455Sobrien#endif /* not lint */ 3579455Sobrien 3679455Sobrien#include <stdio.h> 3779455Sobrien#include <stdlib.h> 3879455Sobrien#include <string.h> 3979455Sobrien#include <ctype.h> 4079455Sobrien#include <unistd.h> 4179455Sobrien#include <time.h> 4279455Sobrien 4379455Sobrien#include <sys/param.h> 4479455Sobrien 4579455Sobrien#include "ext.h" 4679455Sobrien#include "fsutil.h" 4779455Sobrien 4879455Sobrien#define SLOT_EMPTY 0x00 /* slot has never been used */ 4979455Sobrien#define SLOT_E5 0x05 /* the real value is 0xe5 */ 5079455Sobrien#define SLOT_DELETED 0xe5 /* file in this slot deleted */ 5179455Sobrien 5279455Sobrien#define ATTR_NORMAL 0x00 /* normal file */ 5379455Sobrien#define ATTR_READONLY 0x01 /* file is readonly */ 5479455Sobrien#define ATTR_HIDDEN 0x02 /* file is hidden */ 5579455Sobrien#define ATTR_SYSTEM 0x04 /* file is a system file */ 5679455Sobrien#define ATTR_VOLUME 0x08 /* entry is a volume label */ 5779455Sobrien#define ATTR_DIRECTORY 0x10 /* entry is a directory name */ 5879455Sobrien#define ATTR_ARCHIVE 0x20 /* file is new or modified */ 5979455Sobrien 6079455Sobrien#define ATTR_WIN95 0x0f /* long name record */ 6179455Sobrien 6279455Sobrien/* 6379455Sobrien * This is the format of the contents of the deTime field in the direntry 6479455Sobrien * structure. 6579455Sobrien * We don't use bitfields because we don't know how compilers for 6679455Sobrien * arbitrary machines will lay them out. 6779455Sobrien */ 6879455Sobrien#define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */ 6979455Sobrien#define DT_2SECONDS_SHIFT 0 7079455Sobrien#define DT_MINUTES_MASK 0x7E0 /* minutes */ 7179455Sobrien#define DT_MINUTES_SHIFT 5 7279455Sobrien#define DT_HOURS_MASK 0xF800 /* hours */ 7379455Sobrien#define DT_HOURS_SHIFT 11 7479455Sobrien 7579455Sobrien/* 7679455Sobrien * This is the format of the contents of the deDate field in the direntry 7779455Sobrien * structure. 7879455Sobrien */ 7979455Sobrien#define DD_DAY_MASK 0x1F /* day of month */ 8079455Sobrien#define DD_DAY_SHIFT 0 8179455Sobrien#define DD_MONTH_MASK 0x1E0 /* month */ 8279455Sobrien#define DD_MONTH_SHIFT 5 8379455Sobrien#define DD_YEAR_MASK 0xFE00 /* year - 1980 */ 8479455Sobrien#define DD_YEAR_SHIFT 9 8579455Sobrien 8679455Sobrien 8779455Sobrien/* dir.c */ 8892839Simpstatic struct dosDirEntry *newDosDirEntry(void); 8992839Simpstatic void freeDosDirEntry(struct dosDirEntry *); 9092839Simpstatic struct dirTodoNode *newDirTodo(void); 9192839Simpstatic void freeDirTodo(struct dirTodoNode *); 9292839Simpstatic char *fullpath(struct dosDirEntry *); 9392839Simpstatic u_char calcShortSum(u_char *); 9492839Simpstatic int delete(int, struct bootblock *, struct fatEntry *, cl_t, int, 9592839Simp cl_t, int, int); 9692839Simpstatic int removede(int, struct bootblock *, struct fatEntry *, u_char *, 9792839Simp u_char *, cl_t, cl_t, cl_t, char *, int); 9892839Simpstatic int checksize(struct bootblock *, struct fatEntry *, u_char *, 9992839Simp struct dosDirEntry *); 10092839Simpstatic int readDosDirSection(int, struct bootblock *, struct fatEntry *, 10192839Simp struct dosDirEntry *); 10279455Sobrien 10379455Sobrien/* 10479455Sobrien * Manage free dosDirEntry structures. 10579455Sobrien */ 10679455Sobrienstatic struct dosDirEntry *freede; 10779455Sobrien 10879455Sobrienstatic struct dosDirEntry * 10992839SimpnewDosDirEntry(void) 11079455Sobrien{ 11179455Sobrien struct dosDirEntry *de; 11279455Sobrien 11379455Sobrien if (!(de = freede)) { 11479455Sobrien if (!(de = (struct dosDirEntry *)malloc(sizeof *de))) 11579455Sobrien return 0; 11679455Sobrien } else 11779455Sobrien freede = de->next; 11879455Sobrien return de; 11979455Sobrien} 12079455Sobrien 12179455Sobrienstatic void 12292839SimpfreeDosDirEntry(struct dosDirEntry *de) 12379455Sobrien{ 12479455Sobrien de->next = freede; 12579455Sobrien freede = de; 12679455Sobrien} 12779455Sobrien 12879455Sobrien/* 12979455Sobrien * The same for dirTodoNode structures. 13079455Sobrien */ 13179455Sobrienstatic struct dirTodoNode *freedt; 13279455Sobrien 13379455Sobrienstatic struct dirTodoNode * 13492839SimpnewDirTodo(void) 13579455Sobrien{ 13679455Sobrien struct dirTodoNode *dt; 13779455Sobrien 13879455Sobrien if (!(dt = freedt)) { 13979455Sobrien if (!(dt = (struct dirTodoNode *)malloc(sizeof *dt))) 14079455Sobrien return 0; 14179455Sobrien } else 14279455Sobrien freedt = dt->next; 14379455Sobrien return dt; 14479455Sobrien} 14579455Sobrien 14679455Sobrienstatic void 14792839SimpfreeDirTodo(struct dirTodoNode *dt) 14879455Sobrien{ 14979455Sobrien dt->next = freedt; 15079455Sobrien freedt = dt; 15179455Sobrien} 15279455Sobrien 15379455Sobrien/* 15479455Sobrien * The stack of unread directories 15579455Sobrien */ 156227081Sedstatic struct dirTodoNode *pendingDirectories = NULL; 15779455Sobrien 15879455Sobrien/* 15979455Sobrien * Return the full pathname for a directory entry. 16079455Sobrien */ 16179455Sobrienstatic char * 16292839Simpfullpath(struct dosDirEntry *dir) 16379455Sobrien{ 16479455Sobrien static char namebuf[MAXPATHLEN + 1]; 16579455Sobrien char *cp, *np; 16679455Sobrien int nl; 16779455Sobrien 16879455Sobrien cp = namebuf + sizeof namebuf - 1; 16979455Sobrien *cp = '\0'; 17079455Sobrien do { 17179455Sobrien np = dir->lname[0] ? dir->lname : dir->name; 17279455Sobrien nl = strlen(np); 17379455Sobrien if ((cp -= nl) <= namebuf + 1) 17479455Sobrien break; 17579455Sobrien memcpy(cp, np, nl); 17679455Sobrien *--cp = '/'; 17779455Sobrien } while ((dir = dir->parent) != NULL); 17879455Sobrien if (dir) 17979455Sobrien *--cp = '?'; 18079455Sobrien else 18179455Sobrien cp++; 18279455Sobrien return cp; 18379455Sobrien} 18479455Sobrien 18579455Sobrien/* 18679455Sobrien * Calculate a checksum over an 8.3 alias name 18779455Sobrien */ 18879455Sobrienstatic u_char 18992839SimpcalcShortSum(u_char *p) 19079455Sobrien{ 19179455Sobrien u_char sum = 0; 19279455Sobrien int i; 19379455Sobrien 19479455Sobrien for (i = 0; i < 11; i++) { 19579455Sobrien sum = (sum << 7)|(sum >> 1); /* rotate right */ 19679455Sobrien sum += p[i]; 19779455Sobrien } 19879455Sobrien 19979455Sobrien return sum; 20079455Sobrien} 20179455Sobrien 20279455Sobrien/* 20379455Sobrien * Global variables temporarily used during a directory scan 20479455Sobrien */ 20579455Sobrienstatic char longName[DOSLONGNAMELEN] = ""; 20679455Sobrienstatic u_char *buffer = NULL; 20779455Sobrienstatic u_char *delbuf = NULL; 20879455Sobrien 20979455Sobrienstruct dosDirEntry *rootDir; 21079455Sobrienstatic struct dosDirEntry *lostDir; 21179455Sobrien 21279455Sobrien/* 21379455Sobrien * Init internal state for a new directory scan. 21479455Sobrien */ 21579455Sobrienint 21692839SimpresetDosDirSection(struct bootblock *boot, struct fatEntry *fat) 21779455Sobrien{ 21879455Sobrien int b1, b2; 21979455Sobrien cl_t cl; 22079455Sobrien int ret = FSOK; 221241806Suqs size_t len; 22279455Sobrien 223203874Skib b1 = boot->bpbRootDirEnts * 32; 224203874Skib b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec; 22579455Sobrien 226241806Suqs if ((buffer = malloc(len = b1 > b2 ? b1 : b2)) == NULL) { 227241806Suqs perr("No space for directory buffer (%zu)", len); 22879455Sobrien return FSFATAL; 22979455Sobrien } 230203872Skib 231241806Suqs if ((delbuf = malloc(len = b2)) == NULL) { 232203872Skib free(buffer); 233241806Suqs perr("No space for directory delbuf (%zu)", len); 234203872Skib return FSFATAL; 235203872Skib } 236203872Skib 237203872Skib if ((rootDir = newDosDirEntry()) == NULL) { 238203872Skib free(buffer); 239203872Skib free(delbuf); 240241806Suqs perr("No space for directory entry"); 241203872Skib return FSFATAL; 242203872Skib } 243203872Skib 24479455Sobrien memset(rootDir, 0, sizeof *rootDir); 24579455Sobrien if (boot->flags & FAT32) { 246209364Sbrian if (boot->bpbRootClust < CLUST_FIRST || 247209364Sbrian boot->bpbRootClust >= boot->NumClusters) { 24879455Sobrien pfatal("Root directory starts with cluster out of range(%u)", 249203874Skib boot->bpbRootClust); 25079455Sobrien return FSFATAL; 25179455Sobrien } 252203874Skib cl = fat[boot->bpbRootClust].next; 25379455Sobrien if (cl < CLUST_FIRST 25479455Sobrien || (cl >= CLUST_RSRVD && cl< CLUST_EOFS) 255203874Skib || fat[boot->bpbRootClust].head != boot->bpbRootClust) { 25679455Sobrien if (cl == CLUST_FREE) 25779455Sobrien pwarn("Root directory starts with free cluster\n"); 25879455Sobrien else if (cl >= CLUST_RSRVD) 25979455Sobrien pwarn("Root directory starts with cluster marked %s\n", 26079455Sobrien rsrvdcltype(cl)); 26179455Sobrien else { 26279455Sobrien pfatal("Root directory doesn't start a cluster chain"); 26379455Sobrien return FSFATAL; 26479455Sobrien } 26579455Sobrien if (ask(1, "Fix")) { 266203874Skib fat[boot->bpbRootClust].next = CLUST_FREE; 26779455Sobrien ret = FSFATMOD; 26879455Sobrien } else 26979455Sobrien ret = FSFATAL; 27079455Sobrien } 27179455Sobrien 272203874Skib fat[boot->bpbRootClust].flags |= FAT_USED; 273203874Skib rootDir->head = boot->bpbRootClust; 27479455Sobrien } 27579455Sobrien 27679455Sobrien return ret; 27779455Sobrien} 27879455Sobrien 27979455Sobrien/* 28079455Sobrien * Cleanup after a directory scan 28179455Sobrien */ 28279455Sobrienvoid 28392839SimpfinishDosDirSection(void) 28479455Sobrien{ 28579455Sobrien struct dirTodoNode *p, *np; 28679455Sobrien struct dosDirEntry *d, *nd; 28779455Sobrien 28879455Sobrien for (p = pendingDirectories; p; p = np) { 28979455Sobrien np = p->next; 29079455Sobrien freeDirTodo(p); 29179455Sobrien } 29279455Sobrien pendingDirectories = 0; 29379455Sobrien for (d = rootDir; d; d = nd) { 29479455Sobrien if ((nd = d->child) != NULL) { 29579455Sobrien d->child = 0; 29679455Sobrien continue; 29779455Sobrien } 29879455Sobrien if (!(nd = d->next)) 29979455Sobrien nd = d->parent; 30079455Sobrien freeDosDirEntry(d); 30179455Sobrien } 30279455Sobrien rootDir = lostDir = NULL; 30379455Sobrien free(buffer); 30479455Sobrien free(delbuf); 30579455Sobrien buffer = NULL; 30679455Sobrien delbuf = NULL; 30779455Sobrien} 30879455Sobrien 30979455Sobrien/* 31079455Sobrien * Delete directory entries between startcl, startoff and endcl, endoff. 31179455Sobrien */ 31279455Sobrienstatic int 31392839Simpdelete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl, 31492839Simp int startoff, cl_t endcl, int endoff, int notlast) 31579455Sobrien{ 31679455Sobrien u_char *s, *e; 31779455Sobrien off_t off; 318203874Skib int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec; 31979455Sobrien 32079455Sobrien s = delbuf + startoff; 32179455Sobrien e = delbuf + clsz; 32279455Sobrien while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) { 32379455Sobrien if (startcl == endcl) { 32479455Sobrien if (notlast) 32579455Sobrien break; 32679455Sobrien e = delbuf + endoff; 32779455Sobrien } 328203874Skib off = startcl * boot->bpbSecPerClust + boot->ClusterOffset; 329203874Skib off *= boot->bpbBytesPerSec; 33079455Sobrien if (lseek(f, off, SEEK_SET) != off 33179455Sobrien || read(f, delbuf, clsz) != clsz) { 332241806Suqs perr("Unable to read directory"); 33379455Sobrien return FSFATAL; 33479455Sobrien } 33579455Sobrien while (s < e) { 33679455Sobrien *s = SLOT_DELETED; 33779455Sobrien s += 32; 33879455Sobrien } 33979455Sobrien if (lseek(f, off, SEEK_SET) != off 34079455Sobrien || write(f, delbuf, clsz) != clsz) { 341241806Suqs perr("Unable to write directory"); 34279455Sobrien return FSFATAL; 34379455Sobrien } 34479455Sobrien if (startcl == endcl) 34579455Sobrien break; 34679455Sobrien startcl = fat[startcl].next; 34779455Sobrien s = delbuf; 34879455Sobrien } 34979455Sobrien return FSOK; 35079455Sobrien} 35179455Sobrien 35279455Sobrienstatic int 35392839Simpremovede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start, 35492839Simp u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type) 35579455Sobrien{ 35679455Sobrien switch (type) { 35779455Sobrien case 0: 35879455Sobrien pwarn("Invalid long filename entry for %s\n", path); 35979455Sobrien break; 36079455Sobrien case 1: 361209364Sbrian pwarn("Invalid long filename entry at end of directory %s\n", 362209364Sbrian path); 36379455Sobrien break; 36479455Sobrien case 2: 36579455Sobrien pwarn("Invalid long filename entry for volume label\n"); 36679455Sobrien break; 36779455Sobrien } 36879455Sobrien if (ask(0, "Remove")) { 36979455Sobrien if (startcl != curcl) { 37079455Sobrien if (delete(f, boot, fat, 37179455Sobrien startcl, start - buffer, 37279455Sobrien endcl, end - buffer, 37379455Sobrien endcl == curcl) == FSFATAL) 37479455Sobrien return FSFATAL; 37579455Sobrien start = buffer; 37679455Sobrien } 377203872Skib /* startcl is < CLUST_FIRST for !fat32 root */ 378203872Skib if ((endcl == curcl) || (startcl < CLUST_FIRST)) 37979455Sobrien for (; start < end; start += 32) 38079455Sobrien *start = SLOT_DELETED; 38179455Sobrien return FSDIRMOD; 38279455Sobrien } 38379455Sobrien return FSERROR; 38479455Sobrien} 38579455Sobrien 38679455Sobrien/* 38779455Sobrien * Check an in-memory file entry 38879455Sobrien */ 38979455Sobrienstatic int 39092839Simpchecksize(struct bootblock *boot, struct fatEntry *fat, u_char *p, 39192839Simp struct dosDirEntry *dir) 39279455Sobrien{ 39379455Sobrien /* 39479455Sobrien * Check size on ordinary files 39579455Sobrien */ 396203872Skib u_int32_t physicalSize; 39779455Sobrien 39879455Sobrien if (dir->head == CLUST_FREE) 39979455Sobrien physicalSize = 0; 40079455Sobrien else { 40179455Sobrien if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters) 40279455Sobrien return FSERROR; 40379455Sobrien physicalSize = fat[dir->head].length * boot->ClusterSize; 40479455Sobrien } 40579455Sobrien if (physicalSize < dir->size) { 40679455Sobrien pwarn("size of %s is %u, should at most be %u\n", 40779455Sobrien fullpath(dir), dir->size, physicalSize); 40879455Sobrien if (ask(1, "Truncate")) { 40979455Sobrien dir->size = physicalSize; 41079455Sobrien p[28] = (u_char)physicalSize; 41179455Sobrien p[29] = (u_char)(physicalSize >> 8); 41279455Sobrien p[30] = (u_char)(physicalSize >> 16); 41379455Sobrien p[31] = (u_char)(physicalSize >> 24); 41479455Sobrien return FSDIRMOD; 41579455Sobrien } else 41679455Sobrien return FSERROR; 41779455Sobrien } else if (physicalSize - dir->size >= boot->ClusterSize) { 41879455Sobrien pwarn("%s has too many clusters allocated\n", 41979455Sobrien fullpath(dir)); 42079455Sobrien if (ask(1, "Drop superfluous clusters")) { 42179455Sobrien cl_t cl; 422268968Spfg u_int32_t sz, len; 42379455Sobrien 424268968Spfg for (cl = dir->head, len = sz = 0; 425268968Spfg (sz += boot->ClusterSize) < dir->size; len++) 42679455Sobrien cl = fat[cl].next; 42779455Sobrien clearchain(boot, fat, fat[cl].next); 42879455Sobrien fat[cl].next = CLUST_EOF; 429268968Spfg fat[dir->head].length = len; 43079455Sobrien return FSFATMOD; 43179455Sobrien } else 43279455Sobrien return FSERROR; 43379455Sobrien } 43479455Sobrien return FSOK; 43579455Sobrien} 43679455Sobrien 43779455Sobrien/* 43879455Sobrien * Read a directory and 43979455Sobrien * - resolve long name records 44079455Sobrien * - enter file and directory records into the parent's list 44179455Sobrien * - push directories onto the todo-stack 44279455Sobrien */ 44379455Sobrienstatic int 44492839SimpreadDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat, 44592839Simp struct dosDirEntry *dir) 44679455Sobrien{ 44779455Sobrien struct dosDirEntry dirent, *d; 44879455Sobrien u_char *p, *vallfn, *invlfn, *empty; 44979455Sobrien off_t off; 45079455Sobrien int i, j, k, last; 45179455Sobrien cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; 45279455Sobrien char *t; 45379455Sobrien u_int lidx = 0; 45479455Sobrien int shortSum; 45579455Sobrien int mod = FSOK; 45679455Sobrien#define THISMOD 0x8000 /* Only used within this routine */ 45779455Sobrien 45879455Sobrien cl = dir->head; 45979455Sobrien if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { 46079455Sobrien /* 46179455Sobrien * Already handled somewhere else. 46279455Sobrien */ 46379455Sobrien return FSOK; 46479455Sobrien } 46579455Sobrien shortSum = -1; 46679455Sobrien vallfn = invlfn = empty = NULL; 46779455Sobrien do { 46879455Sobrien if (!(boot->flags & FAT32) && !dir->parent) { 469203874Skib last = boot->bpbRootDirEnts * 32; 470209364Sbrian off = boot->bpbResSectors + boot->bpbFATs * 471209364Sbrian boot->FATsecs; 47279455Sobrien } else { 473203874Skib last = boot->bpbSecPerClust * boot->bpbBytesPerSec; 474203874Skib off = cl * boot->bpbSecPerClust + boot->ClusterOffset; 47579455Sobrien } 47679455Sobrien 477203874Skib off *= boot->bpbBytesPerSec; 47879455Sobrien if (lseek(f, off, SEEK_SET) != off 47979455Sobrien || read(f, buffer, last) != last) { 480241806Suqs perr("Unable to read directory"); 48179455Sobrien return FSFATAL; 48279455Sobrien } 48379455Sobrien last /= 32; 48479455Sobrien /* 48579455Sobrien * Check `.' and `..' entries here? XXX 48679455Sobrien */ 48779455Sobrien for (p = buffer, i = 0; i < last; i++, p += 32) { 48879455Sobrien if (dir->fsckflags & DIREMPWARN) { 48979455Sobrien *p = SLOT_EMPTY; 49079455Sobrien continue; 49179455Sobrien } 49279455Sobrien 49379455Sobrien if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { 49479455Sobrien if (*p == SLOT_EMPTY) { 49579455Sobrien dir->fsckflags |= DIREMPTY; 49679455Sobrien empty = p; 49779455Sobrien empcl = cl; 49879455Sobrien } 49979455Sobrien continue; 50079455Sobrien } 50179455Sobrien 50279455Sobrien if (dir->fsckflags & DIREMPTY) { 50379455Sobrien if (!(dir->fsckflags & DIREMPWARN)) { 50479455Sobrien pwarn("%s has entries after end of directory\n", 50579455Sobrien fullpath(dir)); 50679455Sobrien if (ask(1, "Extend")) { 50779455Sobrien u_char *q; 50879455Sobrien 50979455Sobrien dir->fsckflags &= ~DIREMPTY; 51079455Sobrien if (delete(f, boot, fat, 51179455Sobrien empcl, empty - buffer, 51279455Sobrien cl, p - buffer, 1) == FSFATAL) 51379455Sobrien return FSFATAL; 51479455Sobrien q = empcl == cl ? empty : buffer; 51579455Sobrien for (; q < p; q += 32) 51679455Sobrien *q = SLOT_DELETED; 51779455Sobrien mod |= THISMOD|FSDIRMOD; 51879455Sobrien } else if (ask(0, "Truncate")) 51979455Sobrien dir->fsckflags |= DIREMPWARN; 52079455Sobrien } 52179455Sobrien if (dir->fsckflags & DIREMPWARN) { 52279455Sobrien *p = SLOT_DELETED; 52379455Sobrien mod |= THISMOD|FSDIRMOD; 52479455Sobrien continue; 52579455Sobrien } else if (dir->fsckflags & DIREMPTY) 52679455Sobrien mod |= FSERROR; 52779455Sobrien empty = NULL; 52879455Sobrien } 52979455Sobrien 53079455Sobrien if (p[11] == ATTR_WIN95) { 53179455Sobrien if (*p & LRFIRST) { 53279455Sobrien if (shortSum != -1) { 53379455Sobrien if (!invlfn) { 53479455Sobrien invlfn = vallfn; 53579455Sobrien invcl = valcl; 53679455Sobrien } 53779455Sobrien } 53879455Sobrien memset(longName, 0, sizeof longName); 53979455Sobrien shortSum = p[13]; 54079455Sobrien vallfn = p; 54179455Sobrien valcl = cl; 54279455Sobrien } else if (shortSum != p[13] 54379455Sobrien || lidx != (*p & LRNOMASK)) { 54479455Sobrien if (!invlfn) { 54579455Sobrien invlfn = vallfn; 54679455Sobrien invcl = valcl; 54779455Sobrien } 54879455Sobrien if (!invlfn) { 54979455Sobrien invlfn = p; 55079455Sobrien invcl = cl; 55179455Sobrien } 55279455Sobrien vallfn = NULL; 55379455Sobrien } 55479455Sobrien lidx = *p & LRNOMASK; 55579455Sobrien t = longName + --lidx * 13; 556209364Sbrian for (k = 1; k < 11 && t < longName + 557209364Sbrian sizeof(longName); k += 2) { 55879455Sobrien if (!p[k] && !p[k + 1]) 55979455Sobrien break; 56079455Sobrien *t++ = p[k]; 56179455Sobrien /* 56279455Sobrien * Warn about those unusable chars in msdosfs here? XXX 56379455Sobrien */ 56479455Sobrien if (p[k + 1]) 56579455Sobrien t[-1] = '?'; 56679455Sobrien } 56779455Sobrien if (k >= 11) 56879455Sobrien for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) { 56979455Sobrien if (!p[k] && !p[k + 1]) 57079455Sobrien break; 57179455Sobrien *t++ = p[k]; 57279455Sobrien if (p[k + 1]) 57379455Sobrien t[-1] = '?'; 57479455Sobrien } 57579455Sobrien if (k >= 26) 57679455Sobrien for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { 57779455Sobrien if (!p[k] && !p[k + 1]) 57879455Sobrien break; 57979455Sobrien *t++ = p[k]; 58079455Sobrien if (p[k + 1]) 58179455Sobrien t[-1] = '?'; 58279455Sobrien } 58379455Sobrien if (t >= longName + sizeof(longName)) { 58479455Sobrien pwarn("long filename too long\n"); 58579455Sobrien if (!invlfn) { 58679455Sobrien invlfn = vallfn; 58779455Sobrien invcl = valcl; 58879455Sobrien } 58979455Sobrien vallfn = NULL; 59079455Sobrien } 59179455Sobrien if (p[26] | (p[27] << 8)) { 59279455Sobrien pwarn("long filename record cluster start != 0\n"); 59379455Sobrien if (!invlfn) { 59479455Sobrien invlfn = vallfn; 59579455Sobrien invcl = cl; 59679455Sobrien } 59779455Sobrien vallfn = NULL; 59879455Sobrien } 59979455Sobrien continue; /* long records don't carry further 60079455Sobrien * information */ 60179455Sobrien } 60279455Sobrien 60379455Sobrien /* 60479455Sobrien * This is a standard msdosfs directory entry. 60579455Sobrien */ 60679455Sobrien memset(&dirent, 0, sizeof dirent); 60779455Sobrien 60879455Sobrien /* 60979455Sobrien * it's a short name record, but we need to know 61079455Sobrien * more, so get the flags first. 61179455Sobrien */ 61279455Sobrien dirent.flags = p[11]; 61379455Sobrien 61479455Sobrien /* 61579455Sobrien * Translate from 850 to ISO here XXX 61679455Sobrien */ 61779455Sobrien for (j = 0; j < 8; j++) 61879455Sobrien dirent.name[j] = p[j]; 61979455Sobrien dirent.name[8] = '\0'; 62079455Sobrien for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) 62179455Sobrien dirent.name[k] = '\0'; 62279455Sobrien if (dirent.name[k] != '\0') 62379455Sobrien k++; 62479455Sobrien if (dirent.name[0] == SLOT_E5) 62579455Sobrien dirent.name[0] = 0xe5; 62679455Sobrien 62779455Sobrien if (dirent.flags & ATTR_VOLUME) { 62879455Sobrien if (vallfn || invlfn) { 62979455Sobrien mod |= removede(f, boot, fat, 63079455Sobrien invlfn ? invlfn : vallfn, p, 63179455Sobrien invlfn ? invcl : valcl, -1, 0, 63279455Sobrien fullpath(dir), 2); 63379455Sobrien vallfn = NULL; 63479455Sobrien invlfn = NULL; 63579455Sobrien } 63679455Sobrien continue; 63779455Sobrien } 63879455Sobrien 63979455Sobrien if (p[8] != ' ') 64079455Sobrien dirent.name[k++] = '.'; 64179455Sobrien for (j = 0; j < 3; j++) 64279455Sobrien dirent.name[k++] = p[j+8]; 64379455Sobrien dirent.name[k] = '\0'; 64479455Sobrien for (k--; k >= 0 && dirent.name[k] == ' '; k--) 64579455Sobrien dirent.name[k] = '\0'; 64679455Sobrien 64779455Sobrien if (vallfn && shortSum != calcShortSum(p)) { 64879455Sobrien if (!invlfn) { 64979455Sobrien invlfn = vallfn; 65079455Sobrien invcl = valcl; 65179455Sobrien } 65279455Sobrien vallfn = NULL; 65379455Sobrien } 65479455Sobrien dirent.head = p[26] | (p[27] << 8); 65579455Sobrien if (boot->ClustMask == CLUST32_MASK) 65679455Sobrien dirent.head |= (p[20] << 16) | (p[21] << 24); 65779455Sobrien dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); 65879455Sobrien if (vallfn) { 659203872Skib strlcpy(dirent.lname, longName, 660203872Skib sizeof(dirent.lname)); 66179455Sobrien longName[0] = '\0'; 66279455Sobrien shortSum = -1; 66379455Sobrien } 66479455Sobrien 66579455Sobrien dirent.parent = dir; 66679455Sobrien dirent.next = dir->child; 66779455Sobrien 66879455Sobrien if (invlfn) { 66979455Sobrien mod |= k = removede(f, boot, fat, 67079455Sobrien invlfn, vallfn ? vallfn : p, 67179455Sobrien invcl, vallfn ? valcl : cl, cl, 67279455Sobrien fullpath(&dirent), 0); 67379455Sobrien if (mod & FSFATAL) 67479455Sobrien return FSFATAL; 67579455Sobrien if (vallfn 67679455Sobrien ? (valcl == cl && vallfn != buffer) 67779455Sobrien : p != buffer) 67879455Sobrien if (k & FSDIRMOD) 67979455Sobrien mod |= THISMOD; 68079455Sobrien } 68179455Sobrien 68279455Sobrien vallfn = NULL; /* not used any longer */ 68379455Sobrien invlfn = NULL; 68479455Sobrien 68579455Sobrien if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) { 68679455Sobrien if (dirent.head != 0) { 68779455Sobrien pwarn("%s has clusters, but size 0\n", 68879455Sobrien fullpath(&dirent)); 68979455Sobrien if (ask(1, "Drop allocated clusters")) { 69079455Sobrien p[26] = p[27] = 0; 69179455Sobrien if (boot->ClustMask == CLUST32_MASK) 69279455Sobrien p[20] = p[21] = 0; 69379455Sobrien clearchain(boot, fat, dirent.head); 69479455Sobrien dirent.head = 0; 69579455Sobrien mod |= THISMOD|FSDIRMOD|FSFATMOD; 69679455Sobrien } else 69779455Sobrien mod |= FSERROR; 69879455Sobrien } 69979455Sobrien } else if (dirent.head == 0 70079455Sobrien && !strcmp(dirent.name, "..") 70179455Sobrien && dir->parent /* XXX */ 70279455Sobrien && !dir->parent->parent) { 70379455Sobrien /* 70479455Sobrien * Do nothing, the parent is the root 70579455Sobrien */ 70679455Sobrien } else if (dirent.head < CLUST_FIRST 70779455Sobrien || dirent.head >= boot->NumClusters 70879455Sobrien || fat[dirent.head].next == CLUST_FREE 70979455Sobrien || (fat[dirent.head].next >= CLUST_RSRVD 71079455Sobrien && fat[dirent.head].next < CLUST_EOFS) 71179455Sobrien || fat[dirent.head].head != dirent.head) { 71279455Sobrien if (dirent.head == 0) 71379455Sobrien pwarn("%s has no clusters\n", 71479455Sobrien fullpath(&dirent)); 71579455Sobrien else if (dirent.head < CLUST_FIRST 71679455Sobrien || dirent.head >= boot->NumClusters) 71779455Sobrien pwarn("%s starts with cluster out of range(%u)\n", 71879455Sobrien fullpath(&dirent), 71979455Sobrien dirent.head); 72079455Sobrien else if (fat[dirent.head].next == CLUST_FREE) 72179455Sobrien pwarn("%s starts with free cluster\n", 72279455Sobrien fullpath(&dirent)); 72379455Sobrien else if (fat[dirent.head].next >= CLUST_RSRVD) 72479455Sobrien pwarn("%s starts with cluster marked %s\n", 72579455Sobrien fullpath(&dirent), 72679455Sobrien rsrvdcltype(fat[dirent.head].next)); 72779455Sobrien else 72879455Sobrien pwarn("%s doesn't start a cluster chain\n", 72979455Sobrien fullpath(&dirent)); 73079455Sobrien if (dirent.flags & ATTR_DIRECTORY) { 73179455Sobrien if (ask(0, "Remove")) { 73279455Sobrien *p = SLOT_DELETED; 73379455Sobrien mod |= THISMOD|FSDIRMOD; 73479455Sobrien } else 73579455Sobrien mod |= FSERROR; 73679455Sobrien continue; 73779455Sobrien } else { 73879455Sobrien if (ask(1, "Truncate")) { 73979455Sobrien p[28] = p[29] = p[30] = p[31] = 0; 74079455Sobrien p[26] = p[27] = 0; 74179455Sobrien if (boot->ClustMask == CLUST32_MASK) 74279455Sobrien p[20] = p[21] = 0; 74379455Sobrien dirent.size = 0; 74479455Sobrien mod |= THISMOD|FSDIRMOD; 74579455Sobrien } else 74679455Sobrien mod |= FSERROR; 74779455Sobrien } 74879455Sobrien } 74979455Sobrien 75079455Sobrien if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters) 75179455Sobrien fat[dirent.head].flags |= FAT_USED; 75279455Sobrien 75379455Sobrien if (dirent.flags & ATTR_DIRECTORY) { 75479455Sobrien /* 75579455Sobrien * gather more info for directories 75679455Sobrien */ 75779455Sobrien struct dirTodoNode *n; 75879455Sobrien 75979455Sobrien if (dirent.size) { 76079455Sobrien pwarn("Directory %s has size != 0\n", 76179455Sobrien fullpath(&dirent)); 76279455Sobrien if (ask(1, "Correct")) { 76379455Sobrien p[28] = p[29] = p[30] = p[31] = 0; 76479455Sobrien dirent.size = 0; 76579455Sobrien mod |= THISMOD|FSDIRMOD; 76679455Sobrien } else 76779455Sobrien mod |= FSERROR; 76879455Sobrien } 76979455Sobrien /* 77079455Sobrien * handle `.' and `..' specially 77179455Sobrien */ 77279455Sobrien if (strcmp(dirent.name, ".") == 0) { 77379455Sobrien if (dirent.head != dir->head) { 77479455Sobrien pwarn("`.' entry in %s has incorrect start cluster\n", 77579455Sobrien fullpath(dir)); 77679455Sobrien if (ask(1, "Correct")) { 77779455Sobrien dirent.head = dir->head; 77879455Sobrien p[26] = (u_char)dirent.head; 77979455Sobrien p[27] = (u_char)(dirent.head >> 8); 78079455Sobrien if (boot->ClustMask == CLUST32_MASK) { 78179455Sobrien p[20] = (u_char)(dirent.head >> 16); 78279455Sobrien p[21] = (u_char)(dirent.head >> 24); 78379455Sobrien } 78479455Sobrien mod |= THISMOD|FSDIRMOD; 78579455Sobrien } else 78679455Sobrien mod |= FSERROR; 78779455Sobrien } 78879455Sobrien continue; 78979455Sobrien } 79079455Sobrien if (strcmp(dirent.name, "..") == 0) { 79179455Sobrien if (dir->parent) { /* XXX */ 79279455Sobrien if (!dir->parent->parent) { 79379455Sobrien if (dirent.head) { 79479455Sobrien pwarn("`..' entry in %s has non-zero start cluster\n", 79579455Sobrien fullpath(dir)); 79679455Sobrien if (ask(1, "Correct")) { 79779455Sobrien dirent.head = 0; 79879455Sobrien p[26] = p[27] = 0; 79979455Sobrien if (boot->ClustMask == CLUST32_MASK) 80079455Sobrien p[20] = p[21] = 0; 80179455Sobrien mod |= THISMOD|FSDIRMOD; 80279455Sobrien } else 80379455Sobrien mod |= FSERROR; 80479455Sobrien } 80579455Sobrien } else if (dirent.head != dir->parent->head) { 80679455Sobrien pwarn("`..' entry in %s has incorrect start cluster\n", 80779455Sobrien fullpath(dir)); 80879455Sobrien if (ask(1, "Correct")) { 80979455Sobrien dirent.head = dir->parent->head; 81079455Sobrien p[26] = (u_char)dirent.head; 81179455Sobrien p[27] = (u_char)(dirent.head >> 8); 81279455Sobrien if (boot->ClustMask == CLUST32_MASK) { 81379455Sobrien p[20] = (u_char)(dirent.head >> 16); 81479455Sobrien p[21] = (u_char)(dirent.head >> 24); 81579455Sobrien } 81679455Sobrien mod |= THISMOD|FSDIRMOD; 81779455Sobrien } else 81879455Sobrien mod |= FSERROR; 81979455Sobrien } 82079455Sobrien } 82179455Sobrien continue; 82279455Sobrien } 82379455Sobrien 82479455Sobrien /* create directory tree node */ 82579455Sobrien if (!(d = newDosDirEntry())) { 826241806Suqs perr("No space for directory"); 82779455Sobrien return FSFATAL; 82879455Sobrien } 82979455Sobrien memcpy(d, &dirent, sizeof(struct dosDirEntry)); 83079455Sobrien /* link it into the tree */ 83179455Sobrien dir->child = d; 83279455Sobrien 83379455Sobrien /* Enter this directory into the todo list */ 83479455Sobrien if (!(n = newDirTodo())) { 835241806Suqs perr("No space for todo list"); 83679455Sobrien return FSFATAL; 83779455Sobrien } 83879455Sobrien n->next = pendingDirectories; 83979455Sobrien n->dir = d; 84079455Sobrien pendingDirectories = n; 84179455Sobrien } else { 84279455Sobrien mod |= k = checksize(boot, fat, p, &dirent); 84379455Sobrien if (k & FSDIRMOD) 84479455Sobrien mod |= THISMOD; 84579455Sobrien } 84679455Sobrien boot->NumFiles++; 84779455Sobrien } 848203872Skib 849203872Skib if (!(boot->flags & FAT32) && !dir->parent) 850203872Skib break; 851203872Skib 85279455Sobrien if (mod & THISMOD) { 85379455Sobrien last *= 32; 85479455Sobrien if (lseek(f, off, SEEK_SET) != off 85579455Sobrien || write(f, buffer, last) != last) { 856241806Suqs perr("Unable to write directory"); 85779455Sobrien return FSFATAL; 85879455Sobrien } 85979455Sobrien mod &= ~THISMOD; 86079455Sobrien } 86179455Sobrien } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters); 86279455Sobrien if (invlfn || vallfn) 86379455Sobrien mod |= removede(f, boot, fat, 86479455Sobrien invlfn ? invlfn : vallfn, p, 86579455Sobrien invlfn ? invcl : valcl, -1, 0, 86679455Sobrien fullpath(dir), 1); 867203872Skib 868203872Skib /* The root directory of non fat32 filesystems is in a special 869203872Skib * area and may have been modified above without being written out. 870203872Skib */ 871203872Skib if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) { 872203872Skib last *= 32; 873203872Skib if (lseek(f, off, SEEK_SET) != off 874203872Skib || write(f, buffer, last) != last) { 875241806Suqs perr("Unable to write directory"); 876203872Skib return FSFATAL; 877203872Skib } 878203872Skib mod &= ~THISMOD; 879203872Skib } 88079455Sobrien return mod & ~THISMOD; 88179455Sobrien} 88279455Sobrien 88379455Sobrienint 88492839SimphandleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat) 88579455Sobrien{ 88679455Sobrien int mod; 88779455Sobrien 88879455Sobrien mod = readDosDirSection(dosfs, boot, fat, rootDir); 88979455Sobrien if (mod & FSFATAL) 89079455Sobrien return FSFATAL; 89179455Sobrien 89279455Sobrien /* 89379455Sobrien * process the directory todo list 89479455Sobrien */ 89579455Sobrien while (pendingDirectories) { 89679455Sobrien struct dosDirEntry *dir = pendingDirectories->dir; 89779455Sobrien struct dirTodoNode *n = pendingDirectories->next; 89879455Sobrien 89979455Sobrien /* 90079455Sobrien * remove TODO entry now, the list might change during 90179455Sobrien * directory reads 90279455Sobrien */ 90379455Sobrien freeDirTodo(pendingDirectories); 90479455Sobrien pendingDirectories = n; 90579455Sobrien 90679455Sobrien /* 90779455Sobrien * handle subdirectory 90879455Sobrien */ 90979455Sobrien mod |= readDosDirSection(dosfs, boot, fat, dir); 91079455Sobrien if (mod & FSFATAL) 91179455Sobrien return FSFATAL; 91279455Sobrien } 91379455Sobrien 91479455Sobrien return mod; 91579455Sobrien} 91679455Sobrien 91779455Sobrien/* 91879455Sobrien * Try to reconnect a FAT chain into dir 91979455Sobrien */ 92079455Sobrienstatic u_char *lfbuf; 92179455Sobrienstatic cl_t lfcl; 92279455Sobrienstatic off_t lfoff; 92379455Sobrien 92479455Sobrienint 92592839Simpreconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head) 92679455Sobrien{ 92779455Sobrien struct dosDirEntry d; 92879455Sobrien u_char *p; 92979455Sobrien 93079455Sobrien if (!ask(1, "Reconnect")) 93179455Sobrien return FSERROR; 93279455Sobrien 93379455Sobrien if (!lostDir) { 93479455Sobrien for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) { 93579455Sobrien if (!strcmp(lostDir->name, LOSTDIR)) 93679455Sobrien break; 93779455Sobrien } 93879455Sobrien if (!lostDir) { /* Create LOSTDIR? XXX */ 93979455Sobrien pwarn("No %s directory\n", LOSTDIR); 94079455Sobrien return FSERROR; 94179455Sobrien } 94279455Sobrien } 94379455Sobrien if (!lfbuf) { 94479455Sobrien lfbuf = malloc(boot->ClusterSize); 94579455Sobrien if (!lfbuf) { 946241806Suqs perr("No space for buffer"); 94779455Sobrien return FSFATAL; 94879455Sobrien } 94979455Sobrien p = NULL; 95079455Sobrien } else 95179455Sobrien p = lfbuf; 95279455Sobrien while (1) { 95379455Sobrien if (p) 95479455Sobrien for (; p < lfbuf + boot->ClusterSize; p += 32) 95579455Sobrien if (*p == SLOT_EMPTY 95679455Sobrien || *p == SLOT_DELETED) 95779455Sobrien break; 95879455Sobrien if (p && p < lfbuf + boot->ClusterSize) 95979455Sobrien break; 96079455Sobrien lfcl = p ? fat[lfcl].next : lostDir->head; 96179455Sobrien if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) { 96279455Sobrien /* Extend LOSTDIR? XXX */ 96379455Sobrien pwarn("No space in %s\n", LOSTDIR); 96479455Sobrien return FSERROR; 96579455Sobrien } 96679455Sobrien lfoff = lfcl * boot->ClusterSize 967203874Skib + boot->ClusterOffset * boot->bpbBytesPerSec; 96879455Sobrien if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 969203872Skib || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 970241806Suqs perr("could not read LOST.DIR"); 97179455Sobrien return FSFATAL; 97279455Sobrien } 97379455Sobrien p = lfbuf; 97479455Sobrien } 97579455Sobrien 97679455Sobrien boot->NumFiles++; 97779455Sobrien /* Ensure uniqueness of entry here! XXX */ 97879455Sobrien memset(&d, 0, sizeof d); 97979455Sobrien (void)snprintf(d.name, sizeof(d.name), "%u", head); 98079455Sobrien d.flags = 0; 98179455Sobrien d.head = head; 98279455Sobrien d.size = fat[head].length * boot->ClusterSize; 98379455Sobrien 98479455Sobrien memset(p, 0, 32); 98579455Sobrien memset(p, ' ', 11); 98679455Sobrien memcpy(p, d.name, strlen(d.name)); 98779455Sobrien p[26] = (u_char)d.head; 98879455Sobrien p[27] = (u_char)(d.head >> 8); 98979455Sobrien if (boot->ClustMask == CLUST32_MASK) { 99079455Sobrien p[20] = (u_char)(d.head >> 16); 99179455Sobrien p[21] = (u_char)(d.head >> 24); 99279455Sobrien } 99379455Sobrien p[28] = (u_char)d.size; 99479455Sobrien p[29] = (u_char)(d.size >> 8); 99579455Sobrien p[30] = (u_char)(d.size >> 16); 99679455Sobrien p[31] = (u_char)(d.size >> 24); 99779455Sobrien fat[head].flags |= FAT_USED; 99879455Sobrien if (lseek(dosfs, lfoff, SEEK_SET) != lfoff 999203872Skib || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { 1000241806Suqs perr("could not write LOST.DIR"); 100179455Sobrien return FSFATAL; 100279455Sobrien } 100379455Sobrien return FSDIRMOD; 100479455Sobrien} 100579455Sobrien 100679455Sobrienvoid 100792839Simpfinishlf(void) 100879455Sobrien{ 100979455Sobrien if (lfbuf) 101079455Sobrien free(lfbuf); 101179455Sobrien lfbuf = NULL; 101279455Sobrien} 1013