home

about

license

support

K/Base

Indy
HomeContactsSite Map


Setting the file date on Indy's FTP Server component

Earlier, I blogged about the problems abusing the MDTM command to set the file date.  On a FTP Server, supporting the ability to set the file time is even worse.  The Indy FTP Server now supports three ways to set the time.  The commands are MDTM, SITE UTIME, and the MFx commands.

I will discuss each one of these individually.  If you do NOT have the FTP server, you can download it here.

MDTM

There is the usual abuse of the MDTM command.  To support this, you must use the OnSetModifiedTime event.  It is defined like this:

	procedure(ASender: TIdFTPServerContext; const AFileName : String; var AFileTime : TIdDateTime) of object;
The reason that AFileName is a variable parameter because another command I will mention later requires you to return the time as it was set on the file system.   You also should use the OnFileExistCheck event which is defined like this:
rocedure(ASender: TIdFTPServerContext; const APathName: string; var VExist : Boolean) of object;

The reason is that you have can have a hard time distinguishing betwen a get file date MDTM request with a set file MDTM request if the file name starts with something that looks like a valid time stamp followed by a space and a bunch of characters (remember that some file systems are extremely liberal about filenames).  You return True if the filename exists and the request is assumed to be a get file date request or if you set VExists to false, the server assumes you want to set the file date on the filename after the time stamp.   You should not use Borland's SysUtil routines with this event because this event passes a time stamp as GMT while Borland's SysUtil routines handle times in the user's local time zone.  You also have to return a time in GMT form. A code sample from the demo FTP server should help you handle this:

procedure TForm1.IdFTPServer1SetModifiedTime(ASender: TIdFTPServerContext;
  const AFileName: string; var AFileTime: TDateTime);
//This looks odd because you have to return the date after you modify it in the
//command reply.
//
//That's part of the MFF, MFCT, and MFMT commands which are defined by:
//
//http://ftp.netzmafia.de/rfc/internet-drafts/draft-somers-ftp-mfxx-01.txt
//http://www.trevezel.com/downloads/draft-somers-ftp-mfxx-00.html
//
//I know that this draft expired but it really is a shame because there is a large
//need for this.  This is a more eligant solution than simply abusing the MDTM
//command to set a file date.
var LHandle : THandle;
  LModDate : TFileTime;
begin
//We use the Win32 API instead of Borland's RTL because this is based on GMT.
//This is based on:
//http://www.swissdelphicenter.ch/torry/showcode.php?id=855

  LHandle := CreateFile(PChar(ReplaceChars(AppDir+'\'+AFileName)),
                     GENERIC_READ or GENERIC_WRITE,
                     0,
                     nil,
                     OPEN_EXISTING,
                     FILE_FLAG_BACKUP_SEMANTICS,
                     0);
  if LHandle <> INVALID_HANDLE_VALUE then
  begin
    try
      LModDate := TDateTimeToFileTime(AFileTime);
      if Windows.SetFileTime(LHandle,nil,nil,@LModDate) then
      begin
        //now that we set the date, we need to return what it was
        //set to because different file systems have different resolutions.
        if Windows.GetFileTime(LHandle,nil,nil,@LModDate) then
        begin
          AFileTime := FileTimeToTDateTime(LModDate);
        end;
      end
      else
      begin
        raise Exception.Create('File Operation Aborted');
      end;
    finally
      CloseHandle(LHandle);
    end;
  end
end;

SITE UTIME

NcFTP and gFTP use a SITE UTIME command.  If you support the first feature, you also support this for setting the file's Last Modified time.  But this command is also used by NcFTP to set a file creation time and a file last access time.  The bad thing about this is that there are actually two syntaxes that I have seen.

NcFTP sends the command like this "SITE UTIME .bashrc 20050815165138 20050815081129 20050815081129 UTC" while gFTP sends the command like this "SITE UTIME 20050815041129 /.bashrc" (the time stamp is the user's local time).  In the previous example commands, I refer to the same exact file.   have a special event in the FTP server called OnSiteUTIME which is defined like this:

procedure(ASender: TIdFTPServerContext; const AFileName : String;
    var VLastAccessTime, VLastModTime, VCreateDate : TIdDateTime;
    var VAUth : Boolean) of object;
This event is used not only to support the SITE UTIME command but also to set the file's Last Access time with the MMF command.   You should set VAuth to false if there is a permission problem.  Otherwise, just leave it set to true.  Like the OnSetModifiedFile event, times are given and returned as GMT.  I admit that this event is not necessary but I provided it so that you could set file dates in one Win32 API call (to reduce your I/O).  One thing I need to note is that some timestamps are "0" and that means that that particular time should not be set.   I admit that it makes things harder but I did because of how much the command varies.  Here's some code from the sample FTP server:
procedure TForm1.IdFTPServer1SiteUTIME(ASender: TIdFTPServerContext;
  const AFileName: string; var VLastAccessTime, VLastModTime,
  VCreateDate: TDateTime; var VAUth: Boolean);
var LHandle : THandle;
  LModDate : TFileTime;
  LPModDate : PFileTime;
  LCreateDate :TFileTime;
  LPCreateDate : PFileTime;
  LAccessDate :TFileTime;
  LPAccessDate : PFileTime;
begin
//We use the Win32 API instead of Borland's RTL because this is based on GMT.
//This is based on:
//http://www.swissdelphicenter.ch/torry/showcode.php?id=855

//We do things in a round-about way with pointers because sometimes a date will not
//be provided by a client and this could be used by the MFF command to set
//the Last Access Time fact

//The idea behind this event is to change the file dates in only ONE pass
  LHandle := CreateFile(PChar(ReplaceChars(AppDir+'\'+AFileName)),
                     GENERIC_READ or GENERIC_WRITE,
                     0,
                     nil,
                     OPEN_EXISTING,
                     FILE_FLAG_BACKUP_SEMANTICS,
                     0);
  if LHandle <> INVALID_HANDLE_VALUE then
  begin
    try
      if VCreateDate<>0 then
      begin
        LCreateDate := TDateTimeToFileTime(VCreateDate);
        LPCreateDate := @LCreateDate;
      end
      else
      begin
        LPCreateDate := nil;
      end;
      if VLastModTime<>0 then
      begin
        LModDate := TDateTimeToFileTime(VLastModTime);
        LPModDate := @LModDate;
      end
      else
      begin
        LPModDate := nil;
      end;
      if VLastAccessTime <> 0 then
      begin
        LAccessDate := TDateTimeToFileTime(VLastAccessTime);
        LPAccessDate := @LAccessDate;
      end
      else
      begin
        LPAccessDate := nil;
      end;

      if Windows.SetFileTime(LHandle,LPCreateDate,LPAccessDate,@LModDate) then
      begin
        //now that we set the date, we need to return what it was
        //set to because different file systems have different resolutions.
        if Windows.GetFileTime(LHandle,@LCreateDate,@LAccessDate,@LModDate) then
        begin
          VLastModTime := FileTimeToTDateTime(LModDate);
          VCreateDate := FileTimeToTDateTime(LCreateDate);
          VLastAccessTime := FileTimeToTDateTime(LAccessDate);
        end;
      end
      else
      begin
        raise Exception.Create('File Operation Aborted');
      end;
    finally
      CloseHandle(LHandle);
    end;
  end

end;

MFMT and MFF Modify

This is my favorate because it is standardized with times given only with GMT. If you support the MDTM command to set the file time, you also support this automatically.  It's handled transparently in the FTP server.  There is, however, an optional OnSetCreationTime event that is defined exactly like the OnSetModifiedTime and it works similarly (you just set the file creation time instead of the file's last modification time).   Here is some sample code from the demo FTP Server:

procedure TForm1.IdFTPServer1SetCreationTime(ASender: TIdFTPServerContext;
  const AFileName: string; var AFileTime: TDateTime);
var LHandle : THandle;
  LCreateDate : TFileTime;
begin
//We use the Win32 API instead of Borland's RTL because this is based on GMT.
//This is based on:
//http://www.swissdelphicenter.ch/torry/showcode.php?id=855

  LHandle := CreateFile(PChar( ReplaceChars(AppDir+'\'+AFileName)),
                     GENERIC_READ or GENERIC_WRITE,
                     0,
                     nil,
                     OPEN_EXISTING,
                     FILE_FLAG_BACKUP_SEMANTICS,
                     0);
  if LHandle <> INVALID_HANDLE_VALUE then
  begin
    try
      LCreateDate := TDateTimeToFileTime(AFileTime);
      if Windows.SetFileTime(LHandle,@LCreateDate,nil,nil) then
      begin
        //now that we set the date, we need to return what it was
        //set to because different file systems have different resolutions.
        if Windows.GetFileTime(LHandle,@LCreateDate,nil,nil) then
        begin
          AFileTime := FileTimeToTDateTime(LCreateDate);
        end;
      end
      else
      begin
        raise Exception.Create('File Operation Aborted');
      end;
    finally
      CloseHandle(LHandle);
    end;
  end
end;
On a Linux file system, you probably not use this event but on Win32, you might particularly as the MLST/MLSD commands can return the file's creation time.

Conclusion (or my little soapbox)

Personally, I think what I have described is a mess that should not event exist when you want to do something so simple as set a file's modification time.  If we only used the "MFMT" or "MFF Modify" commands, we would save a lot of problems.  

If anything, I have probably understated the value of the MFF command in this blog entry.  That command is an attempt to provide a flexible standardized way to modify information about a file such as file ownership (CHOWN), file group ownership (CHGRP), file permissions (CHMOD), and file attributes in Windows (ATTRIB).  In fact, you could modify all of that stuff in one single command.   This is one thing that makes SSH's file transfer (SFTP) a very robust protocol.  I wish more servers would support this feature better than they do today and I wish that IETF doesn't sit on this.


Corporate Sponsors

Atozed







home

about

license

support

K/Base

site map

links

Copyright © 1993 - 2008 Chad Z. Hower (Kudzu) and the Indy Pit Crew.          Website design by RuInternet.ru