npm package: @opsimathically/dirmap
Description: Recurses through all files and directories from a provided base directory, and provides information and utility.
Author: Jason Medeiros
Latest version: 1.0.6

# dirmap

Recurse through the filesystem, stat and map individual files, directories,
and devices, and store them within relevant maps as a file-info type, containing
relevant permissions information for the current user, other users, etc. Developers
determine what to map using callbacks; if no callback is provided, everything
found will be mapped.

Map some, map none, map all, or just operate on individual files via callbacks
individually. This code is useful for finding things like bad security permissions
which could lead to system security problems, finding arbitrary files, finding files
of certain sizes, md5 hashing files, etc. Subsequent runs on multiple directories
will compound the maps so that one object can store multiple directory recursions.

To empty all the maps, reset state, and start fresh, use the dirmap.reset() method.
Call dirmap.stop() from within found/fail callback to stop recursing.
This code was designed to work on Linux.

## Install

```bash
npm install @opsimathically/dirmap
```

## Building from source

This package is intended to be run via npm, but if you'd like to build from source,
clone this repo, enter directory, and run `npm install` for dev dependencies, then run
`npm run build`.

## Usage

```typescript
import { DirMap, file_info_t } from '@opsimathically/dirmap';

(async function () {
  /*
  Entries are mapped via a filesystem absolute path as key and can be 
  accessed via individual maps shown below.  Maps are separated by type
  to reduce the work a developer would need to do to make this code 
  useful for their specific tasks.
  
  // this map holds all entries of all types
  dirmap.result_map

  // these maps hold only specific types
  dirmap.directory_map
  dirmap.block_dev_map
  dirmap.char_dev_map
  dirmap.fifo_map
  dirmap.file_map
  dirmap.socket_map
  dirmap.symlink_map
  dirmap.other_map
  */

  // create a new dirmap, execute it against the etc directory, map
  // all files and create md5 hashes.  No callback provided.
  const dirmap = new DirMap();
  await dirmap.run({
    base_dir: '/etc/',
    options: {
      generate_file_md5s: true
    }
  });

  // run dirmap again against /dev/ without resetting.  The results of
  // both runs will be within the dirmap map sets.  This time we do not
  // md5 discovered files since the option was not provided.
  await dirmap.run({
    base_dir: '/dev/'
  });

  // reset the maps to empty them all out and start fresh
  dirmap.reset();

  // run dirmap using a found callback.  This allows us to filter
  // what gets mapped and what doesn't.  In this case, we're just looking
  // for the /etc/hosts file.  Since we return true from the callback,
  // only on this file, all of our maps will only contain this single entry.
  // You an filter any file(s) you'd like, just return true when a file
  // matches your desired criteria, and it will be mapped.
  //
  // We use this.stop() to stop recursion after the file is found.
  //
  await dirmap.run({
    base_dir: '/etc/',
    onfoundcb: async function (this: DirMap, file_info: file_info_t) {
      if (file_info.absolute_path === '/etc/hosts') {
        this.stop();
        return true;
      }
      return false;
    }
  });

  // You can also supply a fail callback, that will let you determine
  // what failed to map due to whatever error.  We use any for the error
  // as we can get arbitrary error codes from arbitrary systems.
  await dirmap.run({
    base_dir: '/root/',
    onfoundcb: async function (this: DirMap, file_info: file_info_t) {
      return false;
    },
    onfailcb: async function (this: DirMap, absolute_path: string, err: any) {
      console.log(`Could not map: ${absolute_path}`);
      return false;
    }
  });
})();
```

## Example File Entry Data

Each file mapped will correspond a file_info_t object, which will
appear as follows.

```typescript
// example object output for /etc/hosts
{
  name: 'hosts',
  raw_stats: {
    dev: 66306,
    mode: 33188,
    nlink: 1,
    uid: 0,
    gid: 0,
    rdev: 0,
    blksize: 4096,
    ino: 28708018,
    size: 315,
    blocks: 8,
    atimeMs: 1742196218432.5286,
    mtimeMs: 1737011996644.207,
    ctimeMs: 1737011996645.207,
    birthtimeMs: 1737011996644.207
  },
  type_info: {
    is_dir: false,
    is_block_dev: false,
    is_char_dev: false,
    is_fifo: false,
    is_file: true,
    is_socket: false,
    is_symlink: false
  },
  file_md5: undefined,
  permissions: {
    owner: {
      readable: true,
      writable: true,
      executable: false,
      suid: false,
      guid: false,
      sticky: false
    },
    group: {
      readable: true,
      writable: false,
      executable: false,
      suid: false,
      guid: false,
      sticky: false
    },
    others: {
      readable: true,
      writable: false,
      executable: false,
      suid: false,
      guid: false,
      sticky: false
    },
    processUser: {
      readable: true,
      writable: false,
      executable: false,
      suid: false,
      guid: false,
      sticky: false
    },
    sticky: false
  },
  absolute_path: '/etc/hosts',
  relative_path: '/hosts',
  base_path: '/etc',
  file_or_dir_path: '/etc',
  owner: { uid: 0, gid: 0 },
  process: { uid: 1000, gid: 1000 },
  extra: {}
}
```