Recursion Issue: Variable maintains values from lower in the stack.

Here’s a tough one, if you like a challenge.
(Or it’s an easy one, and I’ve been staring at it for too long.)
I’m working on a script that fixes POSIX and ACL permissions for every folder within a pre-determined directory structure. We’ve already set these permissions in a .csv file, which I’ve saved in the resources folder of the contents of this app (which is called PermissionScript. Not particularly clever, I know). The script traverses the folder structure, comparing the name and path of the folder against listings in the .csv.
This actually works very well.
Where I run into a problem is that, with ACLs, I want to sort of fake propagation. As you may know, ACLs can be inherited to sub-directories and -files, but only if those files and folders were added after the ACLs were put into place. In order to account for this, my logic was to maintain a list of the ACLs whose properties included inheritance (the inheritance_list), pass it as a variable to the recursive handler on the way down, and apply these ACLs along with the normal ones to lower folders.
Since this list was in a recursive handler, it seems obvious that any added items going down will disappear on the way back up.
Buuuuut…
For some reason, any time ANY item is added to the inheritance_list, it stays for the duration of the script.

Perhaps my actual code will explain it better. This is the Traversal handler:


-- For reference, here is an explanation of every variable passed into the Traversal() handler:
--thisFolder is the folder for which I am changing the permissions.
-- folderList is the list of POSIX permissions from the .csv.
-- ACLList is the list of ACL permissions from the .csv.
-- ID_folder is the number of the column in the POSIX permissions list where the "ID" info is found.
-- owner_folder is the number of the column in the POSIX permissions list where the "owner" info is found.
-- group_folder is the number of the column in the POSIX permissions list where the "group" info is found.
-- permNums_folder is the number of the column in the POSIX permissions list where the permission numbers are found. (ie "777" for read, write, execute for owner, group, and others.)
-- location_folder is the number of the column in the POSIX permissions list where the Path info is found.
-- aclParent: if the number in this field is the same as that of ID_folder, this ACL should be applied.
-- inheritance_acl: either self or all.
-- command_acl: where the actual chmod command is in the .csv.
-- inheritance_list: the supposedly dynamic list of ACLs within a single hierarchy.
-- counter: It's literally a counter. To see how many folders were traversed. At one point, I was returning this value.
-- username_item: for use with "do shell script"'s
-- password_item: for use with "do shell script"'s
-- deletion_choice: user chose whether to delete the existing ACLs or retain them.
-- debugLog: debug log. I've removed anything relating to this, for brevity.

on Traversal(thisFolder, folderList, ACLList, ID_folder, owner_folder, group_folder, permNums_folder, location_folder, aclParent_acl, inheritance_acl, command_acl, inheritance_list, counter, username_item, password_item, deletionChoice, debugLog)
	set origTIDs to AppleScript's text item delimiters
	set myInheritance_List to inheritance_list
	set counter to counter + 1
	if deletionChoice is "Delete" then
		set thisPerm to deleteExistingACLs(thisFolder, username_item, password_item)
	end if
	set myPOSIXList to findPosixList(thisFolder, folderList, location_folder) -- This returns the specific entry in the .csv for the POSIX permissions of this folder.
	set myACLList to findACLList(ACLList, aclParent_acl, ID_folder, myPOSIXList, command_acl) -- This does the same thing for the ACL permissions.
	-- Do POSIX permissions
	set thisVariable to giveMePermission(thisFolder, username_item, password_item) -- This handler call sets the owner and group to "Nobody" and gives 777 access.
	set posixChmod to chmodPOSIX(thisFolder, myPOSIXList, owner_folder, group_folder, permNums_folder, username_item, password_item) -- This handler actually applies the POSIX permissions.
	-- Do Inherited ACL permissions
	if myInheritance_List is not {} then
		set locationOfFolder to quoted form of (POSIX path of (thisFolder as string))
		repeat with m from 1 to count myInheritance_List
			set thisACLList to item m of myInheritance_List as string
			set thisCommand to parseCommand(thisACLList, command_acl)
			set thisCommand to thisCommand & " " & locationOfFolder
			set DIWinheritedAclCommand to do shell script thisCommand user name username_item password password_item with administrator privileges
			-- Apply "inherited" ACLs. Basically, fake propagation.
		end repeat
	end if
	if myACLList is not {} then
		--Parse command
		repeat with o from 1 to count myACLList
			set thisACL to item o of myACLList
			set locationOfFolder to quoted form of (POSIX path of (thisFolder as string))
			set command to parseCommand(thisACL, command_acl)
			set command to command & " " & locationOfFolder
			-- Do ACL chmod command here.
			set DIWaclCommand to do shell script command user name username_item password password_item with administrator privileges
			if text item inheritance_acl of thisACL is not "self" then
				if thisACL is not in (myInheritance_List as string) then set end of myInheritance_List to {thisACL}
			end if
		end repeat
		astid(",")
	end if
	astid(origTIDs)
	-- Traverse any contained folders.
	set foldersToTraverse to {}
	tell application "Finder" to set foldersToTraverse to every folder of thisFolder
	if foldersToTraverse is not {} then
		repeat with n from 1 to (count of items of foldersToTraverse) of foldersToTraverse
			set thatFolder to (item n of foldersToTraverse)
			set debugLog to Traversal(thatFolder, folderList, ACLList, ID_folder, , owner_folder, group_folder, , permNums_folder, location_folder, aclParent_acl, inheritance_acl, command_acl, myInheritance_List, counter, username_item, password_item, deletionChoice, debugLog)
		end repeat
	end if
	set myInheritance_List to {}
	return debugLog
end Traversal

Any of the given folders should have a MAXIMUM of maybe 5 ACLs. But by the time it traverses the last folder (which in this case is within the top folder that was chosen, not several folders down), it will apply every ACL it has ever put in the inheritance list.
Since I’m not returning the inheritance_list variable at any given point, I have no clue why this is happening. Any ideas?

Thanks,
_Josh

Model: Mac Pro
AppleScript: 2.0.1
Browser: Firefox 3.6.6
Operating System: Mac OS X (10.5)

I really hopes this helps but I have only given your code a gander.

A list is passed by reference so you can’t rely on the scoping helping you, but you could manually delete the items on your way back up the recursion tree. I’m sure you don’t want to hear that but. :slight_smile: It wouldn’t be that much work if you can treat the list as a stack, or use several stacks which you consolidate the stack to a list if the the Acl’s is grouped in some way. -Must do a read up on Acl’s. You would then only need to keep track of the number of items you have to “pop” off.

You could also have an extra list which is initiated as empty when you enter the handler in which you add the items to delete into, and then delete the items to delete from the “worker” list on your way “up”.

Hopes this helps in some sort of way.

Best Regards

McUsr

Thanks for you quick response, McUsr.
That thought had occurred to me. In fact, I was deleting the variable at the last line of the handler before the return:


...
       end repeat
   end if
   set myInheritance_List to {}
   return debugLog
end Traversal

I tried deleting the variable I passed into the handler (inheritance_list, before I set myInheritance_List to it), with equal failure.

I’d have to read up on stacks, as well. But you have given me an idea. I don’t know why the info is traveling upstream in the recursion, but since I’m adding each ACL to the end of that list, I could keep a count of the amount of items I’m adding and, at the end of the function and after the traversing, delete that many items from the end of the list. It seems a little dirty, but I think it might just work. I’ll keep you posted. :slight_smile:

_Josh

…Or actually, here’s another (easier) idea.
You said lists are passed by reference. That must be the problem.
So if I coerced the list into a string before passing it and then coerced it back at the beginning of the function…
“Do you think this would work?” he asked, already testing it.

_josh

Hello. I was just testing my own lies -sorry :|. A property is always passed by reference.
Here is another, much easier concept. This is the way you want to do it.
What gets into a local variable stays in a local variable.


property theList : {"Apples", "Pears", "Bananas"}
listAdder(1, theList)
” (*Apples, Pears, Bananas, Oranges*)
” (*Apples, Pears, Bananas, Oranges, Oranges*)
log theList --(*Apples, Pears, Bananas*)
on listAdder(theCount, aList)
	local tmpList
	set tmpList to aList & {"Oranges"}
	log tmpList
	set theCount to theCount + 1
	if (theCount) > 2 then return null
	listAdder(theCount, tmpList)
end listAdder

Best Regards

McUsr

You’re passing the list down, sharing it between inheritance_list and myInhertiance_List. Try changing this line .

set myInheritance_List to inheritance_list

. which simply shares the list with another variable, to .

copy inheritance_list to myInheritance_List

. which actually makes a copy of the list. In fact, you don’t need the myInheritance_List variable at all then. You could use inheritance_list all the way down:

copy inheritance_list to inheritance_list

Hello I just want to add that this statement set the value of global to a value of a global.


set myInheritance_List to inheritance_list

To make an explicit local variable you need to declare the variable local.

I am returning to my lies when I understand the topic properly.

Edit:Normally the value of aList should have been retained, but I believe since I uses it for a concatenation into another local variable, and alist “points” to the first element in that list, I’m suspecting
that the address aList points to is changed to address of the local variable.
Then I have changed the address of what aList references, and that would be a value, and therefore it retains it original address and values when returning from the handler.


property theList : {"Apples", "Pears", "Bananas"}
listAdder(1, theList)
” (*Apples, Pears, Bananas*) (from handler)
” (*Apples, Pears, Bananas, Oranges*) (from handler)
log theList
” (*Apples, Pears, Bananas*) (from script)


on listAdder(theCount, aList)
	local tmpList
	log aList ” OBSERVE: which variable I use for logging the items in the list.
	set tmpList to aList & {"Oranges"}
	set theCount to theCount + 1
	if (theCount) > 2 then return null
	listAdder(theCount, tmpList)
end listAdder


Best Regards

McUsr

You know, I had tried doing this as a local variable, and the same issue took place. I was pretty confused by that.
The copy might work just as well.
My method seems to have worked (that of coercing the list to a string, and then the string to a list). I’m of the mindset that “As long as it works…”

Thanks, guys. You’ve been a fantastic help.

:slight_smile:

_Josh

Hello.

I have by accident developed a technique for you: :slight_smile: But Nigels solutions should work as long as you declare the variable which the parameter containing the list will be copied into as local.

It is in the post above. It isn’t totally clean.

  1. create local variable
  2. if not having anything else to concatenate list passed as parameter, concatenate with an empty list or what ever. The code above demonstrates that this works.

Yet another solution is to use a “gobetween” handler like this.
Stolen from Matt Neuburgs book AppleScript The Definitive Guide 2nd Edition page 141


on byValue(x)
	local y
	copy x to y
	return y
end byValue

Best Regards

McUsr

An associate of mine suggested the “gobetween” variable, but I’m afraid I didn’t understand his logic. Willing to try anything, though I was… :wink:

Hello you use it when you call up your self again.

1st: correct adding of element to list in a “mutable” way: by either using front of or end of or altering contents of an item.


property theList : {"Apples", "Pears", "Bananas"}
listAdder(1, theList)
log theList
--(*Apples, Pears, Bananas*)
--(*Apples, Pears, Bananas, Oranges*)
--(*Apples, Pears, Bananas, Oranges, Oranges*)
--(*Apples, Pears, Bananas, Oranges, Oranges, Oranges*)
” (*Apples, Pears, Bananas, Oranges, Oranges, Oranges*)



on listAdder(theCount, aList)
	local tmpList
	log aList -- OBSERVE: which variable I use for logging the items in the list.
	set end of aList to "Oranges"
	set theCount to theCount + 1
	if (theCount) > 3 then
		-- log aList -- OBSERVE: which variable I use for logging the items in the list.
		return null
	end if
	listAdder(theCount, aList)
	log aList -- OBSERVE: which variable I use for logging the items in the list.
end listAdder

With byValue():


property theList : {"Apples", "Pears", "Bananas"}
listAdder(1, byValue(theList))(*Apples, Pears, Bananas*)
log theList
--(*Apples, Pears, Bananas*)
--(*Apples, Pears, Bananas, Oranges*)
--(*Apples, Pears, Bananas, Oranges, Oranges*)
--(*Apples, Pears, Bananas, Oranges, Oranges*)
--(*Apples, Pears, Bananas, Oranges*)
--(*Apples, Pears, Bananas*)

on listAdder(theCount, aList)
	local tmpList
	log aList -- OBSERVE: which variable I use for logging the items in the list.
	set end of aList to "Oranges"
	set theCount to theCount + 1
	if (theCount) > 3 then
		-- log aList -- OBSERVE: which variable I use for logging the items in the list.
		return null
	end if
	listAdder(theCount, byValue(aList))
	log aList -- OBSERVE: which variable I use for logging the items in the list.
end listAdder

on byValue(x)
	local y
	copy x to y
	return y
end byValue

But it is a easy to concatenate a list with an empty list, this returns a new list, with the same contents as
the previous list:


property theList : {"Apples", "Pears", "Bananas"}
listAdder(1, theList)
log theList
(*Apples, Pears, Bananas*)
(*Apples, Pears, Bananas, Oranges*)
(*Apples, Pears, Bananas, Oranges, Oranges*)
(*Apples, Pears, Bananas, Oranges, Oranges*)
(*Apples, Pears, Bananas, Oranges*)
(*Apples, Pears, Bananas*)


on listAdder(theCount, aList)
	local tmpList
	log aList -- OBSERVE: which variable I use for logging the items in the list.
	set aList to aList & {}
	set end of aList to "Oranges"
	set theCount to theCount + 1
	if (theCount) > 3 then
		-- log aList -- OBSERVE: which variable I use for logging the items in the list.
		return null
	end if
	listAdder(theCount, aList)
	log aList -- OBSERVE: which variable I use for logging the items in the list.
end listAdder

I think it should be a tad faster too. :wink:

Hopes this helps.
What confused me about references and such earlier was that I didn’t take into account that a new list are returned as a result of a concatenation.

Best Regards

McUsr

Actually, no. Variables used inside a handler are local unless declared otherwise. [*] Variables inside a recursive handler are local to the iteration.

Josh’s problem was that although the variables themselves were all local, they were all being set to the same instance of the list. Modifying the list under one variable modified it for them all.

[*] Variables declared global in the run handler are global throughout the script. Variables declared global within an ordinary handler have a scope which is limited to that handler, the run handler, and any other handlers where the same variables are explicitly declared global.

Hello Nigel. Thanks for your information.

I always thought that without being declared local it would be visible downwards, but what you are saying is that that only applies to variables in the top level of the script, which is were the run handler resides, and that the variable must be declared global then.

[b]Edit[/bI have confused it with scope rules for script properties.

-Yes I got it Thank you very much. All in all, this has been a day full of experiences . :slight_smile:

Have a real good Evening.

Best Regards

McUsr