Undelete or file recovery software?

Started by barcher174, May 09, 2015, 12:22:10 am

Previous topic - Next topic


Does there exist any kind of undelete software for nextstep?



Hi Brian,
the saying in comp.sys.next usegroups was that it is not possible in a senseful way to recover files on NeXTstep (UNIX).
Some examples:
Undelete utility needed
UNDELETE? (directory is now lost!)

But in 1991 it was Mike Morton who has posted a recovery program into the public domain:
Recovering deleted files: saga and code

Please read the whole posting and take Mike's warning serious:
"Here's the program.  Use it as you see fit, but make sure you know what
you're doing, since this is currently a completely special-purpose,
one-night job.  No idea how it will work on any other machines, but
I'm placing it in the public domain in hopes that someone will think
about writing a better scavenger."

Cheers, Nuss


Mike's original code:

/*  Recover unallocated blocks from a file system and store them
   in a specified directory, which should not be on the same file
   system.  We attempt to select only blocks with ASCII; others
   might save everything, or have another criterion.

   No guarantees at all are made for this code.

   Mike Morton, 11 June 91
   Based very heavily on code from bp at NeXT

/*  If you use the same handleBlock() code, this is the threshhold below
   which a block is assumed to be garbage.  We don't insist on 100% vanilla
   ASCII because some text files may contain non-ASCII stuff (ellipsis,
   curly quotes, etc).
#define GOODPCT 0.90                    /* assume this %, or more, ASCII means worth saving */

#include  <stdio.h>
#include  <sys/file.h>
#include  <sys/param.h>
#include  <ufs/fs.h>

#define FRAGSIZE (MAXBSIZE/8)           /* size of one sub-block (better name for this?) */

static char *dirName = 0;               /* where to store output */

/*  save -- Write some data to a new file "filename" inside the directory
   specified in the command line args.
static void save (filename, data, length)
 char *filename;                       /* INPUT: name to save under */
 unsigned char *data;                  /* INPUT: stuff to save */
 unsigned long length;                 /* INPUT: length of stuff to save */
{ char pathname [200];
 FILE *f;

 /* printf (filename); printf ("\n"); return; */

 strcpy (pathname, dirName);           /* get dir      */
 strcat (pathname, "/");               /* get dir/     */
 strcat (pathname, filename);          /* get dir/file */

#if 0 /* useful for writing out only some stuff during testing */
{ static long counter = 0;
 if ((counter++ % 200) != 0) return;
 printf ("\nsaving: %s", pathname);

 f = fopen (pathname, "w+");
 if (! f) {  printf ("Couldn't open %s\n", pathname); return; }
 if (fwrite (data, 1, length, f) != length)
   printf ("Write failed!\n", pathname);
 fclose (f);
}                                       /* end of save () */

/*  handleBlock -- This is called from findFreeData().  We're passed a partly or completely
   free block which has been read in, and write out either the block, fragments of it, or

   If you're trying to recover some other kind of data (other than ASCII), you want to
   change this function to save different stuff when findFreeData() calls it.
static void handleBlock (block, blkNum, freeInfo)
 unsigned char *block;                 /* INPUT: pointer to MAXBSIZE bytes of data */
 unsigned long blkNum;                 /* INPUT: block number on disk */
 unsigned char freeInfo;               /* INPUT: bit-coded info for eight fragments of block */
                                       /* one-bits mean the frag is free (hence, interesting) */
{ unsigned int fragNum;                 /* ranges 0..7, for frags of the block */
 register unsigned char *subBlock;     /* one frag of data */
 register unsigned short count;
 register unsigned long goodChars;     /* count of apparently-ASCII chars in fragment or block */
 unsigned char fragBit;                /* bit compared against "freeInfo" */
 char filename [200];

 /*  If the 8-frag block is entirely free, write it out if it looks good.  We'll do the
     8-frag breakup below as well, if it seems good from that p.o.v. */
 if (freeInfo == 0xff)
 { subBlock = block;                   /* set up a working pointer... */
   count = MAXBSIZE;                   /* ...and a loop counter */
   goodChars = 0;
   /*  My hard disk appears to initialize a lot of stuff with many 0x04s, so use 0x05: */
   while (count--)
   { if ((*subBlock > 0x05)
         && ((*subBlock & 0x80) == 0)) /* appears to be ASCII? */

   /*  If we beat the quota, save this stuff: */
   if (goodChars >= (GOODPCT * MAXBSIZE))
   { sprintf (filename, "Block-%03d-%ld", goodChars*100/MAXBSIZE, blkNum);
     save (filename, block, MAXBSIZE);
return; /* HACK -- save space and don't fragment blocks we already write out whole */
 }                                     /* end of handling all-free block */

 /*  Do the same thing for each fragment. */
 for (fragNum = 0; fragNum <= 7; fragNum++)
 { fragBit = (0x80 >> fragNum);        /* bits run left-to-right (lower blocks are lefterly) */

   if (freeInfo & fragBit)             /* interesting fragment? */
   { subBlock = block + (fragNum * FRAGSIZE);  /* point to this frag of the block */

     count = FRAGSIZE;
     goodChars = 0;
     while (count--)                   /* tally the fragment */
     { if ((*subBlock > 0x05)
           && ((*subBlock & 0x80) == 0)) /* appears to be ASCII? */

     /*  If we beat the quota, save this stuff: */
     if (goodChars >= (GOODPCT * FRAGSIZE))
     { sprintf (filename, "Frag-%03d-%ld-%d", goodChars*100/FRAGSIZE, blkNum, fragNum);
       subBlock = block + (fragNum * FRAGSIZE);  /* point (again) to this frag of the block */
       save (filename, subBlock, FRAGSIZE);
   }                                   /* end of handling interesting frag */
 }                                     /* end of loop through frags */
}                                       /* end of handleBlock () */

/*  bread -- Read a block (specified by 1K-block number, not byte address). */
bread (fd, blk, buf, size)
 int fd;                               /* INPUT: file to read from */
 daddr_t blk;                          /* INPUT: block to start at */
 char *buf;                            /* OUTPUT: where to read to */
 int size;                             /* INPUT: how much to read */
{ if (lseek (fd, (long) dbtob (blk), 0) < 0)
 { fprintf(stderr, "bread: lseek error.\n");
   return 1;

 if (read (fd, buf, size) != size)
 { fprintf(stderr, "bread: read error.\n");
   return 1;

 return 0;
}                                       /* end of bread () */

/*  findFreeData -- Find all the free or partly-free blocks in a file system, and
   pass them to "func".
void findFreeData (filesys, func)
 char *filesys;                        /* INPUT: pathname of file system */
 void (* func) ();                     /* INPUT: function to call with blocks */
{ int rawFd;                            /* file descriptor for raw device */
 char slop[MAXBSIZE];                  /* big enough to read a whole block */
 struct fs *sblock = (struct fs *) slop; /* superblock, overlaid on buffer */
 unsigned int cgNum;                   /* number of a cylinder group */
 char cgSlop[MAXBSIZE];                /* big enough to read a whole block */
 struct cg* theCg = (struct cg*) cgSlop; /* cylinder group struct, overlaid on buffer */
 unsigned char *freeMapPtr;            /* pointer into free-block list */
 unsigned long blk;                    /* index into same */
 char blockData [MAXBSIZE];            /* data from one block */
 unsigned long totBlksRead = 0;

 /*  Open the device */
 if ((rawFd = open(filesys, O_RDONLY, 0)) < 0)
   { perror("open");
     fprintf(stderr, "findFreeData: cannot open %s.\n", filesys);
 sync ();                              /* get the disk up to date */

 /*  Read, check, and chat about the superblock. */
 if (bread (rawFd, SBLOCK, sblock, SBSIZE))
   { fprintf(stderr, "Error reading superblock.\n"); exit(1); }
 if (sblock->fs_magic != FS_MAGIC)
 { fprintf(stderr, "Bad superblock magic number.\n");
 printf ("Free blocks %ld\n",  sblock->fs_cstotal.cs_nbfree);
 printf ("Free frags %ld\n",   sblock->fs_cstotal.cs_nffree);

 /*  Loop through all cylinder groups. */
 /*  @@@ Why must we use -1 in sblock->fs_ncg-1?  We get lots of errors without this... */
 for (cgNum = 0; cgNum < sblock->fs_ncg-1; cgNum++)
 { printf ("CYLINDER GROUP #%d\n\n", cgNum); fflush (stdout); /* help impatient humans */

   if (bread (rawFd, (daddr_t) cgtod (sblock, cgNum), theCg, MAXBSIZE))
     { printf ("Read of cgtod failed.\n"); return; }
   if (theCg->cg_magic != CG_MAGIC)
     { printf ("Magic constant in cylinder group info is wrong!\n"); return; }
   if (theCg->cg_cgx != cgNum)
     { printf ("Cylinder number in cylinder group info is wrong!\n"); return; }

   /*  Walk the array of free blocks at the end of the cylinder block.  Each byte in
       the array is bit-coded -- any '1' bits at all mean some of the block is free.
   freeMapPtr = theCg->cg_free;
   for (blk = 0; blk < theCg->cg_ndblk; blk++)
   { if (freeMapPtr [blk])             /* nonzero value means partly or entirely free */
     { daddr_t absBlk;                 /* absolute block number on disk, suitable for bread() */

       absBlk = (8*blk) + cgdmin(sblock, cgNum); /* find start of 8K block */
       if (bread (rawFd, absBlk, blockData, MAXBSIZE))
         printf ("Read failed for block #%ld.\n", absBlk);
       { ++totBlksRead;
         (* func) (blockData, absBlk,  freeMapPtr [blk]); /* pass block & free-info to whomever */
     }                                 /* end of handling partly/entirely free block */
   }                                   /* end of loop through blocks */
 }                                     /* end of loop through cylinders */
 printf ("\nTotal blocks read: %ld.\n", totBlksRead);

}                                       /* end of findFreeData () */

void main(argc, argv)
 int argc;
 char *argv[];
{ register int argNum = 0;              /* argument number */
 char *fileSystemName;

 argNum = 1;
 while (argNum < argc)
 { if (*argv[argNum] == '-')
   { switch (argv[argNum][1])
     { case 's':                       /* specify file System */
         if (++argNum == argc) usage(argv[0]); /* no more args? */
         fileSystemName = argv[argNum];

       case 'o':                       /* specify Output directory */
         if (++argNum == argc) usage(argv[0]); /* no more args? */
         dirName = argv[argNum];
     }                                 /* end of switch on switch */
   }                                   /* end of handling "-" token */
   else usage(argv[0]);

 }                                     /* end of scanning args */

 if (fileSystemName == 0) usage(argv[0]); /* insist on a file system name */
 if (dirName == 0) usage(argv[0]);     /* insist on an output directory */

 /*  Pass every unallocated block or fragment to handleBlock() */
 findFreeData (fileSystemName, handleBlock);
}                                       /* end of main () */

/*  Print a usage message and die. */
 char *s;
{ printf("usage: %s -s filesystem -o outputdirectory\n", s);


To compile Mike's code on OpenStep the following change was required:

< { if (lseek (fd, (long) dbtob (blk), 0) < 0)
> { if (lseek (fd, (long) dbtob (blk, size), 0) < 0)

Of course this is all fully untested and without any warranty!

PS: Sorry for the multi-post, but I could not find any forum feature to upload files or hide long text.


Sorry if this is a dumb question but is this more effective than just running a 'cat' on the drive and then 'strings' on the output?

Brian Archer


Have you tried something like photorec? It works directly on the data and ignores the filesystem. I don't see why it wouldn't work on a nextstep partition, though you'd probably access the drive from another computer. It does seem pretty portable though so you might even be able to compile it on NeXT.



The problem is that I'm trying to do this on a MO disk so I have no way to mount it on a linux system.


I still have a canon L10132 (sans optical drive but of course including the SCSI converter board) I'm willing to part with...
October 12, 1988 Computing Advances To The NeXT Level


Hi Brian,

I'd try to avoid direct recovery of the "defective" drive, but would make an image of the drive first.
Do you possibly have enough space to dd your MO disk to a harddrive?
Maybe this can remove requirement of a NeXT recovery tool.

As gregwtmtno wrote, when having a drive-image, I'd first try PhotoRec against the image (e.g. on Linux).
If PhotoRec fails on the image, you still can use cat/strings, or the posted recovery tool, without any harm to the MO drive.

Cheers, Nuss