Logo Search packages:      
Sourcecode: gnome-vfs2 version File versions  Download package

gnome-vfs-parse-ls.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* gnome-vfs-parse-ls.c - Routines for parsing output from the `ls' command.

   Copyright (C) 1999 Free Software Foundation

   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Authors: 1995 Miguel de Icaza
   1995 Jakub Jelinek
   1998 Pavel Machek
   1999 Cleanup by Ettore Perazzoli

   finduid, findgid are from GNU tar.  */

#include <config.h>
#include "gnome-vfs-parse-ls.h"

#include <glib/gi18n-lib.h>
#include <glib/gmem.h>
#include <glib/gmessages.h>
#include <glib/gstrfuncs.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#ifndef TUNMLEN 
#define TUNMLEN 256
#endif
#ifndef TGNMLEN
#define TGNMLEN 256
#endif


typedef struct {
      int saveuid;
      int my_uid;
      char saveuname[TUNMLEN];
} finduid_cache;
#define finduid_cache_init {-993, -993}

typedef struct {
      int savegid;
      int my_gid;
      char savegname[TGNMLEN];
} findgid_cache;
#define findgid_cache_init {-993, -993}

static int finduid (char *uname, finduid_cache *cache)
{
      struct passwd *pw;
      
      if (uname[0] != cache->saveuname[0]/* Quick test w/o proc call */
          || 0 != strncmp (uname, cache->saveuname, TUNMLEN)) {
            strncpy (cache->saveuname, uname, TUNMLEN);
            pw = getpwnam (uname);
            if (pw) {
                  cache->saveuid = pw->pw_uid;
            } else {
                  if (cache->my_uid < 0)
                        cache->my_uid = getuid ();
                  cache->saveuid = cache->my_uid;
            }
      }
      return cache->saveuid;
}

static int findgid (char *gname, findgid_cache *cache)
{
      struct group *gr;
      
      if (gname[0] != cache->savegname[0]/* Quick test w/o proc call */
          || 0 != strncmp (gname, cache->savegname, TUNMLEN)) {
            strncpy (cache->savegname, gname, TUNMLEN);
            gr = getgrnam (gname);
            if (gr) {
                  cache->savegid = gr->gr_gid;
            } else {
                  if (cache->my_gid < 0)
                        cache->my_gid = getgid ();
                  cache->savegid = cache->my_gid;
            }
      }
      return cache->savegid;
}


/* FIXME bugzilla.eazel.com 1188: This is ugly.  */
#define MAXCOLS 30

static int
vfs_split_text (char *p,
            char *columns[],
            int column_ptr[])
{
      char *original = p;
      int  numcols;

      for (numcols = 0; *p && numcols < MAXCOLS; numcols++) {
            while (*p == ' ' || *p == '\r' || *p == '\n') {
                  *p = 0;
                  p++;
            }
            columns [numcols] = p;
            column_ptr [numcols] = p - original;
            while (*p && *p != ' ' && *p != '\r' && *p != '\n')
                  p++;
      }
      return numcols;
}

static int
is_num (const char *s)
{
      if (!s || s[0] < '0' || s[0] > '9')
            return 0;
      return 1;
}

static int
is_dos_date (char *str)
{
      if (strlen (str) == 8 && str[2] == str[5] && strchr ("\\-/", (int)str[2]) != NULL)
            return 1;

      return 0;
}

static int
is_week (char *str, struct tm *tim)
{
      static char *week = "SunMonTueWedThuFriSat";
      char *pos;

      if ((pos = strstr (week, str)) != NULL) {
            if (tim != NULL)
                  tim->tm_wday = (pos - week)/3;
            return 1;
      }
      return 0;    
}

static int
is_month (const char *str, struct tm *tim)
{
      static char *month = "JanFebMarAprMayJunJulAugSepOctNovDec";
      char *pos;
    
      if ((pos = strstr (month, str)) != NULL) {
            if (tim != NULL)
                  tim->tm_mon = (pos - month)/3;
            return 1;
      }
      return 0;
}

static int
is_time (const char *str, struct tm *tim)
{
      char *p, *p2;

      if ((p = strchr (str, ':')) && (p2 = strrchr (str, ':'))) {
            if (p != p2) {
                  if (sscanf (str, "%2d:%2d:%2d",
                            &tim->tm_hour, &tim->tm_min, &tim->tm_sec)
                      != 3)
                        return 0;
            }
            else {
                  if (sscanf (str, "%2d:%2d",
                            &tim->tm_hour, &tim->tm_min)
                      != 2)
                        return 0;
            }
      }
      else 
            return 0;
    
      return 1;
}

static int is_year (const char *str, struct tm *tim)
{
      long year;

      if (strchr (str,':'))
            return 0;

      if (strlen (str) != 4)
            return 0;

      if (sscanf (str, "%ld", &year) != 1)
            return 0;

      if (year < 1900 || year > 3000)
            return 0;

      tim->tm_year = (int) (year - 1900);

      return 1;
}

/*
 * FIXME bugzilla.eazel.com 1182:
 * this is broken. Consider following entry:
 * -rwx------   1 root     root            1 Aug 31 10:04 2904 1234
 * where "2904 1234" is filename. Well, this code decodes it as year :-(.
 */

static int
vfs_parse_filetype (char c)
{
      switch (c) {
        case 'd':
            return S_IFDIR; 
        case 'b':
            return S_IFBLK;
        case 'c':
            return S_IFCHR;
        case 'l':
            return S_IFLNK;
        case 's':
#ifdef IS_IFSOCK /* And if not, we fall through to IFIFO, which is pretty
                close. */
            return S_IFSOCK;
#endif
        case 'p':
            return S_IFIFO;
        case 'm':
      case 'n':         /* Don't know what these are :-) */
        case '-':
      case '?':
            return S_IFREG;
        default:
            return -1;
      }
}

static int
vfs_parse_filemode (const char *p)
{
      /* converts rw-rw-rw- into 0666 */
      int res = 0;

      switch (*(p++)) {
      case 'r':
            res |= 0400; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'w':
            res |= 0200; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'x':
            res |= 0100; break;
      case 's':
            res |= 0100 | S_ISUID; break;
      case 'S':
            res |= S_ISUID; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'r':
            res |= 0040; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'w':
            res |= 0020; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'x':
            res |= 0010; break;
      case 's':
            res |= 0010 | S_ISGID; break;
        case 'l':
      /* Solaris produces these */
      case 'S':
            res |= S_ISGID; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'r':
            res |= 0004; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'w':
            res |= 0002; break;
      case '-':
            break;
      default:
            return -1;
      }

      switch (*(p++)) {
      case 'x':
            res |= 0001; break;
      case 't':
            res |= 0001 | S_ISVTX; break;
      case 'T':
            res |= S_ISVTX; break;
      case '-':
            break;
      default:
            return -1;
      }

      return res;
}

static gboolean
is_last_column (int idx,
            int num_cols,
            const char *carbon,
            int column_ptr[])
{
      const char *p;

      if (idx + 1 == num_cols) {
            return TRUE;
      }

      p = carbon + column_ptr[idx + 1] - 1;
      return *p == '\r' || *p == '\n';
}

static int
vfs_parse_filedate (int idx,
                char *columns[],
                int num_cols,
                const char *carbon,
                int column_ptr[],
                time_t *t)
{     /* This thing parses from idx in columns[] array */

      char *p;
      struct tm tim;
      int d[3];
      int got_year = 0, got_time = 0;
      int current_mon;
      time_t now;
    
      /* Let's setup default time values */
      now = time (NULL);
      tim = *localtime (&now);
      current_mon = tim.tm_mon;
      tim.tm_hour = 0;
      tim.tm_min  = 0;
      tim.tm_sec  = 0;
      tim.tm_isdst = -1; /* Let mktime () try to guess correct dst offset */
    
      p = columns [idx++];
    
      /* We eat weekday name in case of extfs */
      if (is_week (p, &tim))
            p = columns [idx++];

      /* Month name */
      if (is_month (p, &tim)) {
            /* And we expect, it followed by day number */
            if (is_num (columns[idx]))
                  tim.tm_mday = (int)atol (columns [idx++]);
            else
                  return 0; /* No day */

      } else {
            /* We usually expect:
               Mon DD hh:mm
               Mon DD  YYYY
               But in case of extfs we allow these date formats:
               Mon DD YYYY hh:mm
               Mon DD hh:mm YYYY
               Wek Mon DD hh:mm:ss YYYY
               MM-DD-YY hh:mm
               where Mon is Jan-Dec, DD, MM, YY two digit day, month, year,
               YYYY four digit year, hh, mm, ss two digit hour, minute
               or second. */

            /* Here just this special case with MM-DD-YY */
            if (is_dos_date (p)) {
                  p[2] = p[5] = '-';
          
                  if (sscanf (p, "%2d-%2d-%2d", &d[0], &d[1], &d[2]) == 3) {
                        /*  We expect to get:
                            1. MM-DD-YY
                            2. DD-MM-YY
                            3. YY-MM-DD
                            4. YY-DD-MM  */
            
                        /* Hmm... maybe, next time :)*/
            
                        /* At last, MM-DD-YY */
                        d[0]--; /* Months are zerobased */
                        /* Y2K madness */
                        if (d[2] < 70)
                              d[2] += 100;

                        tim.tm_mon  = d[0];
                        tim.tm_mday = d[1];
                        tim.tm_year = d[2];
                        got_year = 1;
                  } else
                        return 0; /* sscanf failed */
            } else
                  return 0; /* unsupported format */
      }

      /* Here we expect to find time and/or year */

      if (is_num (columns[idx])) {
            if ((got_time = is_time (columns[idx], &tim)) ||
                (got_year = is_year (columns[idx], &tim))) {
                  idx++;

                  /* This is a special case for ctime () or Mon DD YYYY hh:mm */
                  if (is_num (columns[idx]) && 
                      !is_last_column (idx, num_cols, carbon, column_ptr) && /* ensure that we don't eat two lines at once,
                                                                  where the first line provides a year-like
                                                                  filename but no year, or a time-like filename
                                                                  but no time */
                      ((!got_year && (got_year = is_year (columns[idx], &tim))) ||
                       (!got_time && (got_time = is_time (columns[idx], &tim)))))
                        idx++; /* time & year or reverse */
            } /* only time or date */
      }
      else 
            return 0; /* Nor time or date */

      /*
       * If the date is less than 6 months in the past, it is shown without year
       * other dates in the past or future are shown with year but without time
       * This does not check for years before 1900 ... I don't know, how
       * to represent them at all
       */
      if (!got_year &&
          current_mon < 6 && current_mon < tim.tm_mon && 
          tim.tm_mon - current_mon >= 6)

            tim.tm_year--;

      if ((*t = mktime (&tim)) < 0)
            *t = 0;
      return idx;
}

/**
 * gnome_vfs_parse_ls_lga:
 * @p: string containing the data in the form same as 'ls -al'.
 * @s: pointer to stat structure.
 * @filename: filename, will be filled here.
 * @linkname: linkname, will be filled here.
 *
 * Parses the string @p which is in the form same as the 'ls -al' output and fills
 * in the details in the struct @s, @filename and @linkname.
 *
 * Return value: 0 if cannot parse @p, 1 if successful.
 */
int
gnome_vfs_parse_ls_lga (const char *p,
                  struct stat *s,
                  char **filename,
                  char **linkname)
{
      char *columns [MAXCOLS]; /* Points to the string in column n */
      int column_ptr [MAXCOLS]; /* Index from 0 to the starting positions of the columns */
      int idx, idx2, num_cols;
      int i;
      int nlink;
      char *p_copy, *p_pristine;
      finduid_cache uid_cache = finduid_cache_init;
      findgid_cache gid_cache = findgid_cache_init;

      if (strncmp (p, "total", 5) == 0)
            return 0;

      p_copy = g_strdup (p);

      if ((i = vfs_parse_filetype (*(p++))) == -1)
            goto error;

      s->st_mode = i;
      if (*p == ' ')    /* Notwell 4 */
            p++;
      if (*p == '[') {
            if (strlen (p) <= 8 || p [8] != ']')
                  goto error;
            /* Should parse here the Notwell permissions :) */
            if (S_ISDIR (s->st_mode))
                  s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR
                               | S_IXUSR | S_IXGRP | S_IXOTH);
            else
                  s->st_mode |= (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR);
            p += 9;
      } else {
            if ((i = vfs_parse_filemode (p)) == -1)
                  goto error;
            s->st_mode |= i;
            p += 9;

            /* This is for an extra ACL attribute (HP-UX) */
            if (*p == '+')
                  p++;
      }

      g_free (p_copy);
      p_copy = g_strdup (p);
      p_pristine = g_strdup (p);
      num_cols = vfs_split_text (p_copy, columns, column_ptr);

      nlink = atol (columns [0]);
      if (nlink < 0)
            goto error;

      s->st_nlink = nlink;

      if (!is_num (columns[1]))
            s->st_uid = finduid (columns [1], &uid_cache);
      else
            s->st_uid = (uid_t) atol (columns [1]);

      /* Mhm, the ls -lg did not produce a group field */
      for (idx = 3; idx <= 5; idx++) 
            if (is_month (columns [idx], NULL)
                || is_week (columns [idx], NULL)
                || is_dos_date (columns[idx]))
                  break;

      if (idx == 6 || (idx == 5
                   && !S_ISCHR (s->st_mode)
                   && !S_ISBLK (s->st_mode)))
            goto error;

      /* We don't have gid */ 
      if (idx == 3 || (idx == 4 && (S_ISCHR (s->st_mode)
                              || S_ISBLK (s->st_mode))))
            idx2 = 2;
      else { 
            /* We have gid field */
            if (is_num (columns[2]))
                  s->st_gid = (gid_t) atol (columns [2]);
            else
                  s->st_gid = findgid (columns [2], &gid_cache);
            idx2 = 3;
      }

      /* This is device */
      if (S_ISCHR (s->st_mode) || S_ISBLK (s->st_mode)) {
            guint32 maj, min;
      
            if (!is_num (columns[idx2])
                || sscanf (columns [idx2], " %d,", &maj) != 1)
                  goto error;
      
            if (!is_num (columns[++idx2])
                || sscanf (columns [idx2], " %d", &min) != 1)
                  goto error;
      
#ifdef HAVE_ST_RDEV
            /* Starting from linux 2.6, minor number is split between bits 
             * 0-7 and 20-31 of dev_t. This calculation is also valid
             * on older kernel with 8 bit minor and major numbers 
             * http://lwn.net/Articles/49966/ has a pretty good explanation
             * of the format of dev_t
             */
            min = (min & 0xff) | ((min & 0xfff00) << 12);
            s->st_rdev = ((maj & 0xfff) << 8) | (min & 0xfff000ff);
#endif
            s->st_size = 0;
      
      } else {
            /* Common file size */
            if (!is_num (columns[idx2]))
                  goto error;
      
            s->st_size = (gsize) atol (columns [idx2]);
#ifdef HAVE_ST_RDEV
            s->st_rdev = 0;
#endif
      }

      idx = vfs_parse_filedate (idx, columns, num_cols, p, column_ptr, &s->st_mtime);
      if (!idx)
            goto error;
      /* Use resulting time value */
      s->st_atime = s->st_ctime = s->st_mtime;
      s->st_dev = 0;
      s->st_ino = 0;
#ifdef HAVE_ST_BLKSIZE
      s->st_blksize = 512;
#endif
#ifdef HAVE_ST_BLOCKS
      s->st_blocks = (s->st_size + 511) / 512;
#endif

      for (i = idx + 1, idx2 = 0; i < num_cols; i++ ) 
            if (strcmp (columns [i], "->") == 0) {
                  idx2 = i;
                  break;
            }
    
      if (((S_ISLNK (s->st_mode) 
            || (num_cols == idx + 3 && s->st_nlink > 1))) /* Maybe a hardlink?
                                                 (in extfs) */
          && idx2) {
            int p;
            char *s;
          
            if (filename) {
                  s = g_strndup (p_copy + column_ptr [idx],
                               column_ptr [idx2] - column_ptr [idx] - 1);
                  *filename = s;
            }
            if (linkname) {
                  s = g_strdup (p_copy + column_ptr [idx2+1]);
                  p = strlen (s);
                  if (s [p-1] == '\r' || s [p-1] == '\n')
                        s [p-1] = 0;
                  if (s [p-2] == '\r' || s [p-2] == '\n')
                        s [p-2] = 0;
            
                  *linkname = s;
            }
      } else {
            /* Extract the filename from the string copy, not from the columns
             * this way we have a chance of entering hidden directories like ". ."
             */
            if (filename) {
                  /* 
                   *filename = g_strdup (columns [idx++]);
                   */
                  int p;
                  char *s;

                  s = g_strdup (p_pristine + column_ptr [idx]);
                  p = strcspn (s, "\r\n");
                  s[p] = '\0';

                  *filename = s;
            }
            if (linkname)
                  *linkname = NULL;
      }
      g_free (p_copy);
      g_free (p_pristine);
      return 1;

 error:
      {
            static int errorcount = 0;

            if (++errorcount < 5)
                  g_warning (_("Could not parse: %s"), p_copy);
            else if (errorcount == 5)
                  g_warning (_("More parsing errors will be ignored."));
      }

      if (p_copy != p)        /* Carefull! */
            g_free (p_copy);
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index