/*------------------------------------------------------------------------*
 *                                                                        *
 *      pwd.c                                                             *
 *      Affiche le nom du repertoire courant                              *
 *      @Author Frederic Olivie                                           *
 *      @Group Staff                                                      *
 *                                                                        *
 *------------------------------------------------------------------------*/

// Attention. L'utilisation du champ d_ino de la structure dirent
// est illegal. Selon le man :
//
//     According to POSIX, the dirent structure contains a  field
//     char  d_name[]  of unspecified size, with at most NAME_MAX
//     characters preceding the terminating null character.   Use
//     of  other  fields  will  harm the portability of your proÂ-
//     grams.  POSIX 1003.1-2001 also documents the  field  ino_t
//     d_ino as an XSI extension.
//
// Il faut donc utiliser l'appel systeme stat() afin d'obtenir des
// informations sur le repertoire voulu.
//


// Cet include est standard
#include<stdio.h>

// Ces includes sont donnes par "man 2 stat"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// Cet include est donne par "man 3 opendir"
#include <dirent.h>

// Cet include est donne par "man 3 perror"
#include <errno.h>

// Cet include nous est donne par "man strdup"
#include <string.h>

char *getWD()
{
  struct stat current ;
  struct stat parent ;
  struct stat tempBuf ;

  DIR *directory ;
  struct dirent *dirEntry ;

  int found = 0 ;
  int len, len2 ;
  char *resultDir ;
  char *foundDir ;

  // On recupere les infos du repertoire courant
  if (stat(".", &current) != 0) {
    perror("Can not stat current directory") ;
    exit (-1) ;
  }

  // On monte d'un cran dans la hierarchie (vers le parent)
  // pour chercher le nom du repertoire courant
  chdir("..") ;

  // On recupere les infos du nouveau repertoire courant
  // qui est maintenant le parent (depuis le chdir)
  if (stat(".", &parent) != 0) {
    perror("Can not stat parent directory") ;
    exit (-1) ;
  }

  // On lit le nouveau repertoire courant (donc, le parent)
  if (!(directory = opendir("."))) {
    perror("Could not read directory content\n") ;
    exit (-1) ;
  }

  // Si le repertoire courant (.) et le parent (..) ont le meme numero
  // d'inode ET le meme numero de device, alors on est a la
  // racine du systeme.
  //
  // Le numero de device sert a referencer les systemes de fichiers
  // montes sur le systeme. Tous les repertoires qui sont a la racine
  // d'un systeme de fichier ont le meme numero d'inode. Il est donc
  // impossible de savoir si on est a la racine du systeme ou d'un
  // systeme de fichier sans comparer les numeros de device.
  //
  // Par ailleurs, il n'est pas acceptable de considerer que la valeur
  // "2" est celle qui correspond a la racine. Meme si c'est le cas
  // dans les faits, ce n'est specifie nulle part.
  //
  // C'est donc illegal de compter dessus.

  if ((current.st_dev == parent.st_dev)
      && (current.st_ino == parent.st_ino)) {

    // On est a la racine. On alloue le buffer qui va redescendre
    // la recursion pour se remplir au fur et a mesure.
    // On rajoute la valeur "/" a l'interieur
    if (!(resultDir = (char *)malloc(sizeof(char)*FILENAME_MAX))) {
      perror("malloc") ;
      exit(-1) ;
    }
    //    strcat(resultDir, "/") ;
    resultDir[0] = 0 ;
    foundDir = NULL ;
  }


  else {
    // Nous ne sommes pas a la racine. On va recuperer le nom du repertoire
    // dans le parent (dans lequel nous nous trouvons actuellement)
    // a l'aide de l'appel systeme stat().
    // On lit donc toutes les entrees du repertoire jusqu'a tomber
    // sur la bonne
    //
    while (dirEntry = readdir(directory)) {
      if (lstat(dirEntry->d_name, &tempBuf) != 0) {
	perror("Can not stat directory") ;
	exit (-1) ;
      }
      // Comme pour la racine, si un repertoire est specifie par le
      // meme numero d'inode et le meme numero de device que le repertoire
      // courant (le premier, avant le chdir(".."), alors, nous avons
      // trouve notre candidat.
      if ((tempBuf.st_dev == current.st_dev)
	  && (tempBuf.st_ino == current.st_ino)) {
	found = 1 ; // Marquons le comme trouve afin de gerer les erreurs
	break ;
      }
    }

    // On sauvegarde la valeur du repertoire trouve
    if (!(foundDir = strdup(dirEntry->d_name))) {
      perror("strdup") ;
      exit (-1) ;
    }
    
    // On ferme le repertoire qui n'est plus necessaire
    closedir(directory) ;

    // Si, a la fin du while, readdir retourne en erreur, c'est que
    // nous avons un soucis (cf. "man 3 readdir")
    if (errno == EBADF) {
      perror("readdir") ;
      exit (-1) ;
    }

    // Verification de la coherence. Si le repertoire n'est pas trouve,
    // nous avons un serieux bug sur le filesystem !!!
    // C'est la raison pour laquelle nous avons utilise la variable "found".
    if (!found) {
      fprintf(stderr, "Something is seriously broken. You should check your filesystem\n") ;
      exit (-1) ;
    }

    // Nous savons qui nous sommes. Appelons la fonction getWD recursivement
    // afin de savoir qui est le repertoire parent (dans lequel nous nous
    // trouvons).
    resultDir = getWD() ;
  }

  // Nous avons recupere la variable resultDir qui doit, apres autant de
  // recursions que necessaire, contenir le chemin d'acces de la racine
  // au repertoire courant.
  // On y rajoute un "/" et le nom du repertoire courant.
  len = strlen(resultDir) ;

  // Ici, on gere le cas particulier ou on execute depuis la racine
  if (len != 1)
    strcat(resultDir, "/") ;


  if (foundDir) {
    len2 = strlen(foundDir) ;

    // On quitte en cas de "chemin" depassant FILENAME_MAX.
    if ((len + len2 + 1) > FILENAME_MAX) {
      fprintf(stderr, "Depth too large. You will have troubles working with this ! Aborting\n") ;
    }

    // On rajoute le repertoire courant. On a donc :
    // getWD()/rep_courant
    // getWD() fournit tout ce qui est au dessus a travers plusieurs niveaux
    // de recursion.
    strncat(resultDir, foundDir, len2) ;
    
    // On libere la memoire de foundDir
    free(foundDir) ;
  }

  // On renvoie la valeur finale (ou bien au main(), ou bien au niveau
  // de recursion inferieur).
  return(resultDir) ;
}

main(argc ,argv)
char **argv ;
{
  puts(getWD()) ;
}
