// "dir"-Befehl für beliebiges Verzeichnis, mit File-Länge usw.
// 
// Aufruf: mydir3
// 
// Klaus Kusche, 2012

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>     // für ctime
#include <limits.h>   // für PATH_MAX

const char *progName;  // für Fehlermeldungen

// Fehlermeldung nach missglückter Dir-Operationen ausgeben 
// (mit errno als Fehler-Grund)
void errMsg(const char *operation, const char *fileName)
{
  fprintf(stderr, "%s: error %s %s: %s\n",
          progName, operation, fileName, strerror(errno));
  exit(EXIT_FAILURE);
}

int main(int argc, const char *argv[])
{
  progName = argv[0];  
  if (argc > 2) {
    fprintf(stderr, "Aufruf: %s [directory]\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  // Wir müssen den Verzeichnis-Namen nicht kopieren.
  // Es reicht, unseren Pointer auf den richtigen Namen zeigen zu lassen:
  // Zeigt entweder auf den Verzeichnisnamen in argv[1]
  // oder auf die String-Konstante "."
  const char *dirName = (argc == 2) ? argv[1] : ".";

  // Analog zu FILE *: Das Ergebnis von opendir, geht ins readdir
  // Was da drin ist, ist uns egal
  DIR *d = opendir(dirName);
  if (d == NULL) {
    errMsg("opening directory", dirName);
  }

  // den für alle Files gleichen Teil des Namens basteln wir nur einmal zusammen
  // nicht bei jedem File wieder!
  size_t dirNameLen = strlen(dirName);
  if (dirNameLen + 2 > PATH_MAX) {
    fprintf(stderr, "%s: %s ist zu lang!\n", argv[0], dirName);
    exit(EXIT_FAILURE);
  }
  char fileName[PATH_MAX];  // Verzeichnispfad und Filename
  strcpy(fileName, dirName);
  strcat(fileName, "/");
  // Ende des Pfades = Beginn des eigentlichen Filenamens in fileName
  // an diese Stelle kopieren wir dann immer wieder den Filenamen
  char *nameBeg = &(fileName[dirNameLen + 1]);           

  for (;;) {  // ein Schleifenumlauf pro File im Verzeichnis d
    // bei readdir kann man "Ende des Directories" und "Fehler"
    // nur durch Auswertung von "errno" unterscheiden,
    // dazu muss man errno vor jedem readdir-Aufruf explizit auf 0 setzen
    errno = 0;
    // Das Ergebnis von readdir zeigt auf die interne statische struct,
    // in die readdir den Filenamen gespeichert hat
    struct dirent *entry = readdir(d);
    if (entry == NULL) break;  // Fehler oder Ende des Verzeichnisses
    // entry ist ein Pointer auf eine struct
    // ==> Zugriff auf deren Member mit "->"
    // zu lang?
    if (dirNameLen + 1 + strlen(entry->d_name) + 1 >= PATH_MAX) {
      fprintf(stderr, "%s: '%s/%s' ist zu lang!\n",
                      argv[0], dirName, entry->d_name);
      continue;  // mach mit dem nächsten File weiter
    }
    // stat braucht den kompletten Filenamen mit Verzeichnis / Name
    // ==> kopiere den Namen hinten an den Pfad-Teil von fileName dran
    strcpy(nameBeg, entry->d_name);  
    // stat hat keine interne struct für sein Ergebnis
    // sondern wir müssen ihm eine leere struct übergeben,
    // in die es sein Ergebnis einträgt
    // ==> wir brauchen eine struct, nicht nur einen Pointer
    struct stat fileStat;
    // stat bekommt einen Pointer auf die struct,
    // in der es sein Ergebnis abliefern soll
    if (stat(fileName, &fileStat) != 0) {
      errMsg("getting entry", fileName);
    }
    // Jetzt stehen in fileStat die Informationen zum aktuellen File:
    // Art des Files prüfen wie in der Angabe angegeben
    // und als typ speichern (einzelnen Zeichen, kein ganzer String!)
    // fileStat ist die struct selbst, kein Pointer
    // ==> Member-Zugriff mit "."
    char typ = S_ISREG(fileStat.st_mode) ? 'f' :
               S_ISDIR(fileStat.st_mode) ? 'd' : '?';
    // ... Filedatum formatieren wie in der Angabe angegeben ...
    // (mit localtime(...) ginge das viel schöner, aber aufwändiger)
    char *date = ctime(&(fileStat.st_mtime));
    // ... und im Datum hinten das \n suchen
    // und an diese Stelle eine Ende-Markierung schreiben
    *(strchr(date, '\n')) = '\0';
    printf("%10ld %c %s %s\n", fileStat.st_size, typ, date, entry->d_name);
  }
  
  if (errno != 0) {
    errMsg("reading directory", dirName);
  }

  if (closedir(d) == -1) {
    errMsg("closing directory", dirName);
  }
  
  exit(EXIT_SUCCESS);
}
