Hi all,

I was thinking how to improve the situation where users complain about
"unrecognized file systems" errors in "tail -f".
(as in the recent https://lists.gnu.org/archive/html/coreutils/2017-11/msg00043.html ).

This is not their "fault", as they simply have an older version
installed, and it is not our "fault", as the file system is likely
already supported in newer versions.

It is simply a matter of making it easier for users to understand what's
going on, and easier for us to not have to answer the same thing again and again.

I suggest the following:

1. a script that can detect which file systems are supported -
there's already one working in 'src/extract-magic'.

2. improve the script to also detect which in which version
the file system is supported (POC attached).

3. Generate an HTML page listing each filesystem, the magic value,
 and the minimal version it is supported in.
something like:

  Hello,
  If you are seeing the error message:

     unrecognized file system type [[0x794c7630]]

  This error is reported when reading files on [[OVERLAYFS]] file
  system. Support for [OVERLAYFS] was added in Coreutils version
  [v8.28].
  Upgrade coreutils version to avoid this warning.
  To see which version  you are using, run "tail --version".

  This warning is harmless - 'tail' will still operate correctly
  on your filesystem, although not as efficient as possible.

  If you have any further questions, please contact [email protected]


5. We modify 'tail' to point the user to the web page instead
of reporting a bug.

6. when new filesystems are added, just update the webpage.

What do you think ?

Attached is a proof-of-concept script implementing step 2.
Usage like so:

$ perl prepare-fs-support src/stat.c | head
 magic-value  c-constant              availble-in-version  human-name
      0xADF5  S_MAGIC_ADFS            v8.15                adfs
      0xADFF  S_MAGIC_AFFS            v8.15                affs
  0x5346414F  S_MAGIC_AFS             v8.15                afs
  0x09041934  S_MAGIC_ANON_INODE_FS   v8.15                anon-inode FS
      0x0187  S_MAGIC_AUTOFS          v8.15                autofs
  0x42465331  S_MAGIC_BEFS            v8.15                befs
  0x1BADFACE  S_MAGIC_BFS             v8.15                bfs
  0x9123683E  S_MAGIC_BTRFS           v8.15                btrfs
  0x0027E0EB  S_MAGIC_CGROUP          v8.15                cgroupfs

Of course, it doesn't even have to be an automated process with a perl script, we can just update a web page manually when a new filesystem is added.


Comments welcomed,
   -assaf






#!/usr/bin/perl -w
# Derive #define directives from specially formatted 'case ...:' statements.

# Copyright (C) 2003-2017 Free Software Foundation, Inc.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program 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 General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

use strict;
use Data::Dumper;
use Getopt::Long;

(my $VERSION = '$Revision: 1.6 $ ') =~ tr/[0-9].//cd;
(my $ME = $0) =~ s|.*/||;

END
{
  # Nobody ever checks the status of print()s.  That's okay, because
  # if any do fail, we're guaranteed to get an indicator when we close()
  # the filehandle.
  #
  # Close stdout now, and if there were no errors, return happy status.
  # If stdout has already been closed by the script, though, do nothing.
  defined fileno STDOUT
    or return;
  close STDOUT
    and return;

  # Errors closing stdout.  Indicate that, and hope stderr is OK.
  warn "$ME: closing standard output: $!\n";

  # Don't be so arrogant as to assume that we're the first END handler
  # defined, and thus the last one invoked.  There may be others yet
  # to come.  $? will be passed on to them, and to the final _exit().
  #
  # If it isn't already an error, make it one (and if it _is_ an error,
  # preserve the value: it might be important).
  $? ||= 1;
}

sub usage ($)
{
  my ($exit_code) = @_;
  my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
  if ($exit_code != 0)
    {
      print $STREAM "Try '$ME --help' for more information.\n";
    }
  else
    {
      print $STREAM <<EOF;
Usage: $ME [OPTIONS] FILE

This script parses src/stat.c file (from GNU coreutils),
and produces a table of filesystem magic values/names,
together with the released coreutils version in which
the filesystem is available.

Typical usage:

   $ME src/stat.c

NOTE:
The version is the most recent version were the filesystem
magic marger was updated. If S_MAGIC_FOO was added in commit
v8.20-11, the output will show v8.21 (assuming v8.21 was released).
If same magic value was later updated in commit v8.24-99,
the shown version will be v8.25 .

OPTIONS:
   --no-header omit header line
   --full     show revision/date/commit in output
   --debug    show debug/progress information
   --help     display this help and exit
   --version  output version information and exit

EOF
    }
  exit $exit_code;
}

{
  my $debug = 0;
  my $show_full = 0;
  my $header = 1;

  GetOptions
    (
     debug => sub { $debug = 1 },
     full => sub { $show_full = 1 },
     'no-header' => sub { $header = 0 },
     help => sub { usage 0 },
     version => sub { print "$ME version $VERSION\n"; exit },
    ) or usage 1;

  my $fail = 0;

  @ARGV < 1
    and (warn "$ME: missing FILE arguments\n"), $fail = 1;
  1 < @ARGV
    and (warn "$ME: too many arguments\n"), $fail = 1;
  $fail
    and usage 1;

  my $file = $ARGV[0];

  open FH, "git blame -s -e -l -t '$file'|"
    or die "$ME: can't git-blame '$file': $!\n";

  ##
  ## Parse src/stat.c , extract S_MAGIC_XXX definitions.
  ## For each one, keep the most recent git revision.
  ## Modeled after 'extract-magic', but additionally
  ## tracks the following "return" statement (which is expected
  ## to contain the human-readable name of the file system).
  my ($c_name, $fs_rev, $magic, $local, $name);
  my @fs;
  while (defined (my $line = <FH>))
  {
      $line =~ /^([A-Fa-f0-9]{40})\s+(\d+)\)\s+(.*)$/
          or warn "failed to parse git-blame line:\n  $line\n";

      my ($rev, $linenum, $linetext) = ($1, $2, $3);

      if ( $linetext =~ /^case S_MAGIC_/ ) {

          warn "return statement not detected after magic '$c_name' - will be 
skipped\n"
              if $c_name;

          $linetext =~
              m!^case (S_MAGIC_\w+): /\* (0x[0-9A-Fa-f]+) (local|remote) \*/!
              or (warn "$ME:$file:$.: malformed case S_MAGIC_... line"),
              $fail = 1, next;
          $c_name = $1;
          $magic = $2;
          $local = $3 eq 'local' ? 1 : 0;
          $fs_rev = $rev;

          #DEBUG
          #print "got cname '$c_name'\n";
      }
      elsif ( $c_name && $linetext =~ /^\s*return\s+"([^"]*)"\s*;/ ) {
          $name = $1 ;

          push @fs, { c_name => $c_name,
                      name   => $name,
                      magic  => $magic,
                      rev    => $fs_rev };

          #DEBUG
          #print "got name '$name'\n";

          undef $c_name ;
          undef $name ;
      }

    }
  close FH;
  if ($debug) {
      print STDERR "\n\nDetected filesystem magic constant/names:\n";
      print STDERR Dumper(\@fs),"\n";
  }

  ### Get list of official released versions
  my @tags = `git tag -l`
      or die "failed to run 'git tag -l': $!";
  my %released_versions = map { chomp ; $_ => 1 } @tags;

  if ($debug) {
      print STDERR "\n\nDetected official released versions:\n";
      print Dumper(\%released_versions);
  }

  ### Get the released version where each filesystem is available
  my %query_revs = map { $_->{rev} => 1 } @fs;
  my %revs;
  foreach my $r (keys %query_revs) {
      # Get the exact commit where this file system was added
      my $commit = `git describe '$r'`
          or die "failed to run 'git describe $r': $!";
      chomp $commit;

      # Extract the major/minor version
      my ($major,$minor) = $commit =~ /^v(\d+)\.(\d+)/;

      # Find the released version in which this filesystem is supported
      my $available;
      my $avail_ver1 = "v" . $major . "." . ($minor+1);
      my $avail_ver2 = "v" . ($major+1) . ".0";
      if (exists $released_versions{$avail_ver1}) {
          # Next minor version
          $available = $avail_ver1;
      } elsif (exists $released_versions{$avail_ver2}) {
          # Next major version
          $available = $avail_ver2;
      } else {
          # Not available yet
          $available = undef ;
      }

      $revs{$r} = { commit => $commit,
                    available => $available
      };
  }
  if ($debug) {
      print STDERR "\n\nDetected version for each revision:\n";
      print Dumper(\%revs);
  }


  # Add version info for each file system
  foreach my $f (@fs) {
      my $rev = $f->{rev};
      my $commit = $revs{$rev}->{commit};
      my $available = $revs{$rev}->{available};

      $f->{commit} = $commit;
      $f->{available} = ( $available or "not-released-yet" );
  }

  #TODO: version sort
  @fs = sort { $a->{available} cmp $b->{available} } @fs ;

  if ($debug) {
      print STDERR "\n\nFinal record for each filesystem record:\n";
      print Dumper(\@fs),"\n";
  }


  if ($header) {
      if ($show_full) {
          printf "%40s %12s %-23s  %12s   %-20s %s\n",
              "git-revision",
              "magic-value",
              "c-constant",
              "availble-in-version",
              "commit",
              "human-name";
      } else {
          printf "%12s  %-23s %-20s %s\n",
              "magic-value",
              "c-constant",
              "availble-in-version",
              "human-name";
      }
  }

  # print as a table
  foreach my $f (@fs) {
      if ($show_full) {
          printf "%40s %12s %-23s  %12s   %-20s %s\n",
                 $f->{rev},
                 $f->{magic},
                 $f->{c_name},
                 $f->{available},
                 $f->{commit},
                 $f->{name};
      } else {
          printf "%12s  %-23s %-20s %s\n",
                 $f->{magic},
                 $f->{c_name},
                 $f->{available},
                 $f->{name};
      }
  }

  exit $fail;
}

Reply via email to