/*
* Copyright 1995, 2004 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
/*
* fileiovms -- FileIO methods for VMS
*/
# define NEED_FCNTL
# define NEED_FILE
# define NEED_STAT
# define NEED_ERRNO
# include <stdhdrs.h>
# include <error.h>
# include <errornum.h>
# include <msgsupp.h>
# include <strbuf.h>
# include <datetime.h>
# include "filesys.h"
# include "fileio.h"
# include <sys/types.h>
# include <utime.h>
# include <rms.h>
# include <starlet.h>
# include <descrip.h>
# include <iodef.h>
# include <atrdef.h>
# include <fibdef.h>
// # include <lib$routines.h>
extern int global_umask;
/* We avoid using the C runtime "chmod" function provided by OpenVMS, among
* others, because that routine updates the timestamp on a file when you
* change permissions.
*
* Typically the way one alters attributes like modification time or file
* protections is through the use of RMS routines and extended attribute
* (XAB) control blocks. However, you can only do that if you can open the
* file in the first place. If a file is read-only, you can't modify
* anything; not even the filesystem attributes of the file!
*
* Instead, we have to use QIO (Queue I/O services), which are a lower
* layer which RMS itself uses. With that interface, we don't have to open
* a file in order to request a metadata operation on it. Unfortunately,
* because it's fairly low-level it's also quite tedious to use.
*
* This function encapsulates most of the work.
*/
static int
vms_attr_frob( char *filename, struct atrdef attrs[], unsigned int func )
{
struct FAB fab;
struct NAM nam;
struct fibdef fib;
char filename_expanded[ NAM$C_MAXRSS ];
char filename_result[ NAM$C_MAXRSS ];
struct dsc$descriptor_s filename_d = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, filename };
struct dsc$descriptor_s dev_d = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, &(nam.nam$t_dvi[1]) };
struct dsc$descriptor fib_d = { sizeof fib, DSC$K_DTYPE_Z, DSC$K_CLASS_S, (char *) &fib };
short int iosb[4]; /* i/o status block */
short int dev_chan;
int status;
int i;
fab = cc$rms_fab; /* initialize fields */
fab.fab$l_fna = filename;
fab.fab$b_fns = strlen( filename );
nam = cc$rms_nam; /* initialize fields */
nam.nam$l_esa = filename_expanded;
nam.nam$b_ess = sizeof( filename_expanded );
nam.nam$l_rsa = filename_result;
nam.nam$b_rss = sizeof( filename_result );
fab.fab$l_nam = &nam; /* chain to fab */
status = sys$parse( &fab );
if( !(status & 1) )
{
// lib$signal( status, fab.fab$l_stv );
return -1;
}
/* Now we have a NAM block; use that to create a device channel, which we
require in order to modify file metadata without opening it.
*/
dev_d.dsc$w_length = nam.nam$t_dvi[0];
status = sys$assign( &dev_d, &dev_chan, 0, 0 );
if( !(status & 1) )
{
// lib$signal( status );
return -1;
}
filename_d.dsc$a_pointer = nam.nam$l_name;
filename_d.dsc$w_length = nam.nam$b_name + nam.nam$b_type + nam.nam$b_ver;
/* Initialize the fib */
memset (&fib, 0, sizeof fib);
for( i=0; i < 3; i++ )
{
fib.fib$w_fid[i] = nam.nam$w_fid[i];
fib.fib$w_did[i] = nam.nam$w_did[i];
}
/* Don't update modtime due to any modifications.
This is the very heart of this routine. If this is not set, then when
the protections on a file are updated, or various time fields are set,
the modification time of the file is updated as well.
*/
fib.fib$l_acctl = FIB$M_NORECORD;
/* Set new file attributes.
The meaning of variable parameters P1-6 (starting at the 7th overall
parameter to this system routine) are dependent on the device type (we
assume a file in a filesystem here) and the function we're performing
(e.g. IO$_MODIFY). These come from the openvms I/O reference manual:
P1 - fib descriptor
P2 - address of file name descriptor
P3 - addr of word to receive length of filename result (don't care)
P4 - address of filename result buffer (don't care)
P5 - attribute descriptor list
P6 - null
*/
status = sys$qiow( NULL, /* no event flag */
dev_chan, func, &iosb,
NULL, NULL, /* no AST procedure or arg */
/* our request-specific parameters */
&fib_d, &filename_d, NULL, NULL, attrs, NULL );
sys$dassgn( dev_chan ); /* close channel */
if( !(status & 1) )
{
// lib$signal( status );
return -1;
}
return 0; /* success! */
}
static int
vms_attr_get( char *filename, struct atrdef attrs[] )
{
return vms_attr_frob( filename, attrs, IO$_ACCESS );
}
static int
vms_attr_set( char *filename, struct atrdef attrs[] )
{
return vms_attr_frob( filename, attrs, IO$_MODIFY );
}
int
vms_chmod( char *filename, unsigned int u_mode )
{
/* Unix modes contain 'user', 'group', and 'other' fields.
* There are three bits for each field:
* 0x4 (r) - file can be read
* 0x2 (w) - file can be mangled
* 0x1 (x) - file contains bugs
* Setting these *enable* access.
*
* VMS has system, owner, group, and world fields.
* There are four bits for each field:
* 0x8 (D) - If set, deletion prohibited
* 0x4 (E) - If set, execution prohibited
* 0x2 (W) - If set, writing prohibited
* 0x1 (R) - If set, reading prohibited
* That is, setting these bits *disables* corresponding access.
*
* Since we are converting from unix to vms, we don't have as many
* fields or mode bits, so we set write and delete to the same value.
* Likewise, we set system and owner permissions to be the same.
*
* Note that we do not handle setuid/setgid/sticky bits here; those
* require adding (or modifying) a POSIX extended access control entry
* and we are only handling basic permissions.
*/
unsigned short int v_mode
= ~( (((u_mode >> 0) & 2) << (12 + 2)) /* o(w) -> W(D) */
| (((u_mode >> 0) & 1) << (12 + 2)) /* o(x) -> W(E) */
| (((u_mode >> 0) & 2) << (12 + 0)) /* o(w) -> W(W) */
| (((u_mode >> 0) & 4) << (12 - 2)) /* o(r) -> W(R) */
| (((u_mode >> 3) & 2) << ( 8 + 2)) /* g(w) -> G(D) */
| (((u_mode >> 3) & 1) << ( 8 + 2)) /* g(x) -> G(E) */
| (((u_mode >> 3) & 2) << ( 8 + 0)) /* g(w) -> G(W) */
| (((u_mode >> 3) & 4) << ( 8 - 2)) /* g(r) -> G(R) */
| (((u_mode >> 6) & 2) << ( 4 + 2)) /* u(w) -> O(D) */
| (((u_mode >> 6) & 1) << ( 4 + 2)) /* u(x) -> O(E) */
| (((u_mode >> 6) & 2) << ( 4 + 0)) /* u(w) -> O(W) */
| (((u_mode >> 6) & 4) << ( 4 - 2)) /* u(r) -> O(R) */
| (((u_mode >> 6) & 2) << ( 0 + 2)) /* u(w) -> S(D) */
| (((u_mode >> 6) & 1) << ( 0 + 2)) /* u(x) -> S(E) */
| (((u_mode >> 6) & 2) << ( 0 + 0)) /* u(w) -> S(W) */
| (((u_mode >> 6) & 4) >> ( 0 + 2)) /* u(r) -> S(R) */
);
struct atrdef attrs[] = { { sizeof v_mode, ATR$C_FPRO, &v_mode },
{ 0, 0, 0 } };
return vms_attr_set( filename, attrs );
}
int
vms_utime( char *filename, time_t modtime )
{
# if 0
/* The vms-epoch timestamps passed to vms_attr_set have to be in local
time, which means adjusting the unix time by the current timezone.
This only returns the correct result if the timezone and offset (and
DST status) are configured correctly by the system manager.
*/
char *timezone = getenv( "SYS$TIMEZONE_DIFFERENTIAL" );
if( timezone )
modtime += atoi( timezone ); /* convert to localtime */
/* convert unix epoch to openvms epoch with nanosecond resolution */
__int64 mtime = ((__int64) modtime * 10000000UL) + 0x007c95674beb4000UL;
/* The above is error-prone, so instead, we'll use the unix utime() call
to set the modtime, then fetch the converted modtime using qiow and
use that to also set creation time, revtime, etc.
*/
# endif
struct utimbuf utm;
utm.modtime = modtime;
utm.actime = time(0) + atoi( timezone );
if( utime( filename, &utm ) < 0)
return -1;
__int64 mtime;
struct atrdef gattrs[] = { { sizeof mtime, ATR$C_MODDATE, &mtime },
{ 0, 0, 0 } };
if( vms_attr_get( filename, gattrs ) < 0)
return -1;
struct atrdef sattrs[] = { { sizeof mtime, ATR$C_CREDATE, &mtime },
{ sizeof mtime, ATR$C_MODDATE, &mtime },
{ sizeof mtime, ATR$C_REVDATE, &mtime },
// This is probably too aggressive:
// { sizeof mtime, ATR$C_ACCDATE, &mtime },
// { sizeof mtime, ATR$C_ATTDATE, &mtime },
{ 0, 0, 0 } };
return vms_attr_set( filename, sattrs );
}
void
FileIO::Chmod( FilePerm perms, Error *e )
{
// Don't set perms on symlinks
if( GetType() & FST_MASK == FST_SYMLINK )
return;
// Permissions for readonly/readwrite, exec vs no exec
int bits = IsExec() ? PERM_0777 : PERM_0666;
switch( perms )
{
case FPM_RO: bits &= ~PERM_0222; break;
case FPM_ROO: bits &= ~PERM_0266; break;
case FPM_RXO: bits = PERM_0500; break;
case FPM_RWO: bits = PERM_0600; break;
case FPM_RWXO: bits = PERM_0700; break;
}
if( vms_chmod( Name(), bits & ~global_umask ) >= 0 )
return;
// Can be called with e==0 to ignore error.
if( e )
e->Sys( "chmod", Name() );
}
void
FileIO::ChmodTime( int mtime, Error *e )
{
if( vms_utime( Name(), DateTime::Localize( mtime ) ) < 0)
e->Sys( "utime", Name() );
}
void
FileIO::Rename( FileSys *target, Error *e )
{
struct stat st;
stat( Name(), &st );
// On VMS and Novelle, the source must be writable (deletable,
// actually) for the rename() to unlink it. So we give it write
// perm first and then revert it to original perms after.
Chmod( FPM_RW, e );
// Don't unlink the target unless the source exists,
// as our rename isn't atomic (like on UNIX) and some
// stumblebum user may have removed the source file.
if( e->Test() )
return;
// One customer (in Iceland) wanted this for IRIX as well.
// You need if you are you running NFS aginst NT as well
// if you are running on NT. Gag me!
target->Unlink( 0 ); // yeech - must not exist to rename
if( rename( Name(), target->Name() ) < 0 )
{
e->Sys( "rename", target->Name() );
// failed, reset target perms anyway.
target->Perms( perms );
target->Chmod( e );
vms_utime( target->Name(), modTime );
return;
}
// reset the target to our perms
target->Perms( perms );
target->Chmod( e );
vms_utime( target->Name(), st.st_mtime );
// source file has been deleted, clear the flag
ClearDeleteOnClose();
}
/*
* FileIO::Unlink() - remove single file (error optional)
*/
void
FileIO::Unlink( Error *e )
{
if( !*Name() )
return;
// yeech - must be writable to remove
Chmod( FPM_RW, 0 );
if( remove( Name() ) < 0 )
{
if( e )
e->Sys( "remove", Name() );
return;
}
// VMS versions files (RMS): delete them all.
// (Supposedly remove() takes care of this, but
// apparently not.)
while( remove( Name() ) >= 0 )
;
}