Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

724 lines
28 KiB

//================================================================================
// Copyright (C) 1997 Microsoft Corporation
// Author: RameshV
// Description: This file has the functions relating to downloading stuff from
// off the DS in a safe way onto the registry. The solution adopted is actually
// to dowload onto the "Config.DS" key rather than to the "Configuration" key
// itself. Then if the full download is successful, backup this key and restore it
// onto the "Configuration" key -- so if anything fails, atleast the old configuration
// would be intact.
//================================================================================
#include <mmregpch.h>
#include <regutil.h>
#include <regsave.h>
#define FreeArray1(X) Error = LoopThruArray((X), DestroyString, NULL, NULL);Require(ERROR_SUCCESS == Error);
#define FreeArray2(X) Error = MemArrayCleanup((X)); Require(ERROR_SUCCESS == Error);
#define FreeArray(X) do{ DWORD Error; FreeArray1(X); FreeArray2(X); }while(0)
typedef DWORD (*ARRAY_FN)(PREG_HANDLE, LPWSTR ArrayString, LPVOID MemObject);
extern
DWORD
DestroyString( // defined in regread.c
IN PREG_HANDLE Unused,
IN LPWSTR StringToFree,
IN LPVOID Unused2
);
extern
DWORD
LoopThruArray( // defined in regread.c
IN PARRAY Array,
IN ARRAY_FN ArrayFn,
IN PREG_HANDLE Hdl,
IN LPVOID MemObject
);
DWORD // need to include headers..
DhcpDsGetEnterpriseServers( // defined in dhcpds\dhcpread.h.
IN DWORD Reserved,
IN LPWSTR ServerName,
IN OUT PARRAY Servers
) ;
//================================================================================
// module files
//================================================================================
REG_HANDLE DsConfig = { NULL, NULL, NULL }; // DsConfig key is stored here
PM_SERVER CurrentServer;
static const
DWORD ZeroReserved = 0;
DWORD
PrepareRegistryForDsDownload( // make reg changes to download
VOID
)
{
DWORD Err, Disposition;
REG_HANDLE DsConfigParent;
if( NULL != DsConfig.Key ) return ERROR_INVALID_PARAMETER;
memset(&DsConfigParent,0, sizeof(DsConfigParent));
Err = RegOpenKeyEx( // open the parent key for DsConfig.
HKEY_LOCAL_MACHINE,
REG_THIS_SERVER_DS_PARENT,
ZeroReserved,
REG_ACCESS,
&DsConfigParent.Key
);
if( ERROR_SUCCESS != Err ) return Err; // cant do much if server key aint there.
Err = DhcpRegRecurseDelete(&DsConfigParent, REG_THIS_SERVER_DS_VALUE);
RegCloseKey(DsConfigParent.Key); // this is all the parent is needed for
if( ERROR_SUCCESS != Err && ERROR_FILE_NOT_FOUND != Err ) {
return Err; // could not delete the "config_ds" trash?
}
Err = RegCreateKeyEx( // now create a fresh "config_ds" key
HKEY_LOCAL_MACHINE,
REG_THIS_SERVER_DS,
ZeroReserved,
REG_CLASS,
REG_OPTION_NON_VOLATILE,
REG_ACCESS,
NULL,
&DsConfig.Key,
&Disposition
);
if( ERROR_SUCCESS != Err ) return Err; // could not create the key, nowhere to store..
return DhcpRegSetCurrentServer(&DsConfig); // now set this as the default server loc to use..
}
VOID
CleanupAfterDownload( // keep stuff clean for other modules
VOID
)
{
if( NULL != DsConfig.Key ) RegCloseKey(DsConfig.Key);
DsConfig.Key = NULL; // close the config_ds key and,
DhcpRegSetCurrentServer(NULL); // forget abt the config_ds key..
//remote the Ds cache no matter what...
}
DWORD
CopyRegKeys( // copy between reg keys
IN HKEY SrcKey, // copy tree rooted at this key
IN LPWSTR DestKeyLoc, // onto registry key located here
IN LPWSTR FileName // using this as the temp file
)
{
DWORD Err, Disposition;
HKEY DestKey;
BOOLEAN HadBackup;
NTSTATUS NtStatus;
NtStatus = RtlAdjustPrivilege (SE_BACKUP_PRIVILEGE, TRUE, FALSE, &HadBackup);
if( ERROR_SUCCESS != NtStatus ) { // could not request backup priv..
return RtlNtStatusToDosError(NtStatus);
}
NtStatus = RtlAdjustPrivilege (SE_RESTORE_PRIVILEGE, TRUE, FALSE, &HadBackup);
if( ERROR_SUCCESS != NtStatus ) {
return RtlNtStatusToDosError(NtStatus);
}
Err = RegSaveKey(SrcKey, FileName, NULL); // NULL ==> no security on file
if( ERROR_SUCCESS != Err ) return Err; // if key cant be saved, cant restore.
Err = RegCreateKeyEx( // now create a fresh "config_ds" key
HKEY_LOCAL_MACHINE,
DestKeyLoc,
ZeroReserved,
REG_CLASS,
REG_OPTION_NON_VOLATILE,
REG_ACCESS,
NULL,
&DestKey,
&Disposition
);
if( ERROR_SUCCESS != Err ) return Err; // could not create the key, nowhere to store..
Err = RegRestoreKey(DestKey, FileName, 0 ); // 0 ==> no flags, in particular, not volatile.
RegCloseKey(DestKey); // dont need this key anyways.
return Err;
}
DWORD
FixSpecificClusters( // this fixes a specific cluster
IN HKEY NewCfgKey, // root cfg where to copy over
IN LPWSTR Subnet, // the subnet to copy to
IN LPWSTR Range, // the range to copy to
IN LPBYTE InUseClusters, // in use cluster value
IN ULONG InUseSize,
IN LPBYTE UsedClusters, // used clusters value
IN ULONG UsedSize
)
{
return ERROR_CALL_NOT_IMPLEMENTED; // not done yet...
// just concat REG_SUBNETS Subnet REG_RANGES Range and try to open that.
// if it fails, quit, other wise just set the given values over...
}
DWORD
FixAllClusters1( // copy cluster info frm old to new cfg
IN HKEY NewCfgKey,
IN HKEY OldCfgKey
)
{
REG_HANDLE Cfg, Tmp1, Tmp2;
DWORD Err;
ARRAY Subnets, Ranges;
LPWSTR ThisSubnet, ThisRange;
ARRAY_LOCATION Loc1, Loc2;
Cfg.Key = OldCfgKey; // Should not poke inside directly
MemArrayInit(&Subnets);
Err = DhcpRegServerGetList(&Cfg, NULL, NULL, &Subnets, NULL, NULL, NULL);
if( ERROR_SUCCESS != Err ) {
MemArrayCleanup(&Subnets);
return Err;
}
for( Err = MemArrayInitLoc(&Subnets, &Loc1)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(&Subnets, &Loc1)
) { // for each subnet, look for ranges
Err = MemArrayGetElement(&Subnets, &Loc1, &ThisSubnet);
Err = DhcpRegServerGetSubnetHdl(&Cfg, ThisSubnet, &Tmp1);
if( ERROR_SUCCESS != Err ) { // what do we do? just ignore it I think
continue;
}
Err = DhcpRegSubnetGetList(&Tmp1, NULL, &Ranges, NULL, NULL, NULL, NULL );
if( ERROR_SUCCESS != Err ) {
DhcpRegCloseHdl(&Tmp1);
continue;
}
for( Err = MemArrayInitLoc(&Ranges, &Loc2)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(&Ranges, &Loc2)
) { // for each range try to copy it over..
LPBYTE InUseClusters = NULL, UsedClusters = NULL;
ULONG InUseClustersSize = 0, UsedClustersSize = 0;
Err = MemArrayGetElement(&Ranges, &Loc2, &ThisRange);
Err = DhcpRegSubnetGetRangeHdl(&Tmp1, ThisRange, &Tmp2);
if( ERROR_SUCCESS != Err ) continue;
Err = DhcpRegRangeGetAttributes(
&Tmp2,
NULL /* no name */,
NULL /* no comm */,
NULL /* no flags */,
NULL /* no bootp alloc */,
NULL /* no max boop allowed */,
NULL /* no start addr */,
NULL /* no end addr */,
&InUseClusters,
&InUseClustersSize,
&UsedClusters,
&UsedClustersSize
);
if( ERROR_SUCCESS == Err ) {
Err = FixSpecificClusters(
NewCfgKey, ThisSubnet, ThisRange, InUseClusters, InUseClustersSize,
UsedClusters, UsedClustersSize
);
if( InUseClusters ) MemFree(InUseClusters);
if( UsedClusters ) MemFree(UsedClusters);
}
DhcpRegCloseHdl(&Tmp2);
}
FreeArray(&Ranges);
DhcpRegCloseHdl(&Tmp1);
}
FreeArray(&Subnets);
return ERROR_SUCCESS;
}
DWORD
FixAllClusters( // copy the clusters over frm existing to DS_CONFIG
IN HKEY DsKey // so that when it is copied back nothing is lost
)
{
HKEY OldCfgKey;
ULONG Disposition, Err;
return ERROR_SUCCESS; // Need to fix this..
Err = RegCreateKeyEx(
HKEY_LOCAL_MACHINE,
REG_THIS_SERVER,
ZeroReserved,
REG_CLASS,
REG_OPTION_NON_VOLATILE,
REG_ACCESS,
NULL,
&OldCfgKey,
&Disposition
);
if( ERROR_SUCCESS != Err ) {
return Err; // ugh? this should not happen
}
Err = FixAllClusters1(DsKey, OldCfgKey);
RegCloseKey(OldCfgKey);
return Err;
}
VOID
CopyDsConfigToNormalConfig( // copy downloaded config to normal config
VOID
)
{
BOOL Status;
DWORD Err;
Status = DeleteFile(L"TempDhcpFile.Reg" ); // this file will be used for temp. storage
if( !Status ) { // could not delete this file?
Err = GetLastError();
if( ERROR_FILE_NOT_FOUND != Err && // the file does exist?
ERROR_PATH_NOT_FOUND != Err ) { // this could also happen?
return; // the nwe wont be able to do the copy!
}
}
FixAllClusters(DsConfig.Key); // copy the ranges values over from old to new..
CopyRegKeys(DsConfig.Key, REG_THIS_SERVER, L"TempDhcpFile.Reg");
DeleteFile(L"TempDhcpFile.Reg" ); // dont need this file anymore..
}
#if 0
DWORD
SaveServerClasses( // save all class info onto registry
IN PREG_HANDLE Server, // registry handle to server config.
IN PM_CLASSDEFLIST Classes // list of defined classes
)
{
DWORD Err, Err2;
REG_HANDLE Hdl;
ARRAY_LOCATION Loc;
PM_CLASSDEF ThisClass;
for( // save each class definition
Err = MemArrayInitLoc(&Classes->ClassDefArray, &Loc)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(&Classes->ClassDefArray, &Loc)
) {
//= require ERROR_SUCCESS == Err
Err = MemArrayGetElement(&Classes->ClassDefArray, &Loc, &ThisClass);
//= require ERROR_SUCCESS == Err && NULL != ThisClass
Err = DhcpRegServerGetClassDefHdl(Server,ThisClass->Name,&Hdl);
if( ERROR_SUCCESS != Err ) return Err; // registry error?
Err = DhcpRegClassDefSetAttributes // save this class information
(
/* Hdl */ &Hdl,
/* Name */ &ThisClass->Name,
/* Comment */ &ThisClass->Comment,
/* Flags */ &ThisClass->Type,
/* Value */ &ThisClass->ActualBytes,
/* ValueSize */ ThisClass->nBytes
);
Err2 = DhcpRegCloseHdl(&Hdl); //= require ERROR_SUCCESS == Err2
if( ERROR_SUCCESS != Err) return Err; // could not set-attribs in reg.
}
return ERROR_SUCCESS; // everything went fine.
}
DWORD
SaveServerOptDefs1( // save some option definition
IN PREG_HANDLE Server, // registry handle to server config.
IN LPWSTR ClassName, // name of the class of option
IN PM_OPTDEFLIST OptDefList // list of option definitions
)
{
DWORD Err, Err2;
ARRAY_LOCATION Loc;
PM_OPTDEF ThisOptDef;
for( // save each opt definition
Err = MemArrayInitLoc(&OptDefList->OptDefArray, &Loc)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(&OptDefList->OptDefArray, &Loc)
) {
//= require ERROR_SUCCESS == Err
Err = MemArrayGetElement(&OptDefList->OptDefArray, &Loc, &ThisOptDef);
//= require ERROR_SUCCESS == Err && NULL != ThisOptDef
Err = DhcpRegSaveOptDef // save the option def
(
/* OptId */ ThisOptDef->OptId,
/* ClassName */ ClassName,
/* Name */ ThisOptDef->OptName,
/* Comment */ ThisOptDef->OptComment,
/* OptType */ ThisOptDef->Type,
/* OptVal */ ThisOptDef->OptVal,
/* OptLen */ ThisOptDef->OptValLen
);
if( ERROR_SUCCESS != Err ) return Err; // reg. err saving opt def
}
return ERROR_SUCCESS; // everything went fine.
}
DWORD
SaveServerOptdefs( // save all the opt def's onto registry
IN PREG_HANDLE Server, // registry handle to server config.
IN PM_OPTCLASSDEFLIST Optdefs
)
{
DWORD Err, Err2;
ARRAY_LOCATION Loc;
PM_OPTCLASSDEFL_ONE ThisOptClass;
LPWSTR ClassName;
PM_CLASSDEF ClassDef;
for( // save each opt definition
Err = MemArrayInitLoc(&Optdefs->Array, &Loc)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(&Optdefs->Array, &Loc)
) {
//= require ERROR_SUCCESS == Err
Err = MemArrayGetElement(&Optdefs->Array, &Loc, &ThisOptClass);
//= require ERROR_SUCCESS == Err && NULL != ThisClass
if( 0 == ThisOptClass->ClassId ) { // no class for this option
ClassName = NULL;
} else { // lookup class in this server struct
Err = MemServerGetClassDef(
CurrentServer, // need to pass this as param
ThisOptClass->ClassId,
NULL,
0,
NULL,
&ClassDef
);
if( ERROR_SUCCESS != Err) return Err; // could not find the class? invalid struct
ClassName = ClassDef->Name; // found the class, use this name
}
Err = SaveServerOptDefs1(Server, ClassName, &ThisOptClass->OptDefList);
if( ERROR_SUCCESS != Err) return Err; // could not save some opt definition..
}
return ERROR_SUCCESS; // everything went fine.
}
DWORD
SaveServerOptions1( // save some option
IN PREG_HANDLE Server, // registry handle to server config.
IN LPWSTR ClassName, // name of the class of option
IN PM_OPTLIST OptList // list of options
)
{
DWORD Err, Err2;
ARRAY_LOCATION Loc;
PM_OPTION ThisOpt;
for( // save each option
Err = MemArrayInitLoc(OptList, &Loc)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(OptList, &Loc)
) {
//= require ERROR_SUCCESS == Err
Err = MemArrayGetElement(OptList, &Loc, &ThisOpt);
//= require ERROR_SUCCESS == Err && NULL != ThisOpt
Err = DhcpRegSaveGlobalOption // save the option
(
/* OptId */ ThisOpt->OptId,
/* ClassName */ ClassName,
/* Value */ ThisOpt->Val,
/* ValueSize */ ThisOpt->Len
);
if( ERROR_SUCCESS != Err ) return Err; // reg. err saving option
}
return ERROR_SUCCESS; // everything went fine.
}
DWORD
SaveServerOptions( // save all options onto registry
IN PREG_HANDLE Server, // registry handle to server config.
IN PM_OPTCLASS Options
)
{
DWORD Err, Err2;
ARRAY_LOCATION Loc;
PM_ONECLASS_OPTLIST ThisOptClass;
LPWSTR ClassName;
PM_CLASSDEF ClassDef;
for( // save each class definition
Err = MemArrayInitLoc(&Options->Array, &Loc)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(&Options->Array, &Loc)
) {
//= require ERROR_SUCCESS == Err
Err = MemArrayGetElement(&Options->Array, &Loc, &ThisOptClass);
//= require ERROR_SUCCESS == Err && NULL != ThisOptClass
if( 0 == ThisOptClass->ClassId ) { // no class for this option
ClassName = NULL;
} else { // lookup class in this server struct
Err = MemServerGetClassDef(
CurrentServer, // need to pass this as param
ThisOptClass->ClassId,
NULL,
0,
NULL,
&ClassDef
);
if( ERROR_SUCCESS != Err) return Err; // could not find the class? invalid struct
ClassName = ClassDef->Name; // found the class, use this name
}
Err = SaveServerOptions1(Server, ClassName, &ThisOptClass->OptList);
if( ERROR_SUCCESS != Err) return Err; // could not save some option..
}
return ERROR_SUCCESS; // everything went fine.
}
DWORD
SaveServerScope( // save unicast-or-mcast scope onto reg.
IN PREG_HANDLE ServerHdl, // registry handle to server config
IN PM_SERVER MemServer, // server object in memory
IN LPVOID Scope, // either PM_SUBNET or PM_MSCOPE object
IN BOOL fSubnet // TRUE ==> Subnet type, FALSE ==> MScope type.
)
{
DWORD Err;
PM_SUBNET Subnet = Scope;
PM_MSCOPE Subnet = MScope;
PM_SSCOPE SScope;
if( fSubnet ) { // if subnet, need to add it to superscope..
if( 0 != Subnet->SuperScopeId ) { // this belongs to superscope?
Err = MemServerFindSScope(MemServer, Subnet->SuperScopeId, NULL, &SScope);
if( ERROR_SUCCESS != Err ) { // wrong superscope? invlaid data
return Err;
}
Err = DhcpRegSScopeSaveSubnet(SScope->Name, Subnet->Address);
if( ERROR_SUCCESS != Err ) return Err;// could not add subnet to superscope?
}
}
if( fSubnet ) {
Err = DhcpRegSaveSubnet // save this subnet
(
/* SubnetAddress */ Subnet->Address,
/* SubnetMask */ Subnet->Mask,
/* SubnetState */ Subnet->State,
/* SubnetName */ Subnet->Name
/* SubnetComment */ Subnet->Description
);
} else {
Err = DhcpRegSaveMScope // save this mcast scope
(
/* MScopeId */ MScope->MScopeId,
/* SubnetState */ MScope->State,
/* AddressPolicy */ MScope->Policy,
/* TTL */ MScope->TTL,
/* pMScopeName */ MScope->Name,
/* pMScopeComment */ MScope->Description,
/* LangTag */ MScope->LangTag,
/* ExpiryTime */ &MScope->ExpiryTime
);
}
if( ERROR_SUCCESS != Err ) return Err; // could not save subnet info?
}
DWORD
SaveServerScopes( // save unicast-or-mcast scopes onto reg.
IN PREG_HANDLE Server, // registry handle to server config.
IN PARRAY Scopes, // array of PM_SUBNET or PM_MSCOPE types
IN BOOL fSubnet // TRUE ==> Subnet type, FALSE ==> MScope type.
)
{
DWORD Err;
ARRAY_LOCATION Loc;
PM_SUBNET Subnet;
for( // save each scope..
Err = MemArrayInitLoc(Scopes, &Loc)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(Scopes, &Loc)
) {
//= require ERROR_SUCCESS == Err
Err = MemArrayGetElement(Scopes, &Loc, &Subnet);
//= require ERROR_SUCCESS == Err && NULL != Subnet
Err = SaveServerScope(Server, CurrentServer, Subnet, fSubnet);
if( ERROR_SUCCESS != Err ) return Err; // could not save the subnet/m-scope..
}
return ERROR_SUCCESS;
}
DWORD
SaveServerSubnets( // save all subnet info onto reg.
IN PREG_HANDLE Server, // registry handle to server config.
IN PARRAY Subnets // array of type PM_SUBNET elements
)
{
return SaveServerScopes(Server, Subnets, TRUE); // call common routine
}
DWORD
SaveServerMScopes( // save all the m-cast scopes onto reg.
IN PREG_HANDLE Server, // registry handle to server config.
IN PARRAY MScopes // array of type PM_MSCOPE elements
)
{
return SaveServerScopes(Server, MScopes, FALSE); // call common routine
}
DWORD
DownloadServerInfoFromDs( // save the server info onto registry
IN PM_SERVER Server // the server to save onto registry
)
{
DWORD Err;
REG_HANDLE Hdl, Hdl2;
ARRAY_LOCATION Loc;
CurrentServer = Server; // this global used by several funcs above..
Err = DhcpRegGetThisServer(&Hdl); // get current server hdl
if( ERROR_SUCCESS != Err ) return Err;
Err = DhcpRegServerSetAttributes // set server attributes
(
/* PREG_HANDLE Hdl */ &Hdl,
/* LPWSTR *Name */ &Server->Name,
/* LPWSTR *Comment */ &Server->Comment,
/* DWORD *Flags */ &Server->State
);
// ignore errors..
Err = SaveServerClasses(&Hdl, &Server->ClassDefs);
if( ERROR_SUCCESS == Err ) { // saved classes? save optdefs..
Err = SaveServerOptdefs(&Hdl, &Server->OptDefs);
}
if( ERROR_SUCCESS == Err ) { // saved optdefs? save options..
Err = SaveServerOptions(&Hdl, &Server->Options);
}
if( ERROR_SUCCESS == Err ) { // saved options? save subnets..
Err = SaveServerSubnets(&Hdl, &Server->Subnets);
}
if( ERROR_SUCCESS == Err ) { // saved subnets? save mcast scopes
Err = SaveServerMScopes(&Hdl, &Server->MScopes);
}
(void)DhcpRegCloseHdl(&Hdl); // free resource
return Err;
}
#endif 0
DWORD
DownloadServerInfoFromDs( // save the server info onto registry
IN PM_SERVER Server // the server to save onto registry
)
{
return DhcpRegServerSave(Server);
}
DWORD
DownloadFromDsForReal( // really try to downlaod from DS
IN LPWSTR ServerName
)
{
DWORD Err, Err2;
ARRAY Servers;
ARRAY_LOCATION Loc;
PM_SERVER ThisServer;
Err = MemArrayInit(&Servers); // initialize array
if( ERROR_SUCCESS != Err ) return Err;
Err = DhcpDsGetEnterpriseServers // fetch the server info from DS
(
/* Reserved */ ZeroReserved,
/* ServerName */ ServerName,
/* Servers */ &Servers
);
Err2 = ERROR_SUCCESS; // init return value
for( // process all the information
Err = MemArrayInitLoc(&Servers, &Loc)
; ERROR_FILE_NOT_FOUND != Err ;
Err = MemArrayNextLoc(&Servers, &Loc)
) {
//= require ERROR_SUCCESS == Err
Err = MemArrayGetElement(&Servers, &Loc, &ThisServer);
//= require ERROR_SUCCESS == Err && NULL != ThisServer
Err = DownloadServerInfoFromDs(ThisServer);
if( ERROR_SUCCESS != Err ) { // oops.. could not do it?
Err2 = Err; // store error..
}
MemServerFree(ThisServer); // free all this memory.
}
Err = MemArrayCleanup(&Servers); // free mem allcoated for array
if( ERROR_SUCCESS != Err ) Err2 = Err; // something went wrong?
return Err2;
}
//================================================================================
// the only exported function is this.
//================================================================================
VOID
DhcpRegDownloadDs( // safe download of stuff onto registry
IN LPWSTR ServerName // name of dhcp servre to download for
)
{
DWORD Err;
Err = PrepareRegistryForDsDownload(); // prepare the Config.DS key and stuff..
if( ERROR_SUCCESS != Err ) return; // oops, could not even do this?
Err = DownloadFromDsForReal(ServerName); // actually try to download from DS.
if( ERROR_SUCCESS == Err ) { // could actually download successfully
CopyDsConfigToNormalConfig(); // now copy this configuration to nrml loc.
}
CleanupAfterDownload(); // now cleanup the regsitry handles etc..
DhcpRegUpdateTime(); // fix the time stamp to now..
}
//================================================================================
// end of file
//================================================================================