1test -n "$_bsda_obj_" && return 0 2readonly _bsda_obj_=1 3set -f 4 5# 6# This file contains helper functions for creating object oriented 7# shell scripts. 8# 9# The most significant function is bsda:obj:createClass(), which basically 10# creates a class, including getters, setters, a constructor, a destructor 11# and a copy function. It also creates serialisation methods. 12# 13 14# 15# The stack counter that holds the number of methods that currently 16# use the return stack. 17# 18bsda_obj_callStackCount=0 19 20# 21# This is a prefix to every object ID. 22# 23readonly bsda_obj_frameworkPrefix=BSDA_OBJ_ 24 25# 26# The UID to use for creating new objects. When forking a session use the 27# bsda:obj:fork() function to update this value in the forked process. 28# 29#bsda_obj_uid 30 31# 32# The copy method sets this temporarily to tell the constructor not to call 33# an init method. 34# 35#bsda_obj_doCopy 36 37# 38# A list of objects with a cleanup function, used by the EXIT trap handler 39# to allow objects the destruction of acquired resources. 40# 41#bsda_obj_freeOnExit 42 43# 44# A list of available file descriptors for bsda:obj:getDesc(). 45# 46bsda_obj_desc=3,4,5,6,7,8,9, 47 48# 49# Creates a new class, i.e. a constructor, destructor, getters, setters 50# and so forth. 51# 52# So all that is left to be done are the methods. 53# 54# The following static methods are reserved: 55# - deserialise() 56# - isInstance() 57# - getAttributes() 58# - getMethods() 59# 60# The following methods are reserved: 61# - copy() 62# - delete() 63# - dump() 64# - serialise() 65# 66# The following class prefix bound static attributes are reserved: 67# - private 68# - public 69# 70# The following session, class and process bound static attributes are 71# reserved: 72# nextId 73# 74# @param 1 75# The first parameter is the name of the class. 76# @param @ 77# A description of the class to create. 78# 79# All parameters following the class name make up a list of identifiers 80# for attributes and methods. Every identifier has a prefix, the 81# following formats are supported: 82# 83# - `-:<name>`: 84# A plain attribute. 85# - `r:[<scope>:]<name>` 86# An attribute with a get method named `$obj.getName()`. 87# - `w:[<scope>:]<name>` 88# An attribute with a getter and setter named `$obj.getName()` 89# and `$obj.setName()`. 90# - `x:[<scope>:]<name>` 91# A user implemented method named `$obj.name()`, needs to 92# be implemented as 'class.name()`. 93# - `i:[<scope>:]<name>` 94# An initialisation method called by the constructor. The 95# scope only affects a direct call of the method, the constructor 96# is always public. 97# - `c:[<scope>:]<name>` 98# A cleanup method called by the destructor. The scope only 99# affects direct calls of the method. 100# - `a:[<scope>:]<name>[=<class>] 101# An aggregation. Aggregations are special attributes, 102# referencing other objects, that are automatically 103# deleted, copied and serialised along with an instance 104# of a class. 105# An aggregation attribute has a getter named `$obj.name()`. 106# An optional class name is used to determine if the default 107# `$obj.copy()` and `$obj.serialise()` methods can be created. 108# If the class is omitted, they never are. 109# 110# With these parameters a constructor and a destructor will be built. 111# It is important that all used attributes are listed, or the copy, 112# delete and serialisation methods will not work as expected. 113# 114# Everything that is not recognized as an identifier is treated as a 115# comment. 116# 117# The prefixes r, w, x, i, c and a can be followed by a scope operator 118# public or private. 119# 120# The constructor can be called in the following way: 121# <class> <refname> 122# The class name acts as the name of the constructor, <refname> is the 123# name of the variable to store the reference to the new object in. 124# 125# The destructor can be called in the following way: 126# $reference.delete 127# This will destroy all methods and attributes. 128# 129# A getter takes the name of the variable to store the value in as the 130# first argument, if this is ommitted, the value is written to stdout. 131# 132# The copy method can be used to create a shallow copy of an object. 133# 134# @param bsda_obj_namespace 135# The framework namespace to use when building a class. The impact is on 136# the use of helper functions. 137# @retval 0 138# On success 139# @retval 1 140# If there is more than one init method (i:) specified 141# @retval 2 142# If there is more than one cleanup method (c:) specified 143# @retval 3 144# If there was an unknown scope operator 145# @retval 4 146# If an aggregation with an undefined class occurred 147# @retval 5 148# No class name was given 149# @retval 6 150# Forbidden characters in attribute name, [a-zA-Z0-9_] are allowed 151# 152bsda:obj:createClass() { 153 local IFS class methods method attributes getters setters arg 154 local getter setter attribute init clean 155 local namespacePrefix classPrefix instancePattern 156 local previousMethod scope 157 local aggregations aggregation classname 158 local amethods has_copy has_serialise 159 160 # Default framework namespace. 161 : ${bsda_obj_namespace='bsda:obj'} 162 163 # Get the class name and shift it off the parameter list. 164 if [ -z "$1" ]; then 165 echo "bsda:obj:createClass: ERROR: No class name supplied!" 1>&2 166 return 5 167 fi 168 class="$1" 169 shift 170 171 IFS=$'\n' 172 173 # There are some default methods. 174 methods="delete${IFS}dump${IFS}" 175 attributes= 176 getters= 177 setters= 178 init= 179 clean= 180 has_copy=1 181 has_serialise=1 182 183 # Parse arguments. 184 for arg in "$@"; do 185 case "$arg" in 186 x:*) 187 methods="$methods${arg#x:}$IFS" 188 ;; 189 -:*) 190 attributes="$attributes${arg#-:}$IFS" 191 ;; 192 r:*) 193 attributes="$attributes${arg##*:}$IFS" 194 getters="$getters${arg#r:}$IFS" 195 ;; 196 w:*) 197 attributes="$attributes${arg##*:}$IFS" 198 getters="$getters${arg#w:}$IFS" 199 setters="$setters${arg#w:}$IFS" 200 ;; 201 i:*) 202 if [ -n "$init" ]; then 203 echo "bsda:obj:createClass: ERROR: $class: More than one init method was supplied!" 1>&2 204 return 1 205 fi 206 methods="$methods${arg#i:}$IFS" 207 init="$class.${arg##*:}" 208 ;; 209 c:*) 210 if [ -n "$clean" ]; then 211 echo "bsda:obj:createClass: ERROR: $class: More than one cleanup method was supplied!" 1>&2 212 return 2 213 fi 214 methods="$methods${arg#c:}$IFS" 215 clean="$class.${arg##*:}" 216 ;; 217 a:*) 218 aggregations="$aggregations${arg#a:}$IFS" 219 ;; 220 *) 221 # Assume everything else is a comment. 222 ;; 223 esac 224 done 225 226 # Create aggregations. 227 aggregation="$aggregations" 228 aggregations= 229 for aggregation in $aggregation; do 230 # Get class 231 case "$aggregation" in 232 *=*) 233 classname="${aggregation#*=}" 234 aggregation="${aggregation%%=*}" 235 ;; 236 *) 237 classname= 238 ;; 239 esac 240 241 # Get scope of the getter method 242 case "$aggregation" in 243 *:*) 244 scope="${aggregation%%:*}" 245 aggregation="${aggregation#*:}" 246 ;; 247 *) 248 scope=public 249 ;; 250 esac 251 252 aggregations="$aggregations$aggregation$IFS" 253 attributes="$attributes$aggregation$IFS" 254 methods="$methods$scope:$aggregation$IFS" 255 256 eval "$class.$aggregation() { 257 if [ -n \"\$1\" ]; then 258 eval \"\$1=\\\"\\\$\${this}$aggregation\\\"\" 259 else 260 eval \"echo \\\"\\\$\${this}$aggregation\\\"\" 261 fi 262 }" 263 264 if [ -z "$classname" ]; then 265 has_copy= 266 has_serialise= 267 elif [ "$classname" != "$class" ]; then 268 if ! $classname.getMethods amethods 2>&-; then 269 echo "bsda:obj:createClass: ERROR: $class: Aggregation with undefined class: $classname" 1>&2 270 return 4 271 fi 272 $classname.getMethods \ 273 | /usr/bin/grep -qFx public:copy || has_copy= 274 $classname.getMethods \ 275 | /usr/bin/grep -qFx public:serialise || has_serialise= 276 fi 277 done 278 279 # Remove duplicated attributes. 280 attribute="$attributes" 281 attributes= 282 for attribute in $(echo "$attribute" | /usr/bin/awk '!a[$0]++'); do 283 attributes="$attributes$attribute$IFS" 284 # Verify attribute names. 285 if ! echo "$attribute" | /usr/bin/grep -qx '[a-zA-Z0-9_]*'; then 286 echo "bsda:obj:createClass: ERROR: $class: Attributes must only contain the characters [a-zA-Z0-9_]: $attribute" 1>&2 287 return 6 288 fi 289 done 290 291 # Only classes without a custom destructor get copy() and 292 # serialise() members. 293 if [ -z "$clean" ] && [ -n "$has_copy" ]; then 294 methods="${methods}copy${IFS}" 295 fi 296 if [ -z "$clean" ] && [ -n "$has_serialise" ]; then 297 methods="${methods}serialise${IFS}" 298 fi 299 300 # Create reference prefix. The Process id is added to the prefix when 301 # an object is created. 302 namespacePrefix="${bsda_obj_frameworkPrefix}$(echo "$bsda_obj_namespace" | /usr/bin/tr ':' '_')_" 303 classPrefix="${namespacePrefix}$(echo "$class" | /usr/bin/tr ':' '_')_" 304 305 # Set the instance match pattern. 306 instancePattern="${classPrefix}[0-9a-f]*[0-9]_" 307 308 # Create getters. 309 for method in $getters; do 310 getter="${method##*:}" 311 attribute="$getter" 312 getter="get$(echo "${getter%%${getter#?}}" | /usr/bin/tr '[:lower:]' '[:upper:]')${getter#?}" 313 314 eval "$class.$getter() { 315 if [ -n \"\$1\" ]; then 316 eval \"\$1=\\\"\\\$\${this}$attribute\\\"\" 317 else 318 eval \"echo \\\"\\\$\${this}$attribute\\\"\" 319 fi 320 }" 321 322 # Check for scope operator. 323 if [ "${method%:*}" != "$method" ]; then 324 # Add scope operator to the getter name. 325 getter="${method%:*}:$getter" 326 fi 327 # Add the getter to the list of methods. 328 methods="$methods$getter$IFS" 329 done 330 331 # Create setters. 332 for method in $setters; do 333 setter="${method##*:}" 334 attribute="$setter" 335 setter="set$(echo "${setter%%${setter#?}}" | /usr/bin/tr '[:lower:]' '[:upper:]')${setter#?}" 336 337 eval "$class.$setter() { 338 setvar \"\${this}$attribute\" \"\$1\" 339 }" 340 341 # Check for scope operator. 342 if [ "${method%:*}" != "$method" ]; then 343 # Add scope operator to the getter name. 344 setter="${method%:*}:$setter" 345 fi 346 # Add the setter to the list of methods. 347 methods="$methods$setter$IFS" 348 done 349 350 # Add implicit public scope to methods. 351 method="$methods" 352 methods= 353 for method in $method; do 354 # Check the scope. 355 case "${method%:*}" in 356 $method) 357 # There is no scope operator, add public. 358 methods="${methods}public:$method$IFS" 359 ;; 360 public | private) 361 # The accepted scope operators. 362 methods="$methods$method$IFS" 363 ;; 364 *) 365 # Everything else is not accepted. 366 echo "bsda:obj:createClass: ERROR: $class: Unknown scope operator: ${method%:*}" 1>&2 367 return 3 368 ;; 369 esac 370 done 371 372 373 # If a method is defined more than once, the widest scope wins. 374 # Go through the methods sorted by method name. 375 previousMethod= 376 method="$methods" 377 methods= 378 scope= 379 for method in $(echo "$method" | /usr/bin/sort -t: -k2); do 380 # Check whether the previous and the current method were the 381 # same. 382 if [ "$previousMethod" != "${method##*:}" ]; then 383 # If all scopes of this method have been found, 384 # store it in the final list. 385 methods="$methods${previousMethod:+$scope:$previousMethod$IFS}" 386 scope="${method%:*}" 387 else 388 # Widen the scope if needed. 389 case "${method%:*}" in 390 public) 391 scope=public 392 ;; 393 esac 394 fi 395 396 previousMethod="${method##*:}" 397 done 398 # Add the last method (this never happens in the loop). 399 methods="$methods${previousMethod:+$scope:$previousMethod$IFS}" 400 401 # 402 # Store access scope checks for each scope in the class context. 403 # Note that at the time this is run the variables class and this 404 # still belong to the the caller. 405 # These definitions are repeatedly subject to eval calls, hence 406 # the different escape depth which makes sure the variables 407 # are resolved at the right stage. 408 # 409 410 # Private methods allow the following kinds of access: 411 # - Same class 412 # Access is allowed by all objects with the same class. 413 setvar ${classPrefix}private " 414 if [ \\\"\\\$class\\\" != \\\"$class\\\" ]; then 415 echo \\\"$class.\${method##*:}: Terminated because of access attempt to a private method\\\${class:+ by \\\$class}!\\\" 1>&2 416 return 255 417 fi 418 " 419 # Public methods allow unchecked access. 420 setvar ${classPrefix}public '' 421 422 # Create constructor. 423 eval "$class() { 424 local this class 425 class=$class 426 427 # Create object reference. 428 this=\"${classPrefix}${bsda_obj_uid}_\${${classPrefix}${bsda_obj_uid}_nextId:-0}_\" 429 430 # Increase the object id counter. 431 ${classPrefix}${bsda_obj_uid}_nextId=\$((\$${classPrefix}${bsda_obj_uid}_nextId + 1)) 432 433 # Create method instances. 434 $bsda_obj_namespace:createMethods $class $classPrefix \"\$this\" '$methods' 435 436 ${clean:+bsda_obj_freeOnExit=\"\$bsda_obj_freeOnExit\$this$IFS\"} 437 438 # If this object construction is part of a copy() call, 439 # this constructor is done. 440 if [ -n \"\$bsda_obj_doCopy\" ]; then 441 # Return the object reference. 442 if [ -n \"\$1\" ]; then 443 setvar \"\$1\" \"\$this\" 444 else 445 echo \"\$this\" 446 fi 447 return 0 448 fi 449 450 local _return _var 451 _var=\"\$1\" 452 ${init:+ 453 # Cast the reference variable from the parameters. 454 shift 455 local caller 456 bsda:obj:callerSetup 457 # Call the init method. 458 $init \"\$@\" 459 _return=\$? 460 bsda:obj:callerFinish 461 # Destroy the object on failure. 462 if [ \$_return -ne 0 ]; then 463 \"\$this\".delete 464 return \$_return 465 fi 466 } 467 468 # Return the object reference. 469 if [ -n \"\$_var\" ]; then 470 setvar \"\$_var\" \"\$this\" 471 else 472 echo \"\$this\" 473 fi 474 return 0 475 }" 476 477 # Create destructor. 478 eval "$class.delete() { 479 ${clean:+ 480 $clean \"\$@\" || return \$? 481 # Unregister cleanup function from EXIT trap 482 local nl 483 nl='$IFS' 484 bsda_obj_freeOnExit=\"\${bsda_obj_freeOnExit%%\$this*\}\${bsda_obj_freeOnExit#*\$this\$nl\}\" 485 } 486 487 ${aggregations:+eval \"$( 488 for aggregation in $aggregations; do 489 echo \\\"\\\$\${this}$aggregation\\\".delete 490 done 491 )\"} 492 493 # Delete methods and attributes. 494 $bsda_obj_namespace:deleteMethods \"\$this\" '$methods' 495 $bsda_obj_namespace:deleteAttributes \"\$this\" '$attributes' 496 }" 497 498 # Prints an object in a human readable format. 499 eval "$class.dump() { 500 local result 501 result=\"$class@\$this {${attributes:+$IFS\$( ( 502 $(for aggregation in $aggregations; do 503 echo "eval \"\\\"\\\$\${this}$aggregation\\\".dump var\"" 504 echo "echo \"$aggregation=\$var\"" 505 done) 506 $(for attribute in $attributes; do 507 for aggregation in $aggregations; do 508 test "$aggregation" = "$attribute" && continue 2 509 done 510 echo "getvar var \"\${this}$attribute\"" 511 echo "echo \"$attribute='\$var'\"" 512 done) 513 ) | /usr/bin/sed 's/^/ /')$IFS}}\" 514 \"\$caller\".setvar \"\$1\" \"\$result\" 515 }" 516 517 # Create copy method. 518 eval "$class.copy() { 519 local bsda_obj_doCopy reference 520 521 bsda_obj_doCopy=1 522 523 # Create a new empty object. 524 $class reference 525 526 # Store the new object reference in the target variable. 527 \"\$caller\".setvar \"\$1\" \"\$reference\" 528 529 # For each attribute copy the value over to the 530 # new object. 531 ${attributes:+eval \"$( 532 for attribute in $attributes; do 533 echo "\${reference}$attribute=\\\"\\\$\${this}$attribute\\\"" 534 done 535 )\"} 536 537 ${aggregations:+eval \"$( 538 for aggregation in $aggregations; do 539 echo "\\\"\\\$\${this}$aggregation\\\".copy \${reference}$aggregation" 540 done 541 )\"} 542 }" 543 544 # A serialise method. 545 eval "$class.serialise() { 546 local serialised svar 547 548 serialised= 549 $(for attribute in $attributes; do 550 echo "bsda:obj:serialiseVar svar \"\${this}$attribute\"" 551 echo "serialised=\"\${serialised:+\$serialised;}\$svar\"" 552 done) 553 serialised=\"\$serialised;$class.deserialise \$this\" 554 555 $(for aggregation in $aggregations; do 556 echo eval \"\\\"\\\$\${this}$aggregation\\\".serialise svar\" 557 echo 'serialised="$svar;$serialised"' 558 done) 559 560 \"\$caller\".setvar \"\$1\" \"\$serialised\" 561 }" 562 563 # A static deserialise method. 564 eval "$class.deserialise() { 565 # Create method instances. 566 $bsda_obj_namespace:createMethods $class $classPrefix \"\$1\" '$methods' 567 ${clean:+ 568 if [ -z \"\$bsda_obj_freeOnExit\" ] || \ 569 [ -n \"\${bsda_obj_freeOnExit%%*\$1*\}\" ]; then 570 bsda_obj_freeOnExit=\"\$bsda_obj_freeOnExit\$1$IFS\" 571 fi 572 } 573 }" 574 575 # A static type checker. 576 eval "$class.isInstance() { 577 case \"\$1\" in 578 $instancePattern) 579 return 0 580 ;; 581 esac 582 return 1 583 }" 584 585 # A static method that returns the attributes of a class. 586 eval "$class.getAttributes() { 587 if [ -n \"\$1\" ]; then 588 setvar \"\$1\" '$attributes' 589 else 590 echo '$attributes' 591 fi 592 }" 593 594 # A static method that returns the methods of a class. 595 eval "$class.getMethods() { 596 if [ -n \"\$1\" ]; then 597 setvar \"\$1\" '$methods' 598 else 599 echo '$methods' 600 fi 601 }" 602} 603 604# 605# Delete all objects in a list of objects. 606# 607# If deleting an object fails, it has no influence on the subsequent 608# deletion of the following objects. 609# 610# @param @ 611# The list of objects 612# @return 613# The bitwise OR product of all destructor return values 614# 615bsda:obj:delete[]() { 616 local obj ret 617 ret=0 618 for obj in "$@"; do 619 $obj.delete 620 ret=$((ret | $?)) 621 done 622 return $ret 623} 624 625# 626# Returns an object reference to a serialised object. 627# 628# @param 1 629# If this is the sole parameter, this is a serialised string of which 630# the object reference should be output. In case of a second parameter 631# this is the name of the variable to return the reference to the 632# serialised object to. 633# @param 2 634# The serialised string of which the reference should be returned. 635# 636bsda:obj:getSerializedId() { 637 if [ -n "$2" ]; then 638 setvar "$1" "${2##* }" 639 else 640 echo "${1##* }" 641 fi 642} 643 644# 645# Deserialises a serialised object and returns or outputs a reference to 646# said object. 647# 648# @param &1 649# The name of the variable to store the deserialised object reference 650# in. If empty the reference will be output to stdout. 651# @param 2 652# If given this is the string to be serialised, otherwise it will be 653# expected on stdin. 654# 655bsda:obj:deserialise() { 656 if [ $# -lt 2 ]; then 657 set -- "$1" "$(/bin/cat)" 658 fi 659 660 if [ -n "$1" ]; then 661 setvar "$1" "${2##* }" 662 else 663 echo "${2##* }" 664 fi 665 666 local IFS 667 IFS=$'\n' 668 eval "$2" 669} 670 671# 672# Filters the given string of serialised data to only contain the last 673# representation of each object. 674# 675# The order of objects may be subject to change. 676# 677# @param &1 678# The name of the variable to store the resulting string in. 679# If empty the string is output to stdout. 680# @param 2 681# If given this is the serialised data, otherwise it will be 682# expected on stdin. 683# 684bsda:obj:serialisedUniq() { 685 if [ -n "$1" ]; then 686 if [ -n "$2" ]; then 687 setvar "$1" "$(echo "$2" | /usr/bin/awk '{lines[$NF] = $0} END {for (line in lines) print lines[line]}')" 688 else 689 setvar "$1" "$(/usr/bin/awk '{lines[$NF] = $0} END {for (line in lines) print lines[line]}')" 690 fi 691 else 692 if [ -n "$2" ]; then 693 echo "$2" | /usr/bin/awk '{lines[$NF] = $0} END {for (line in lines) print lines[line]}' 694 else 695 /usr/bin/awk '{lines[$NF] = $0} END {for (line in lines) print lines[line]}' 696 fi 697 fi 698} 699 700# 701# Creates the methods to a new object from a class. 702# 703# This is achieved by creating a method wrapper that provides the 704# context variables this, class and caller. 705# 706# It works under the assumption, that methods are defined as: 707# <class>.<method>() 708# 709# @param 1 710# The class name. 711# @param 2 712# The class prefix where the scope checks are stored. 713# @param 3 714# The object reference. 715# @param 4 716# A list of method names. 717# 718bsda:obj:createMethods() { 719 local IFS method scope 720 IFS=$'\n' 721 for method in $4; do 722 scope=${method%:*} 723 # Get scope check from class. 724 eval "scope=\"\$$2$scope\"" 725 # Add method name to scope. 726 eval "scope=\"$scope\"" 727 method=${method##*:} 728 eval "$3.$method() { 729 $scope 730 local caller 731 bsda:obj:callerSetup 732 local class this _return 733 class=$1 734 this=$3 735 $1.$method \"\$@\" 736 _return=\$? 737 bsda:obj:callerFinish 738 return \$_return 739 }" 740 done 741} 742 743# 744# Deletes methods from an object. This is intended to be used in a destructor. 745# 746# @param 1 747# The object reference. 748# @param 2 749# A list of method names. 750# 751bsda:obj:deleteMethods() { 752 local IFS method 753 IFS=$'\n' 754 for method in $2; do 755 method=${method##*:} 756 unset -f "$1.$method" 757 done 758} 759 760# 761# Deletes attributes from an object. This is intended to be used in a 762# destructor. 763# 764# This works under the assumption, that attributes are defined as: 765# <reference>_<attribute> 766# 767# @param 1 768# The object reference. 769# @param 2 770# A list of attribute names. 771# 772bsda:obj:deleteAttributes() { 773 local IFS attribute 774 IFS=$'\n' 775 for attribute in $2; do 776 unset "${1}$attribute" 777 done 778} 779 780# 781# Setup the caller stack to store variables that should be overwritten 782# in the caller context upon exiting the method. 783# 784# This function is called by the wrapper around class instance methods. 785# 786# The bsda_obj_callStackCount counter is increased and and a stack count prefix 787# is created, which is used by bsda:obj:callerSetvar() to store variables 788# for functions in the caller context until bsda:obj:callerFinish() is 789# called. 790# 791# The call stack prefix is in the format 'bsda_obj_callStack_[0-9]+_'. 792# 793# @param caller 794# Is set to the current stack count prefix. 795# @param bsda_obj_callStackCount 796# Is incremented by 1 and used to create the caller variable. 797# 798bsda:obj:callerSetup() { 799 # Increment the call stack counter and create the caller prefix. 800 caller="bsda_obj_callStack_${bsda_obj_callStackCount}_" 801 bsda_obj_callStackCount=$(($bsda_obj_callStackCount + 1)) 802 803 # Create a wrapper around bsda:obj:callerSetvar for access 804 # through the caller prefix. 805 eval "$caller.setvar() { 806 bsda:obj:callerSetvar \"\$@\" 807 }" 808 809 # Delete the given object when returning to the caller. 810 eval "$caller.delete() { 811 delete_${caller}=\"\$1.delete;\${delete_${caller}}\" 812 }" 813} 814 815# 816# Copy variables from the caller stack into the caller context and clean 817# the stack up. 818# 819# This function is called by the wrapper around class instance methods 820# after the actual method has terminated. 821# 822# @param caller 823# The caller context prefix. 824# @param delete_${caller} 825# The list of objects to delete when returning to the caller. 826# @param setvars_${caller} 827# The list of variables to copy into the caller context. 828# @param bsda_obj_callStackCount 829# Is decremented by 1. 830# 831bsda:obj:callerFinish() { 832 # Delete objects 833 eval "eval \"\${delete_${caller}}\"" 834 unset "delete_${caller}" 835 836 # Remove the bsda:obj:callerSetvar() wrapper. 837 unset -f "$caller.setvar" "$caller.delete" 838 # Decrement the call stack counter. 839 bsda_obj_callStackCount=$(($bsda_obj_callStackCount - 1)) 840 841 # Copy variables to the caller context. 842 local _var IFS 843 IFS=' ' 844 eval "_var=\"\$setvars_${caller}\"" 845 for _var in $_var; do 846 # Copy variable. 847 eval "setvar $_var \"\$$caller$_var\"" 848 # Delete variable from stack. 849 unset $caller$_var 850 done 851 # Delete list of variables from stack. 852 unset setvars_${caller} 853} 854 855# 856# This function stores a variables for overwriting variables in the context 857# of the caller. If no storing variable has been specified (i.e. the first 858# parameter is empty), the value is printed instead. 859# 860# This function is accessable in methods by calling: 861# $caller.setvar 862# 863# The stored variables are processed by the bsda:obj:callerFinish() function. 864# 865# @param 1 866# The name of the variable to store. 867# @param 2 868# The value to store. 869# @param caller 870# The context to store variables in. 871# @param setvars_${caller} 872# A list of all the stored variables for the caller context. 873# 874bsda:obj:callerSetvar() { 875 # Print if no return variable was specified. 876 test -z "$1" && echo "$2" && return 877 878 # Store value. 879 setvar "$caller$1" "$2" 880 # Register variable. 881 eval "setvars_${caller}=\"\$setvars_${caller}\${setvars_${caller}:+ }$1\"" 882} 883 884# 885# Serialises a single variable by a given name. 886# 887# @param 1 888# The name of the variable to return the string to. 889# @param 2 890# The name of the variable to serialise. 891# 892bsda:obj:serialiseVar() { 893 if [ -n "$1" ]; then 894 setvar "$1" "$2=\"\$(printf '$(eval "echo -n \"\$$2\"" | bsda:obj:escape)')\"" 895 else 896 echo "$2=\"\$(printf '$(eval "echo -n \"\$$2\"" | bsda:obj:escape)')\"" 897 fi 898} 899 900# 901# Escapes strings on stdin for serialisation. 902# 903# The printf command can be used for deserialisation. 904# 905bsda:obj:escape() { 906 /usr/bin/vis -woe"'%\$\"-" 907} 908 909# 910# Install traps for garbage collection upon termination of the process. 911# 912bsda:obj:trap() { 913 trap bsda:obj:exit EXIT 914 trap "trap '' HUP INT TERM;exit 1" HUP INT TERM 915} 916 917# 918# This function can be used to update bsda_obj_uid in forked processes. 919# 920# This is necessary when both processes exchange objects (commonly in 921# serialised form) and thus need to be able to create objects with unique 922# IDs. 923# 924# The function should be called within the forked process without parameters. 925# 926# @param bsda_obj_uid 927# Is set to a new uid 928# @param bsda_obj_freeOnExit 929# The list of objects to garbage collect when terminating 930# @param bsda_obj_callStackCount 931# The current call stack depth 932# 933bsda:obj:fork() { 934 # Reset resource collection 935 bsda_obj_freeOnExit= 936 bsda:obj:trap 937 938 # Clear the record of temp objects on the stack below, so 939 # they do not get deleted in the forked process 940 local caller i 941 i=$((bsda_obj_callStackCount)) 942 while [ $i -gt 0 ]; do 943 caller="bsda_obj_callStack_$((i -= 1))_" 944 unset "delete_${caller}" 945 done 946 947 # Update UID 948 bsda_obj_uid="$(/bin/uuidgen | /usr/bin/tr '-' '_')" 949} 950 951# 952# This function can be used to detach the script from its execution context. 953# 954# I.e. it forks and the forked process inherits the responsibilities of 955# the main process, while the main process dies. 956# 957# @warning 958# Detaching the script means loosing the guarantee that resources 959# are freed in order upon termination. It becomes the programmers 960# responsibility to make sure that processes die in the right order. 961# @param @ 962# The command to detach, treat this like an argument to eval 963# @return 964# This function does not return 965# 966bsda:obj:detach() { 967 eval "bsda:obj:trap;" "$@" & 968 trap - EXIT 969 exit 0 970} 971 972# 973# This function calls all delete functions of objects having a cleanup 974# method. 975# 976# Objects spawning processes are responsible for killing them in their 977# destructor. 978# 979# @param bsda_obj_callStackCount 980# The stack depth for unwiding 981# @param bsda_obj_freeOnExit 982# The list of objects to call 983# 984bsda:obj:exit() { 985 local nl obj caller 986 nl=$'\n' 987 # Stack unwinding, just remove temp objects 988 while [ $((bsda_obj_callStackCount)) -gt 0 ]; do 989 caller="bsda_obj_callStack_$((bsda_obj_callStackCount - 1))_" 990 eval "eval \"\${delete_${caller}}\"" 991 unset "delete_${caller}" 992 : $((bsda_obj_callStackCount -= 1)) 993 done 994 995 # Garbage collection 996 while [ -n "$bsda_obj_freeOnExit" ]; do 997 obj="${bsda_obj_freeOnExit%%$nl*}" 998 if ! "$obj".delete; then 999 echo "bsda:obj:exit: WARNING: Delete of $obj failed!" 1>&2 1000 bsda_obj_freeOnExit="${bsda_obj_freeOnExit#$obj$nl}" 1001 fi 1002 done 1003 # Wait if any children stick around. 1004 trap - HUP INT TERM 1005 wait 1006} 1007 1008# 1009# Returns an exclusive file descriptor number for use. 1010# 1011# Note that FreeBSD sh only supports up to 9 file descriptors, so only the 7 1012# descriptors [3; 9] are available. 1013# 1014# @param bsda_obj_desc 1015# The list of available file descriptors 1016# @param &1 1017# A reference to the variable that should contain the descriptor number 1018# @retval 0 1019# The descriptor was returned successfully 1020# @retval 1 1021# No more descriptors were available 1022# 1023bsda:obj:getDesc() { 1024 if [ -z "$bsda_obj_desc" ]; then 1025 return 1 1026 fi 1027 # Return first available file descriptor 1028 setvar $1 ${bsda_obj_desc%%,*} 1029 # Remove descriptor from the store of available pipes 1030 bsda_obj_desc=${bsda_obj_desc#*,} 1031} 1032 1033# 1034# Releases an exclusive file descriptor. 1035# 1036# Returns a file descriptor back into the pool of usable descriptors. 1037# 1038# @param bsda_obj_desc 1039# The list of available file descriptors 1040# @param 1 1041# The file descriptor to release 1042# 1043bsda:obj:releaseDesc() { 1044 test -z "$1" && return 1045 bsda_obj_desc="$bsda_obj_desc$1," 1046} 1047 1048# 1049# Initialise session UID and garbage collection. 1050# 1051bsda:obj:fork 1052 1053# 1054# Hacks. 1055# 1056 1057# 1058# Ignore nullptr delete. 1059# 1060.delete() { 1061 : # bash does not allow empty functions 1062} 1063 1064# 1065# Perform nullptr dump. 1066# 1067# @param &1 1068# The dump string destination variable, set to empty 1069# 1070.dump() { 1071 setvar "$1" 1072} 1073 1074# 1075# Perform nullptr copy. 1076# 1077# @param &1 1078# The copy reference destination variable, set to empty 1079# 1080.copy() { 1081 setvar "$1" 1082} 1083 1084# 1085# Perform nullptr serialise. 1086# 1087# @param &1 1088# The serialise string destination variable, set to empty 1089# 1090.serialise() { 1091 setvar "$1" 1092} 1093 1094# 1095# Compatibility hacks. 1096# 1097 1098# Emulate setvar for shells that don't have it, i.e. bash. 1099if ! setvar 2>&-; then 1100 setvar() { 1101 eval "$1=\"\$2\"" 1102 } 1103fi 1104 1105# Setup getvar for symmetry with setvar 1106if ! getvar 2>&-; then 1107 # 1108 # Returns a variable from a given reference. 1109 # 1110 # The variable is either written to a named variable, or in 1111 # absence of one, output to stdout. 1112 # 1113 # @param &1 1114 # The name of the variable to write to 1115 # @param 2 1116 # The reference to the variable to return 1117 # 1118 getvar() { 1119 if [ -n "$1" ]; then 1120 eval "$1=\"\$$2\"" 1121 else 1122 eval "echo \"\$$2\"" 1123 fi 1124 } 1125fi 1126