Showing posts with label unix. Show all posts
Showing posts with label unix. Show all posts

Wednesday, August 18, 2010

Fun with detailed files - and stat

In a previous post, I tried to find a way to convert Unix user and group ids into their corresponding names. After I reported my findings to the use-revolution mailing list, Andre Garzia stepped in, suggesting to take a look at the stat command.

Calling 'stat' through the shell function for every file would be costly and slow; using it to fetch the information for an entire folder in one go wouldn't be so bad, but if you already use the detailed files function built into revTalk, it would seem like a waste of cycles. So I decided to rewrite my work handlers, employing a caching mechanism for the id-to-name mapping, and using 'stat' to pick up the mapping information not available in the cache.
--> script-local 'static' variables

local sGroupIdMap, sUserIdMap

--> public functions and commands

command LoadIdMaps
p_LoadGroupIdMap
p_LoadUserIdMap
end LoadIdMaps

function GroupIdToName pGroupId, pFile
local tName, tShellCommand
put sGroupIdMap[pGroupId] into tName
if tName is empty and the platform is "MacOS" then
-- stat is only available on MacOS X
put p_GetFileStat(pFile, "%Sg") into tName
put tName into sGroupIdMap[pGroupId]
end if
return tName
end GroupIdToName

function UserIdToName pUserId, pFile
local tName, tShellCommand
put sUserIdMap[pUserId] into tName
if tName is empty and the platform is "MacOS" then
-- stat is only available on MacOS X
put p_GetFileStat(pFile, "%Su") into tName
put tName into sUserIdMap[pUserId]
end if
return tName
end UserIdToName

--> private functions and commands

private function p_GetFileStat pFile, pFormat
local tName, tShellCommand
-- make sure to escape spaces in the file name
replace space with backslash & space in pFile
put "stat -f" && pFormat && pFile into tShellCommand
put word 1 of shell(tShellCommand) into tName
return tName
end p_GetFileStat

private command p_LoadGroupIdMap
if the platform is "Win32" then exit p_LoadGroupIdMap
p_LoadFileIdMap "/etc/group", sGroupIdMap
end p_LoadGroupIdMap

private command p_LoadUserIdMap
if the platform is "Win32" then exit p_LoadUserIdMap
p_LoadFileIdMap "/etc/passwd", sUserIdMap
end p_LoadUserIdMap

private command p_LoadFileIdMap pFile, @pIdMap
local tData, tLine
put URL ("file:" & pFile) into tData
set the itemDelimiter to colon
repeat for each line tLine in tData
-- skip empty and comment lines
if tLine is empty \
or char 1 of tLine is "#" then next repeat
-- fill the id map
put item 1 of tLine into pIdMap[item 3 of tLine]
end repeat
end p_LoadFileIdMap

Armed with the above set of commands and functions, we can still use the detailed files function and obtain user and group names from a cache, calling into 'stat' as necessary. Let's update our sample button script:
on mouseUp
local tOldFolder, tNewFolder, tDetailedFiles
answer folder "Select a folder"
if the result is "Cancel" then exit mouseUp
put it into tNewFolder
--> extract the detailed files information
put the defaultFolder into tOldFolder
set the defaultFolder to tNewFolder
put the detailed files into tDetailedFiles
--> load the group and user id maps
LoadIdMaps
--> build the extended file information
local tDetailedFile, tExtendedFiles
local tFile, tUserId, tGroupId
repeat for each line tDetailedFile in tDetailedFiles
-- skip hidden files
if char 1 of tDetailedFile is "." then next repeat
-- fill the extended file entry
put urlDecode(item 1 of tDetailedFile) into tFile
put item 8 of tDetailedFile into tUserId
put item 9 of tDetailedFile into tGroupId
put tFileName & tab & \
UserIdToName(tUserId, tFile) & tab & \
GroupIdToName(tGroupId, tFile) & return \
after tExtendedFiles
end repeat
--> wrap things up
set the defaultFolder to tOldFolder
put char 1 to -2 of tExtendedFiles into field "Files"
end mouseUp

The more observant amongst you may think "If all you needed was a file's user and group name, why not just 'stat' and get it over with?" - well, the detailed files function returns a lot of useful information in one go, including the size of the resource fork of a file on MacOS systems.
Just one more thing: the 'stat' function was added in MacOS X 10.4 Tiger - so if your application still needs to support MacOS X 10.3 Panther, you're going to have to merge in the 'nireport' call from the previous post. Consider it an exercise for the reader.

On to the next challenge!

Tuesday, August 17, 2010

Fun with detailed files

Earlier today, the following question was posted on the use-revolution mailing list: file owner & group names. Rev veteran Monte Goulding was wondering how to convert file owner and group ids into actual user and group names. I happily logged into the Solaris server at work and dug around a bit, finally replying that he could parse this information from the following two files:
/etc/group
/etc/passwd

And what would such a parsing script look like in revTalk? I'm glad you asked:
command LoadGroupIdMap @pGroupIdMap
p_LoadFileIdMap "/etc/group", pGroupIdMap
end LoadGroupIdMap

command LoadUserIdMap @pUserIdMap
p_LoadFileIdMap "/etc/passwd", pUserIdMap
end LoadUserIdMap

private command p_LoadFileIdMap pFile, @pIdMap
local tData, tLine
put URL ("file:" & pFile) into tData
set the itemDelimiter to colon
repeat for each line tLine in tData
-- skip empty and comment lines
if tLine is empty \
or char 1 of tLine is "#" then next repeat
-- fill the id map
put item 1 of tLine into pIdMap[item 3 of tLine]
end repeat
end p_LoadFileIdMap

Given that both files use a colon as item delimiter, and have the name as first item and the id as third item, I extracted the parsing into a separate private command. Put the above handlers in your stack script, and then fetch each id-to-name-mapping before interpreting the detailed files.
Here's a simple button script that uses the above commands:
on mouseUp
local tOldFolder, tNewFolder, tDetailedFiles
answer folder "Select a folder"
if the result is "Cancel" then exit mouseUp
put it into tNewFolder
--> extract the detailed files information
put the defaultFolder into tOldFolder
set the defaultFolder to tNewFolder
put the detailed files into tDetailedFiles
set the defaultFolder to tOldFolder
--> load the group and user id maps
local tGroupIdMap, tUserIdMap
LoadGroupIdMap tGroupIdMap
LoadUserIdMap tUserIdMap
--> build the extended file information
local tDetailedFile, tExtendedFiles
repeat for each line tDetailedFile in tDetailedFiles
-- skip hidden files
if char 1 of tDetailedFile is "." then next repeat
-- fill the extended file entry
put urlDecode(item 1 of tDetailedFile) & tab & \
tUserIdMap[item 8 of tDetailedFile] & tab & \
tGroupIdMap[item 9 of tDetailedFile] & return \
after tExtendedFiles
end repeat
--> wrap things up
put char 1 to -2 of tExtendedFiles into field "Files"
end mouseUp

Once I got home, I wanted to verify this on my MacOS X machine, only to discover that it wasn't properly mapping the uid and gid to user and group names. Intrigued, I dug a bit deeper and found out that MacOS X actually relies on a DirectoryService when it's not running in single-user mode. Lovely, but how can we get what we need using revTalk?
Well, it turns out you can use the nireport command via the shell function to fetch a list of the group and user names with id. So let's add another helper command to do things the MacOS X way, and update our existing commands to use the correct approach, depending on the platform.
command LoadGroupIdMap @pGroupIdMap
if the platform is "MacOS" then
p_LoadNireportIdMap "/groups gid name", pGroupIdMap
else if the platform is "Linux" then
p_LoadFileIdMap "/etc/group", pGroupIdMap
end if
end LoadGroupIdMap

command LoadUserIdMap @pUserIdMap
if the platform is "MacOS" then
p_LoadNireportIdMap "/users uid name", pUserIdMap
else if the platform is "Linux" then
p_LoadFileIdMap "/etc/passwd", pUserIdMap
end if
end LoadUserIdMap

private command p_LoadFileIdMap pFile, @pIdMap
local tData, tLine
put URL ("file:" & pFile) into tData
set the itemDelimiter to colon
repeat for each line tLine in tData
-- skip empty and comment lines
if tLine is empty \
or char 1 of tLine is "#" then next repeat
-- fill the id map
put item 1 of tLine into pIdMap[item 3 of tLine]
end repeat
end p_LoadFileIdMap

private command p_LoadNireportIdMap pParams, @pIdMap
local tData, tLine
put shell ("nireport ." && pParams) into tData
set the itemDelimiter to tab
repeat for each line tLine in tData
put item 2 of tLine into pIdMap[item 1 of tLine]
end repeat
end p_LoadNireportIdMap

This seemed to do the trick on my iMac PowerPC G5 running MacOS X 10.4 Tiger - but when I tried it on my MacBook Pro Intel running MacOS X 10.6 Snow Leopard, it failed as there is no "nireport" command available. As it turns out, Apple dropped NetInfo when MacOS X 10.5 Leopard came out. So my solution is not quite complete, unfortunately.

To be continued...