--- a/Win32OperatingSystem.st Mon Jan 12 14:15:25 2015 +0100
+++ b/Win32OperatingSystem.st Mon Jan 12 14:54:50 2015 +0100
@@ -14,6 +14,8 @@
"
"{ Package: 'stx:libbasic' }"
+"{ NameSpace: Smalltalk }"
+
AbstractOperatingSystem subclass:#Win32OperatingSystem
instanceVariableNames:''
classVariableNames:'Initialized HostName DomainName CurrentDirectory LastOsTimeLow
@@ -1585,6 +1587,112 @@
result = 0 ifTrue: [ ^self error ].
! !
+!Win32OperatingSystem class methodsFor:'directory access'!
+
+linkInfoFor:osPathname fileSize:fileSize fileAttributes:osFileAttributes osCrtTime:osCrtTime osAccTime:osAccTime osModTime:osModTime
+ |type modeBits crtTime accTime modTime|
+
+%{
+ DWORD __fileAttr = __unsignedLongIntVal( osFileAttributes );
+ int __modeBits = 0;
+
+ if (__fileAttr & FILE_ATTRIBUTE_DIRECTORY) {
+ type = @symbol(directory);
+ __modeBits = 0777; /* executable and WRITABLE - refer to comment in #isWritable: */
+ } else if (__fileAttr & FILE_ATTRIBUTE_REPARSE_POINT) {
+ type = @symbol(symbolicLink);
+ __modeBits = 0777; /* even in UNIX symlinks have 0777 */
+ } else {
+ type = @symbol(regular);
+ if (__fileAttr & FILE_ATTRIBUTE_READONLY) {
+ __modeBits = 0444;
+ } else {
+ __modeBits = 0666;
+ }
+ }
+ modeBits = __mkSmallInteger(__modeBits);
+
+%}.
+ osCrtTime isNil
+ ifTrue: [crtTime := Timestamp now]
+ ifFalse:[crtTime := Timestamp new fromOSTime:(osCrtTime - OperatingSystem osTimeOf19700101)].
+
+ osAccTime isNil
+ ifTrue: [accTime := Timestamp now]
+ ifFalse:[accTime := Timestamp new fromOSTime:(osAccTime - OperatingSystem osTimeOf19700101)].
+
+ osModTime isNil
+ ifTrue: [modTime := accTime]
+ ifFalse:[modTime := Timestamp new fromOSTime:(osModTime - OperatingSystem osTimeOf19700101)].
+
+ ^ FileStatusInfo
+ type:type
+ mode:modeBits
+ uid:nil
+ gid:nil
+ size:fileSize
+ id:0
+ accessed:accTime
+ modified:modTime
+ created:crtTime
+ sourcePath:(osPathname asSingleByteStringIfPossible)
+ fullName:nil
+ alternativeName:nil.
+!
+
+nextLinkInfoFrom:aDirectoryStream dirPointer:dirPointer
+ "return the next FileStatusInfo or nil at the end"
+
+ |resultInfo error fileSize osPathname osModTime osCrtTime osAccTime osFileAttributes|
+
+%{
+ HANDLE d;
+ WIN32_FIND_DATAW data;
+ int rslt;
+
+ if ((dirPointer != nil)
+ && __isExternalAddressLike(dirPointer)) {
+ // __INST(lastErrorNumber) = nil;
+ d = __externalAddressVal(dirPointer);
+
+ do {
+ __threadErrno = 0;
+ rslt = STX_API_NOINT_CALL2( "FindNextFileW", FindNextFileW, d, &data );
+ } while ((rslt < 0) && (__threadErrno == EINTR));
+
+ if (rslt > 0) {
+ fileSize = __MKLARGEINT64(1, data.nFileSizeLow, data.nFileSizeHigh);
+ osPathname = __MKU16STRING( data.cFileName );
+ osFileAttributes = __mkSmallInteger( data.dwFileAttributes );
+
+ osCrtTime = FileTimeToOsTime(&data.ftCreationTime);
+ osAccTime = FileTimeToOsTime(&data.ftLastAccessTime);
+ osModTime = FileTimeToOsTime(&data.ftLastWriteTime);
+
+ } else {
+ error = __mkSmallInteger( __threadErrno );
+ }
+ }
+%}.
+ (error notNil and:[error ~~ 0]) ifTrue:[
+ ^ StreamIOError newException
+ errorCode:error;
+ osErrorHolder:(OperatingSystem errorHolderForNumber:error);
+ parameter:aDirectoryStream;
+ raiseRequest
+ ].
+
+ osPathname isNil ifTrue:[^ nil].
+
+ ^ self
+ linkInfoFor:osPathname
+ fileSize:fileSize
+ fileAttributes:osFileAttributes
+ osCrtTime:osCrtTime
+ osAccTime:osAccTime
+ osModTime:osModTime
+! !
+
!Win32OperatingSystem class methodsFor:'error messages'!
currentErrorNumber
@@ -8658,44 +8766,44 @@
DWORD exitCode;
if (__isExternalAddressLike(processHandleOrPid) ) {
- processHandle = _HANDLEVal(processHandleOrPid);
- if (processHandle == 0) {
- error = @symbol(invalidParameter);
- goto out;
- }
+ processHandle = _HANDLEVal(processHandleOrPid);
+ if (processHandle == 0) {
+ error = @symbol(invalidParameter);
+ goto out;
+ }
} else if( __isSmallInteger(processHandleOrPid) ) {
- // assume, that synchronize needs less privilege...
- processHandle = processHandleToClose = OpenProcess(SYNCHRONIZE, FALSE, __smallIntegerVal(processHandleOrPid));
- if (!processHandle) {
- goto checkError;
- }
+ // assume, that synchronize needs less privilege...
+ processHandle = processHandleToClose = OpenProcess(SYNCHRONIZE, FALSE, __smallIntegerVal(processHandleOrPid));
+ if (!processHandle) {
+ goto checkError;
+ }
} else {
- error = @symbol(invalidParameter);
- goto out;
+ error = @symbol(invalidParameter);
+ goto out;
}
/* check if the handle still refers to a running process */
if (GetExitCodeProcess(processHandle, &exitCode) != 0) {
- if (processHandleToClose)
- CloseHandle(processHandleToClose);
- if (exitCode == STILL_ACTIVE) {
- RETURN(true);
- } else {
- RETURN(false);
- }
+ if (processHandleToClose)
+ CloseHandle(processHandleToClose);
+ if (exitCode == STILL_ACTIVE) {
+ RETURN(true);
+ } else {
+ RETURN(false);
+ }
} else if (processHandleToClose) {
- CloseHandle(processHandleToClose);
+ CloseHandle(processHandleToClose);
}
checkError:
err = GetLastError();
// we do not have access to the process (so pid does exist ;-))
if (err == ERROR_ACCESS_DENIED) {
- RETURN(true);
+ RETURN(true);
}
// pid does not exist
if (err == ERROR_INVALID_PARAMETER) {
- RETURN(false);
+ RETURN(false);
}
// any other error - raise signal
@@ -10339,7 +10447,7 @@
AbstractOperatingSystem::TimeZoneInformation for details"
^ self getTimezoneInformationForYear:nil.
-
+
"
self getTimezoneInformation
"
@@ -10364,54 +10472,54 @@
WCHAR nm[33];
if (anIntegerOrNil == nil) {
- retVal = GetTimeZoneInformation(&tzInfo);
- switch (retVal) {
- case TIME_ZONE_ID_STANDARD:
- case TIME_ZONE_ID_DAYLIGHT:
- case TIME_ZONE_ID_UNKNOWN:
- break;
-
- default:
- case TIME_ZONE_ID_INVALID:
- error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
- goto out;
- }
+ retVal = GetTimeZoneInformation(&tzInfo);
+ switch (retVal) {
+ case TIME_ZONE_ID_STANDARD:
+ case TIME_ZONE_ID_DAYLIGHT:
+ case TIME_ZONE_ID_UNKNOWN:
+ break;
+
+ default:
+ case TIME_ZONE_ID_INVALID:
+ error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
+ goto out;
+ }
} else if (__isSmallInteger(anIntegerOrNil)) {
- int year = __intVal(anIntegerOrNil);
+ int year = __intVal(anIntegerOrNil);
#ifdef __BORLANDC__
- {
- typedef BOOL (WINAPI *P_GetTimeZoneInformationForYear)(
- USHORT,
- LPTIME_ZONE_INFORMATION, // - should be, but is not defined: PDYNAMIC_TIME_ZONE_INFORMATION,
- LPTIME_ZONE_INFORMATION);
- static P_GetTimeZoneInformationForYear pGetTimeZoneInformationForYear;
-
- if (pGetTimeZoneInformationForYear == NULL) {
- pGetTimeZoneInformationForYear =
- (P_GetTimeZoneInformationForYear)GetProcAddress(GetModuleHandle("kernel32.dll"), "GetTimeZoneInformationForYear");
- if (pGetTimeZoneInformationForYear == NULL) {
- pGetTimeZoneInformationForYear = false;
- }
- }
- if (pGetTimeZoneInformationForYear == false) {
- error = __mkSmallInteger(@symbol(primitiveFailed));
- goto out;
- } else {
- if (!pGetTimeZoneInformationForYear(year, NULL, &tzInfo)) {
- error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
- goto out;
- }
- }
- }
-#else
- if (!GetTimeZoneInformationForYear(year, NULL, &tzInfo)) {
- error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
- goto out;
- }
+ {
+ typedef BOOL (WINAPI *P_GetTimeZoneInformationForYear)(
+ USHORT,
+ LPTIME_ZONE_INFORMATION, // - should be, but is not defined: PDYNAMIC_TIME_ZONE_INFORMATION,
+ LPTIME_ZONE_INFORMATION);
+ static P_GetTimeZoneInformationForYear pGetTimeZoneInformationForYear;
+
+ if (pGetTimeZoneInformationForYear == NULL) {
+ pGetTimeZoneInformationForYear =
+ (P_GetTimeZoneInformationForYear)GetProcAddress(GetModuleHandle("kernel32.dll"), "GetTimeZoneInformationForYear");
+ if (pGetTimeZoneInformationForYear == NULL) {
+ pGetTimeZoneInformationForYear = false;
+ }
+ }
+ if (pGetTimeZoneInformationForYear == false) {
+ error = __mkSmallInteger(@symbol(primitiveFailed));
+ goto out;
+ } else {
+ if (!pGetTimeZoneInformationForYear(year, NULL, &tzInfo)) {
+ error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
+ goto out;
+ }
+ }
+ }
+#else
+ if (!GetTimeZoneInformationForYear(year, NULL, &tzInfo)) {
+ error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
+ goto out;
+ }
#endif
} else {
- error = @symbol(badArgument);
- goto out;
+ error = @symbol(badArgument);
+ goto out;
}
bias = __mkSmallInteger(tzInfo.Bias);
@@ -10441,22 +10549,22 @@
out:;
%}.
error notNil ifTrue:[
- self primitiveFailed:error.
+ self primitiveFailed:error.
].
info := self timeZoneInfoClass new.
info
- bias:bias
- name:standardName standardBias:standardBias
- daylightName:daylightName daylightBias:daylightBias.
+ bias:bias
+ name:standardName standardBias:standardBias
+ daylightName:daylightName daylightBias:daylightBias.
standardDate_m ~~ 0 ifTrue:[
- info standardYear:standardDate_y standardMonth:standardDate_m standardDay:standardDate_d
- standardWeekDay:standardDate_wd standardHour:standardDate_h standardMinute:standardDate_min.
+ info standardYear:standardDate_y standardMonth:standardDate_m standardDay:standardDate_d
+ standardWeekDay:standardDate_wd standardHour:standardDate_h standardMinute:standardDate_min.
].
daylightDate_m ~~ 0 ifTrue:[
- info daylightYear:daylightDate_y daylightMonth:daylightDate_m daylightDay:daylightDate_d
- daylightWeekDay:daylightDate_wd daylightHour:daylightDate_h daylightMinute:daylightDate_min.
+ info daylightYear:daylightDate_y daylightMonth:daylightDate_m daylightDay:daylightDate_d
+ daylightWeekDay:daylightDate_wd daylightHour:daylightDate_h daylightMinute:daylightDate_min.
].
^ info
@@ -10525,115 +10633,115 @@
/* try cache */
{
- OBJ lastOsTimeLow, lastOsTimeHi, lastTimeInfo;
-
- lastOsTimeLow = @global(LastOsTimeLow);
- lastOsTimeHi = @global(LastOsTimeHi);
- if (__isInteger(lastOsTimeLow)
- && (__unsignedLongIntVal(lastOsTimeLow) == __unsignedLongIntVal(tLow))
- && lastOsTimeHi
- && (__unsignedLongIntVal(lastOsTimeHi) == __unsignedLongIntVal(tHigh))
- && (@global(LastTimeInfoIsLocal) == isLocalTime)
- ) {
- lastTimeInfo = @global(LastTimeInfo);
- if (lastTimeInfo != nil) {
- RETURN (lastTimeInfo);
- }
- }
+ OBJ lastOsTimeLow, lastOsTimeHi, lastTimeInfo;
+
+ lastOsTimeLow = @global(LastOsTimeLow);
+ lastOsTimeHi = @global(LastOsTimeHi);
+ if (__isInteger(lastOsTimeLow)
+ && (__unsignedLongIntVal(lastOsTimeLow) == __unsignedLongIntVal(tLow))
+ && lastOsTimeHi
+ && (__unsignedLongIntVal(lastOsTimeHi) == __unsignedLongIntVal(tHigh))
+ && (@global(LastTimeInfoIsLocal) == isLocalTime)
+ ) {
+ lastTimeInfo = @global(LastTimeInfo);
+ if (lastTimeInfo != nil) {
+ RETURN (lastTimeInfo);
+ }
+ }
}
if (!OsTimeToFileTime(tLow, tHigh, &fileTime))
- goto out;
+ goto out;
if (!FileTimeToSystemTime(&fileTime, &sysTime))
- goto out;
+ goto out;
if (isLocalTime == false) { // easy: UTC time
- sysTimePtr = &sysTime;
- utcOffset = __mkSmallInteger(0);
- isDst = false;
+ sysTimePtr = &sysTime;
+ utcOffset = __mkSmallInteger(0);
+ isDst = false;
} else { // local time: have to convert and find out about DST
- TIME_ZONE_INFORMATION tzInfo;
- LONGLONG longTime;
- SYSTEMTIME localSysTime;
- FILETIME localFileTime;
-
- sysTimePtr = &localSysTime;
-
- if (!SystemTimeToTzSpecificLocalTime(NULL, &sysTime, &localSysTime))
- goto out;
- if (!SystemTimeToFileTime(&localSysTime, &localFileTime))
- goto out;
-
- // all the rest is computing the UTC offset and whether DST applies
- longTime = ((LONGLONG)fileTime.dwHighDateTime << 32) + fileTime.dwLowDateTime;
- longTime -= ((LONGLONG)localFileTime.dwHighDateTime << 32) + localFileTime.dwLowDateTime;
-
- // utcOffset is the difference from UTC to local time including possible DST
- _utcOffset = longTime / 10000000;
- utcOffset = __mkSmallInteger(_utcOffset);
+ TIME_ZONE_INFORMATION tzInfo;
+ LONGLONG longTime;
+ SYSTEMTIME localSysTime;
+ FILETIME localFileTime;
+
+ sysTimePtr = &localSysTime;
+
+ if (!SystemTimeToTzSpecificLocalTime(NULL, &sysTime, &localSysTime))
+ goto out;
+ if (!SystemTimeToFileTime(&localSysTime, &localFileTime))
+ goto out;
+
+ // all the rest is computing the UTC offset and whether DST applies
+ longTime = ((LONGLONG)fileTime.dwHighDateTime << 32) + fileTime.dwLowDateTime;
+ longTime -= ((LONGLONG)localFileTime.dwHighDateTime << 32) + localFileTime.dwLowDateTime;
+
+ // utcOffset is the difference from UTC to local time including possible DST
+ _utcOffset = longTime / 10000000;
+ utcOffset = __mkSmallInteger(_utcOffset);
# ifdef __BORLANDC__
- {
- typedef BOOL (WINAPI *P_GetTimeZoneInformationForYear)(
- USHORT,
- LPTIME_ZONE_INFORMATION, // - should be, but is not defined: PDYNAMIC_TIME_ZONE_INFORMATION,
- LPTIME_ZONE_INFORMATION);
- static P_GetTimeZoneInformationForYear pGetTimeZoneInformationForYear;
-
- if (pGetTimeZoneInformationForYear == NULL) {
- pGetTimeZoneInformationForYear =
- (P_GetTimeZoneInformationForYear)GetProcAddress(GetModuleHandle("kernel32.dll"), "GetTimeZoneInformationForYear");
- if (pGetTimeZoneInformationForYear == NULL) {
- // ignore this error and fall back to GetTimeZoneInformation()
- reason = @symbol(NoGetTimeZoneInformationForYear);
- pGetTimeZoneInformationForYear = false;
- }
- }
- if (pGetTimeZoneInformationForYear != false) {
- if (pGetTimeZoneInformationForYear(localSysTime.wYear, NULL, &tzInfo)) {
- _stdUtcOffset = (tzInfo.Bias + tzInfo.StandardBias) * 60;
- isDst = (_stdUtcOffset != _utcOffset) ? true : false;
- } else {
- // ignore this error and fall back to GetTimeZoneInformation()
- reason = @symbol(GetTimeZoneInformationForYearFailed);
- error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
- }
- }
- }
+ {
+ typedef BOOL (WINAPI *P_GetTimeZoneInformationForYear)(
+ USHORT,
+ LPTIME_ZONE_INFORMATION, // - should be, but is not defined: PDYNAMIC_TIME_ZONE_INFORMATION,
+ LPTIME_ZONE_INFORMATION);
+ static P_GetTimeZoneInformationForYear pGetTimeZoneInformationForYear;
+
+ if (pGetTimeZoneInformationForYear == NULL) {
+ pGetTimeZoneInformationForYear =
+ (P_GetTimeZoneInformationForYear)GetProcAddress(GetModuleHandle("kernel32.dll"), "GetTimeZoneInformationForYear");
+ if (pGetTimeZoneInformationForYear == NULL) {
+ // ignore this error and fall back to GetTimeZoneInformation()
+ reason = @symbol(NoGetTimeZoneInformationForYear);
+ pGetTimeZoneInformationForYear = false;
+ }
+ }
+ if (pGetTimeZoneInformationForYear != false) {
+ if (pGetTimeZoneInformationForYear(localSysTime.wYear, NULL, &tzInfo)) {
+ _stdUtcOffset = (tzInfo.Bias + tzInfo.StandardBias) * 60;
+ isDst = (_stdUtcOffset != _utcOffset) ? true : false;
+ } else {
+ // ignore this error and fall back to GetTimeZoneInformation()
+ reason = @symbol(GetTimeZoneInformationForYearFailed);
+ error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
+ }
+ }
+ }
# else
- if (GetTimeZoneInformationForYear(localSysTime.wYear, NULL, &tzInfo)) {
- _stdUtcOffset = (tzInfo.Bias + tzInfo.StandardBias) * 60;
- isDst = (_stdUtcOffset != _utcOffset) ? true : false;
- } else {
- // ignore this error and fall back to GetTimeZoneInformation()
- reason = @symbol(GetTimeZoneInformationForYearFailed);
- error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
- }
-# endif
- // this code is a fallback for WIN XP
- if (isDst == nil) {
- DWORD retVal = GetTimeZoneInformation(&tzInfo);
- switch (retVal) {
- case TIME_ZONE_ID_STANDARD:
- case TIME_ZONE_ID_DAYLIGHT:
- case TIME_ZONE_ID_UNKNOWN:
- // nonDstOffset is the difference from UTC to local time without DST
- _stdUtcOffset = (tzInfo.Bias + tzInfo.StandardBias) * 60;
- isDst = (_stdUtcOffset != _utcOffset) ? true : false;
- break;
-
- // these are errors, which may occur, if the
- // Windows OS has not been setupm correctly.
- // We ignore these errors here, but we don't know if DST applies.
- // Assume that there is no DST.
- default:
- case TIME_ZONE_ID_INVALID:
- isDst = false;
- reason = @symbol(TIME_ZONE_ID_INVALID);
- error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
- break;
- }
- } // End WINXP backward compatibility
+ if (GetTimeZoneInformationForYear(localSysTime.wYear, NULL, &tzInfo)) {
+ _stdUtcOffset = (tzInfo.Bias + tzInfo.StandardBias) * 60;
+ isDst = (_stdUtcOffset != _utcOffset) ? true : false;
+ } else {
+ // ignore this error and fall back to GetTimeZoneInformation()
+ reason = @symbol(GetTimeZoneInformationForYearFailed);
+ error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
+ }
+# endif
+ // this code is a fallback for WIN XP
+ if (isDst == nil) {
+ DWORD retVal = GetTimeZoneInformation(&tzInfo);
+ switch (retVal) {
+ case TIME_ZONE_ID_STANDARD:
+ case TIME_ZONE_ID_DAYLIGHT:
+ case TIME_ZONE_ID_UNKNOWN:
+ // nonDstOffset is the difference from UTC to local time without DST
+ _stdUtcOffset = (tzInfo.Bias + tzInfo.StandardBias) * 60;
+ isDst = (_stdUtcOffset != _utcOffset) ? true : false;
+ break;
+
+ // these are errors, which may occur, if the
+ // Windows OS has not been setupm correctly.
+ // We ignore these errors here, but we don't know if DST applies.
+ // Assume that there is no DST.
+ default:
+ case TIME_ZONE_ID_INVALID:
+ isDst = false;
+ reason = @symbol(TIME_ZONE_ID_INVALID);
+ error = __mkSmallInteger(__WIN32_ERR(GetLastError()));
+ break;
+ }
+ } // End WINXP backward compatibility
}
hour = __mkSmallInteger(sysTimePtr->wHour);
@@ -10652,22 +10760,22 @@
"/ '--> REASON: ' errorPrint. reason errorPrintCR. '--> ERROR: ' errorPrint. error errorPrintCR.
year isNil ifTrue:[
- TimeConversionError raiseErrorString:' - out of range'.
+ TimeConversionError raiseErrorString:' - out of range'.
].
info := self timeInfoClass new.
info
- year:year
- month:month
- day:day
- hours:hour
- minutes:minute
- seconds:second
- milliseconds:millis
- utcOffset:utcOffset
- dst:isDst
- dayInYear:yDay
- dayInWeek:weekDay.
+ year:year
+ month:month
+ day:day
+ hours:hour
+ minutes:minute
+ seconds:second
+ milliseconds:millis
+ utcOffset:utcOffset
+ dst:isDst
+ dayInYear:yDay
+ dayInWeek:weekDay.
LastTimeInfo := info.
LastOsTimeLow := tLow.
@@ -12041,6 +12149,10 @@
^ size
!
+sourcePath
+ ^ sourcePath
+!
+
statusChangeTime
"return statusChanged"
@@ -12257,6 +12369,11 @@
isUnknown
^ type == #unknown
+!
+
+isValid
+ "answer true if the fileInfo is valid"
+ ^ type notNil
! !
!Win32OperatingSystem::OSProcessStatus class methodsFor:'documentation'!
@@ -17800,15 +17917,15 @@
!Win32OperatingSystem class methodsFor:'documentation'!
version
- ^ '$Header: /cvs/stx/stx/libbasic/Win32OperatingSystem.st,v 1.516 2014-12-02 10:22:14 stefan Exp $'
+ ^ '$Header: /cvs/stx/stx/libbasic/Win32OperatingSystem.st,v 1.517 2015-01-12 13:54:50 ca Exp $'
!
version_CVS
- ^ '$Header: /cvs/stx/stx/libbasic/Win32OperatingSystem.st,v 1.516 2014-12-02 10:22:14 stefan Exp $'
+ ^ '$Header: /cvs/stx/stx/libbasic/Win32OperatingSystem.st,v 1.517 2015-01-12 13:54:50 ca Exp $'
!
version_SVN
- ^ '$Id: Win32OperatingSystem.st,v 1.516 2014-12-02 10:22:14 stefan Exp $'
+ ^ '$Id: Win32OperatingSystem.st,v 1.517 2015-01-12 13:54:50 ca Exp $'
! !