1 /*
   2  * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27    Hierarchical storage layout:
  28 
  29    <dict>
  30      <key>/</key>
  31      <dict>
  32        <key>foo</key>
  33        <string>/foo's value</string>
  34        <key>foo/</key>
  35        <dict>
  36          <key>bar</key>
  37          <string>/foo/bar's value</string>
  38        </dict>
  39      </dict>
  40    </dict>
  41 
  42    Java pref nodes are stored in several different files. Pref nodes
  43    with at least three components in the node name (e.g. /com/MyCompany/MyApp/)
  44    are stored in a CF prefs file with the first three components as the name.
  45    This way, all preferences for MyApp end up in com.MyCompany.MyApp.plist .
  46    Pref nodes with shorter names are stored in com.apple.java.util.prefs.plist
  47 
  48    The filesystem is assumed to be case-insensitive (like HFS+).
  49    Java pref node names are case-sensitive. If two pref node names differ
  50    only in case, they may end up in the same pref file. This is ok
  51    because the CF keys identifying the node span the entire absolute path
  52    to the node and are case-sensitive.
  53 
  54    Java node names may contain '.' . When mapping to the CF file name,
  55    these dots are left as-is, even though '/' is mapped to '.' .
  56    This is ok because the CF key contains the correct node name.
  57 */
  58 
  59 
  60 
  61 #include <CoreFoundation/CoreFoundation.h>
  62 
  63 #include "jni_util.h"
  64 #include "jlong.h"
  65 #include "jvm.h"
  66 
  67 /*
  68  * Declare library specific JNI_Onload entry if static build
  69  */
  70 DEF_STATIC_JNI_OnLoad
  71 
  72 
  73 // Throw an OutOfMemoryError with the given message.
  74 static void throwOutOfMemoryError(JNIEnv *env, const char *msg)
  75 {
  76     static jclass exceptionClass = NULL;
  77     jclass c;
  78 
  79     (*env)->ExceptionClear(env);  // If an exception is pending, clear it before
  80                                   // calling FindClass() and/or ThrowNew().
  81     if (exceptionClass) {
  82         c = exceptionClass;
  83     } else {
  84         c = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
  85         if ((*env)->ExceptionOccurred(env)) return;
  86         exceptionClass = (*env)->NewGlobalRef(env, c);
  87     }
  88 
  89     (*env)->ThrowNew(env, c, msg);
  90 }
  91 
  92 
  93 // throwIfNull macro
  94 // If var is NULL, throw an OutOfMemoryError and goto badvar.
  95 // var must be a variable. env must be the current JNIEnv.
  96 // fixme throw BackingStoreExceptions sometimes?
  97 #define throwIfNull(var, msg) \
  98     do { \
  99         if (var == NULL) { \
 100             throwOutOfMemoryError(env, msg); \
 101             goto bad##var; \
 102         } \
 103     } while (0)
 104 
 105 
 106 // Converts CFNumber, CFBoolean, CFString to CFString
 107 // returns NULL if value is of some other type
 108 // throws and returns NULL on memory error
 109 // result must be released (even if value was already a CFStringRef)
 110 // value must not be null
 111 static CFStringRef copyToCFString(JNIEnv *env, CFTypeRef value)
 112 {
 113     CFStringRef result;
 114     CFTypeID type;
 115 
 116     type = CFGetTypeID(value);
 117 
 118     if (type == CFStringGetTypeID()) {
 119         result = (CFStringRef)CFRetain(value);
 120     }
 121     else if (type == CFBooleanGetTypeID()) {
 122         // Java Preferences API expects "true" and "false" for boolean values.
 123         result = CFStringCreateCopy(NULL, (value == kCFBooleanTrue) ? CFSTR("true") : CFSTR("false"));
 124         throwIfNull(result, "copyToCFString failed");
 125     }
 126     else if (type == CFNumberGetTypeID()) {
 127         CFNumberRef number = (CFNumberRef) value;
 128         if (CFNumberIsFloatType(number)) {
 129             double d;
 130             CFNumberGetValue(number, kCFNumberDoubleType, &d);
 131             result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%g"), d);
 132             throwIfNull(result, "copyToCFString failed");
 133         }
 134         else {
 135             long l;
 136             CFNumberGetValue(number, kCFNumberLongType, &l);
 137             result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%ld"), l);
 138             throwIfNull(result, "copyToCFString failed");
 139         }
 140     }
 141     else {
 142         // unknown type - return NULL
 143         result = NULL;
 144     }
 145 
 146  badresult:
 147     return result;
 148 }
 149 
 150 
 151 // Create a Java string from the given CF string.
 152 // returns NULL if cfString is NULL
 153 // throws and returns NULL on memory error
 154 static jstring toJavaString(JNIEnv *env, CFStringRef cfString)
 155 {
 156     if (cfString == NULL) {
 157         return NULL;
 158     } else {
 159         jstring javaString = NULL;
 160 
 161         CFIndex length = CFStringGetLength(cfString);
 162         const UniChar *constchars = CFStringGetCharactersPtr(cfString);
 163         if (constchars) {
 164             javaString = (*env)->NewString(env, constchars, length);
 165         } else {
 166             UniChar *chars = malloc(length * sizeof(UniChar));
 167             throwIfNull(chars, "toJavaString failed");
 168             CFStringGetCharacters(cfString, CFRangeMake(0, length), chars);
 169             javaString = (*env)->NewString(env, chars, length);
 170             free(chars);
 171         }
 172     badchars:
 173         return javaString;
 174     }
 175 }
 176 
 177 
 178 
 179 // Create a CF string from the given Java string.
 180 // returns NULL if javaString is NULL
 181 // throws and returns NULL on memory error
 182 static CFStringRef toCF(JNIEnv *env, jstring javaString)
 183 {
 184     if (javaString == NULL) {
 185         return NULL;
 186     } else {
 187         CFStringRef result = NULL;
 188         jsize length = (*env)->GetStringLength(env, javaString);
 189         const jchar *chars = (*env)->GetStringChars(env, javaString, NULL);
 190         throwIfNull(chars, "toCF failed");
 191         result =
 192             CFStringCreateWithCharacters(NULL, (const UniChar *)chars, length);
 193         (*env)->ReleaseStringChars(env, javaString, chars);
 194         throwIfNull(result, "toCF failed");
 195     badchars:
 196     badresult:
 197         return result;
 198     }
 199 }
 200 
 201 
 202 // Create an empty Java string array of the given size.
 203 // Throws and returns NULL on error.
 204 static jarray createJavaStringArray(JNIEnv *env, CFIndex count)
 205 {
 206     static jclass stringClass = NULL;
 207     jclass c;
 208 
 209     if (stringClass) {
 210         c = stringClass;
 211     } else {
 212         c = (*env)->FindClass(env, "java/lang/String");
 213         if ((*env)->ExceptionOccurred(env)) return NULL;
 214         stringClass = (*env)->NewGlobalRef(env, c);
 215     }
 216 
 217     return (*env)->NewObjectArray(env, count, c, NULL); // AWT_THREADING Safe (known object)
 218 }
 219 
 220 
 221 // Java accessors for CF constants.
 222 JNIEXPORT jlong JNICALL
 223 Java_java_util_prefs_MacOSXPreferencesFile_currentUser(JNIEnv *env,
 224                                                        jobject klass)
 225 {
 226     return ptr_to_jlong(kCFPreferencesCurrentUser);
 227 }
 228 
 229 JNIEXPORT jlong JNICALL
 230 Java_java_util_prefs_MacOSXPreferencesFile_anyUser(JNIEnv *env, jobject klass)
 231 {
 232     return ptr_to_jlong(kCFPreferencesAnyUser);
 233 }
 234 
 235 JNIEXPORT jlong JNICALL
 236 Java_java_util_prefs_MacOSXPreferencesFile_currentHost(JNIEnv *env,
 237                                                        jobject klass)
 238 {
 239     return ptr_to_jlong(kCFPreferencesCurrentHost);
 240 }
 241 
 242 JNIEXPORT jlong JNICALL
 243 Java_java_util_prefs_MacOSXPreferencesFile_anyHost(JNIEnv *env, jobject klass)
 244 {
 245     return ptr_to_jlong(kCFPreferencesAnyHost);
 246 }
 247 
 248 
 249 // Create an empty node.
 250 // Does not store the node in any prefs file.
 251 // returns NULL on memory error
 252 static CFMutableDictionaryRef createEmptyNode(void)
 253 {
 254     return CFDictionaryCreateMutable(NULL, 0,
 255                                      &kCFTypeDictionaryKeyCallBacks,
 256                                      &kCFTypeDictionaryValueCallBacks);
 257 }
 258 
 259 
 260 // Create a string that consists of path minus its last component.
 261 // path must end with '/'
 262 // The result will end in '/' (unless path itself is '/')
 263 static CFStringRef copyParentOf(CFStringRef path)
 264 {
 265     CFRange searchRange;
 266     CFRange slashRange;
 267     CFRange parentRange;
 268     Boolean found;
 269 
 270     searchRange = CFRangeMake(0, CFStringGetLength(path) - 1);
 271     found = CFStringFindWithOptions(path, CFSTR("/"), searchRange,
 272                                     kCFCompareBackwards, &slashRange);
 273     if (!found) return CFSTR("");
 274     parentRange = CFRangeMake(0, slashRange.location + 1); // include '/'
 275     return CFStringCreateWithSubstring(NULL, path, parentRange);
 276 }
 277 
 278 
 279 // Create a string that consists of path's last component.
 280 // path must end with '/'
 281 // The result will end in '/'.
 282 // The result will not start with '/' (unless path itself is '/')
 283 static CFStringRef copyChildOf(CFStringRef path)
 284 {
 285     CFRange searchRange;
 286     CFRange slashRange;
 287     CFRange childRange;
 288     Boolean found;
 289     CFIndex length = CFStringGetLength(path);
 290 
 291     searchRange = CFRangeMake(0, length - 1);
 292     found = CFStringFindWithOptions(path, CFSTR("/"), searchRange,
 293                                     kCFCompareBackwards, &slashRange);
 294     if (!found) return CFSTR("");
 295     childRange = CFRangeMake(slashRange.location + 1,
 296                              length - slashRange.location - 1); // skip '/'
 297     return CFStringCreateWithSubstring(NULL, path, childRange);
 298 }
 299 
 300 
 301 // Return the first three components of path, with leading and trailing '/'.
 302 // If path does not have three components, return NULL.
 303 // path must begin and end in '/'
 304 static CFStringRef copyFirstThreeComponentsOf(CFStringRef path)
 305 {
 306     CFRange searchRange;
 307     CFRange slashRange;
 308     CFRange prefixRange;
 309     CFStringRef prefix;
 310     Boolean found;
 311     CFIndex length = CFStringGetLength(path);
 312 
 313     searchRange = CFRangeMake(1, length - 1);  // skip leading '/'
 314     found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,
 315                                     &slashRange);
 316     if (!found) return NULL;  // no second slash!
 317 
 318     searchRange = CFRangeMake(slashRange.location + 1,
 319                               length - slashRange.location - 1);
 320     found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,
 321                                     &slashRange);
 322     if (!found) return NULL;  // no third slash!
 323 
 324     searchRange = CFRangeMake(slashRange.location + 1,
 325                               length - slashRange.location - 1);
 326     found = CFStringFindWithOptions(path, CFSTR("/"), searchRange, 0,
 327                                     &slashRange);
 328     if (!found) return NULL;  // no fourth slash!
 329 
 330     prefixRange = CFRangeMake(0, slashRange.location + 1); // keep last '/'
 331     prefix = CFStringCreateWithSubstring(NULL, path, prefixRange);
 332 
 333     return prefix;
 334 }
 335 
 336 
 337 // Copy the CFPreferences key and value at the base of path's tree.
 338 // path must end in '/'
 339 // topKey or topValue may be NULL
 340 // Returns NULL on error or if there is no tree for path in this file.
 341 static void copyTreeForPath(CFStringRef path, CFStringRef name,
 342                             CFStringRef user, CFStringRef host,
 343                             CFStringRef *topKey, CFDictionaryRef *topValue)
 344 {
 345     CFStringRef key;
 346     CFPropertyListRef value;
 347 
 348     if (topKey) *topKey = NULL;
 349     if (topValue) *topValue = NULL;
 350 
 351     if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) {
 352         // Top-level file. Only key "/" is an acceptable root.
 353         key = (CFStringRef) CFRetain(CFSTR("/"));
 354     } else {
 355         // Second-level file. Key must be the first three components of path.
 356         key = copyFirstThreeComponentsOf(path);
 357         if (!key) return;
 358     }
 359 
 360     value = CFPreferencesCopyValue(key, name, user, host);
 361     if (value) {
 362         if (CFGetTypeID(value) == CFDictionaryGetTypeID()) {
 363             // (key, value) is acceptable
 364             if (topKey) *topKey = (CFStringRef)CFRetain(key);
 365             if (topValue) *topValue = (CFDictionaryRef)CFRetain(value);
 366         }
 367         CFRelease(value);
 368     }
 369     CFRelease(key);
 370 }
 371 
 372 
 373 // Find the node for path in the given tree.
 374 // Returns NULL on error or if path doesn't have a node in this tree.
 375 // path must end in '/'
 376 static CFDictionaryRef copyNodeInTree(CFStringRef path, CFStringRef topKey,
 377                                       CFDictionaryRef topValue)
 378 {
 379     CFMutableStringRef p;
 380     CFDictionaryRef result = NULL;
 381 
 382     p = CFStringCreateMutableCopy(NULL, 0, path);
 383     if (!p) return NULL;
 384     CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey)));
 385     result = topValue;
 386 
 387     while (CFStringGetLength(p) > 0) {
 388         CFDictionaryRef child;
 389         CFStringRef part = NULL;
 390         CFRange slashRange = CFStringFind(p, CFSTR("/"), 0);
 391         // guaranteed to succeed because path must end in '/'
 392         CFRange partRange = CFRangeMake(0, slashRange.location + 1);
 393         part = CFStringCreateWithSubstring(NULL, p, partRange);
 394         if (!part) { result = NULL; break; }
 395         CFStringDelete(p, partRange);
 396 
 397         child = CFDictionaryGetValue(result, part);
 398         CFRelease(part);
 399         if (child  &&  CFGetTypeID(child) == CFDictionaryGetTypeID()) {
 400             // continue search
 401             result = child;
 402         } else {
 403             // didn't find target node
 404             result = NULL;
 405             break;
 406         }
 407     }
 408 
 409     CFRelease(p);
 410     if (result) return (CFDictionaryRef)CFRetain(result);
 411     else return NULL;
 412 }
 413 
 414 
 415 // Return a retained copy of the node at path from the given file.
 416 // path must end in '/'
 417 // returns NULL if node doesn't exist.
 418 // returns NULL if the value for key "path" isn't a valid node.
 419 static CFDictionaryRef copyNodeIfPresent(CFStringRef path, CFStringRef name,
 420                                          CFStringRef user, CFStringRef host)
 421 {
 422     CFStringRef topKey;
 423     CFDictionaryRef topValue;
 424     CFDictionaryRef result;
 425 
 426     copyTreeForPath(path, name, user, host, &topKey, &topValue);
 427     if (!topKey) return NULL;
 428 
 429     result = copyNodeInTree(path, topKey, topValue);
 430 
 431     CFRelease(topKey);
 432     if (topValue) CFRelease(topValue);
 433     return result;
 434 }
 435 
 436 
 437 // Create a new tree that would store path in the given file.
 438 // Only the root of the tree is created, not all of the links leading to path.
 439 // returns NULL on error
 440 static void createTreeForPath(CFStringRef path, CFStringRef name,
 441                               CFStringRef user, CFStringRef host,
 442                               CFStringRef *outTopKey,
 443                               CFMutableDictionaryRef *outTopValue)
 444 {
 445     *outTopKey = NULL;
 446     *outTopValue = NULL;
 447 
 448     // if name is "com.apple.java.util.prefs" then create tree "/"
 449     // else create tree "/foo/bar/baz/"
 450     // "com.apple.java.util.prefs.plist" is also in MacOSXPreferences.java
 451     if (CFEqual(name, CFSTR("com.apple.java.util.prefs"))) {
 452         *outTopKey = CFSTR("/");
 453         *outTopValue = createEmptyNode();
 454     } else {
 455         CFStringRef prefix = copyFirstThreeComponentsOf(path);
 456         if (prefix) {
 457             *outTopKey = prefix;
 458             *outTopValue = createEmptyNode();
 459         }
 460     }
 461 }
 462 
 463 
 464 // Return a mutable copy of the tree containing path and the dict for
 465 //   path itself. *outTopKey and *outTopValue can be used to write the
 466 //   modified tree back to the prefs file.
 467 // *outTopKey and *outTopValue must be released iff the actual return
 468 //   value is not NULL.
 469 static CFMutableDictionaryRef
 470 copyMutableNode(CFStringRef path, CFStringRef name,
 471                 CFStringRef user, CFStringRef host,
 472                 CFStringRef *outTopKey,
 473                 CFMutableDictionaryRef *outTopValue)
 474 {
 475     CFStringRef topKey = NULL;
 476     CFDictionaryRef oldTopValue = NULL;
 477     CFMutableDictionaryRef topValue;
 478     CFMutableDictionaryRef result = NULL;
 479     CFMutableStringRef p;
 480 
 481     if (outTopKey) *outTopKey = NULL;
 482     if (outTopValue) *outTopValue = NULL;
 483 
 484     copyTreeForPath(path, name, user, host, &topKey, &oldTopValue);
 485     if (!topKey) {
 486         createTreeForPath(path, name, user, host, &topKey, &topValue);
 487     } else {
 488         topValue = (CFMutableDictionaryRef)
 489             CFPropertyListCreateDeepCopy(NULL, (CFPropertyListRef)oldTopValue,
 490                                          kCFPropertyListMutableContainers);
 491     }
 492     if (!topValue) goto badtopValue;
 493 
 494     p = CFStringCreateMutableCopy(NULL, 0, path);
 495     if (!p) goto badp;
 496     CFStringDelete(p, CFRangeMake(0, CFStringGetLength(topKey)));
 497     result = topValue;
 498 
 499     while (CFStringGetLength(p) > 0) {
 500         CFMutableDictionaryRef child;
 501         CFStringRef part = NULL;
 502         CFRange slashRange = CFStringFind(p, CFSTR("/"), 0);
 503         // guaranteed to succeed because path must end in '/'
 504         CFRange partRange = CFRangeMake(0, slashRange.location + 1);
 505         part = CFStringCreateWithSubstring(NULL, p, partRange);
 506         if (!part) { result = NULL; break; }
 507         CFStringDelete(p, partRange);
 508 
 509         child = (CFMutableDictionaryRef)CFDictionaryGetValue(result, part);
 510         if (child  &&  CFGetTypeID(child) == CFDictionaryGetTypeID()) {
 511             // continue search
 512             result = child;
 513         } else {
 514             // didn't find target node - add it and continue
 515             child = createEmptyNode();
 516             if (!child) { CFRelease(part); result = NULL; break; }
 517             CFDictionaryAddValue(result, part, child);
 518             result = child;
 519         }
 520         CFRelease(part);
 521     }
 522 
 523     if (result) {
 524         *outTopKey = (CFStringRef)CFRetain(topKey);
 525         *outTopValue = (CFMutableDictionaryRef)CFRetain(topValue);
 526         CFRetain(result);
 527     }
 528 
 529     CFRelease(p);
 530  badp:
 531     CFRelease(topValue);
 532  badtopValue:
 533     if (topKey) CFRelease(topKey);
 534     if (oldTopValue) CFRelease(oldTopValue);
 535     return result;
 536 }
 537 
 538 
 539 JNIEXPORT jboolean JNICALL
 540 Java_java_util_prefs_MacOSXPreferencesFile_addNode
 541 (JNIEnv *env, jobject klass, jobject jpath,
 542  jobject jname, jlong juser, jlong jhost)
 543 {
 544     CFStringRef path = NULL;
 545     CFStringRef name = NULL;
 546 
 547     path = toCF(env, jpath);
 548     if (path != NULL) {
 549         name = toCF(env, jname);
 550     }
 551     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 552     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 553     CFDictionaryRef node = NULL;
 554     jboolean neededNewNode = false;
 555 
 556     if (!path  ||  !name) goto badparams;
 557 
 558     node = copyNodeIfPresent(path, name, user, host);
 559 
 560     if (node) {
 561         neededNewNode = false;
 562         CFRelease(node);
 563     } else {
 564         CFStringRef topKey = NULL;
 565         CFMutableDictionaryRef topValue = NULL;
 566 
 567         neededNewNode = true;
 568 
 569         // copyMutableNode creates the node if necessary
 570         node = copyMutableNode(path, name, user, host, &topKey, &topValue);
 571         throwIfNull(node, "copyMutableNode failed");
 572 
 573         CFPreferencesSetValue(topKey, topValue, name, user, host);
 574 
 575         CFRelease(node);
 576         if (topKey) CFRelease(topKey);
 577         if (topValue) CFRelease(topValue);
 578     }
 579 
 580  badnode:
 581  badparams:
 582     if (path) CFRelease(path);
 583     if (name) CFRelease(name);
 584 
 585     return neededNewNode;
 586 }
 587 
 588 
 589 JNIEXPORT void JNICALL
 590 Java_java_util_prefs_MacOSXPreferencesFile_removeNode
 591 (JNIEnv *env, jobject klass, jobject jpath,
 592  jobject jname, jlong juser, jlong jhost)
 593 {
 594     CFStringRef path = NULL;
 595     CFStringRef name = NULL;
 596 
 597     path = toCF(env, jpath);
 598     if (path != NULL) {
 599         name = toCF(env, jname);
 600     }
 601     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 602     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 603     CFStringRef parentName;
 604     CFStringRef childName;
 605     CFDictionaryRef constParent;
 606 
 607     if (!path  ||  !name) goto badparams;
 608 
 609     parentName = copyParentOf(path);
 610     throwIfNull(parentName, "copyParentOf failed");
 611     childName  = copyChildOf(path);
 612     throwIfNull(childName, "copyChildOf failed");
 613 
 614     // root node is not allowed to be removed, so parentName is never empty
 615 
 616     constParent = copyNodeIfPresent(parentName, name, user, host);
 617     if (constParent  &&  CFDictionaryContainsKey(constParent, childName)) {
 618         CFStringRef topKey;
 619         CFMutableDictionaryRef topValue;
 620         CFMutableDictionaryRef parent;
 621 
 622         parent = copyMutableNode(parentName, name, user, host,
 623                                  &topKey, &topValue);
 624         throwIfNull(parent, "copyMutableNode failed");
 625 
 626         CFDictionaryRemoveValue(parent, childName);
 627         CFPreferencesSetValue(topKey, topValue, name, user, host);
 628 
 629         CFRelease(parent);
 630         if (topKey) CFRelease(topKey);
 631         if (topValue) CFRelease(topValue);
 632     } else {
 633         // might be trying to remove the root itself in a non-root file
 634         CFStringRef topKey;
 635         CFDictionaryRef topValue;
 636         copyTreeForPath(path, name, user, host, &topKey, &topValue);
 637         if (topKey) {
 638             if (CFEqual(topKey, path)) {
 639                 CFPreferencesSetValue(topKey, NULL, name, user, host);
 640             }
 641 
 642             if (topKey) CFRelease(topKey);
 643             if (topValue) CFRelease(topValue);
 644         }
 645     }
 646 
 647 
 648  badparent:
 649     if (constParent) CFRelease(constParent);
 650     CFRelease(childName);
 651  badchildName:
 652     CFRelease(parentName);
 653  badparentName:
 654  badparams:
 655     if (path) CFRelease(path);
 656     if (name) CFRelease(name);
 657 }
 658 
 659 
 660 // child must end with '/'
 661 JNIEXPORT Boolean JNICALL
 662 Java_java_util_prefs_MacOSXPreferencesFile_addChildToNode
 663 (JNIEnv *env, jobject klass, jobject jpath, jobject jchild,
 664  jobject jname, jlong juser, jlong jhost)
 665 {
 666     // like addNode, but can put a three-level-deep dict into the root file
 667     CFStringRef path = NULL;
 668     CFStringRef child = NULL;
 669     CFStringRef name = NULL;
 670 
 671     path = toCF(env, jpath);
 672     if (path != NULL) {
 673         child = toCF(env, jchild);
 674     }
 675     if (child != NULL) {
 676         name = toCF(env, jname);
 677     }
 678     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 679     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 680     CFMutableDictionaryRef parent;
 681     CFDictionaryRef node;
 682     CFStringRef topKey;
 683     CFMutableDictionaryRef topValue;
 684     Boolean beforeAdd = false;
 685 
 686     if (!path  ||  !child  ||  !name) goto badparams;
 687 
 688     node = createEmptyNode();
 689     throwIfNull(node, "createEmptyNode failed");
 690 
 691     // copyMutableNode creates the node if necessary
 692     parent = copyMutableNode(path, name, user, host, &topKey, &topValue);
 693     throwIfNull(parent, "copyMutableNode failed");
 694     beforeAdd = CFDictionaryContainsKey(parent, child);
 695     CFDictionaryAddValue(parent, child, node);
 696     if (!beforeAdd)
 697         beforeAdd = CFDictionaryContainsKey(parent, child);
 698     else
 699         beforeAdd = false;
 700     CFPreferencesSetValue(topKey, topValue, name, user, host);
 701 
 702     CFRelease(parent);
 703     if (topKey) CFRelease(topKey);
 704     if (topValue) CFRelease(topValue);
 705  badparent:
 706     CFRelease(node);
 707  badnode:
 708  badparams:
 709     if (path) CFRelease(path);
 710     if (child) CFRelease(child);
 711     if (name) CFRelease(name);
 712     return beforeAdd;
 713 }
 714 
 715 
 716 JNIEXPORT void JNICALL
 717 Java_java_util_prefs_MacOSXPreferencesFile_removeChildFromNode
 718 (JNIEnv *env, jobject klass, jobject jpath, jobject jchild,
 719  jobject jname, jlong juser, jlong jhost)
 720 {
 721     CFStringRef path = NULL;
 722     CFStringRef child = NULL;
 723     CFStringRef name = NULL;
 724 
 725     path = toCF(env, jpath);
 726     if (path != NULL) {
 727         child = toCF(env, jchild);
 728     }
 729     if (child != NULL) {
 730         name = toCF(env, jname);
 731     }
 732     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 733     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 734     CFDictionaryRef constParent;
 735 
 736     if (!path  ||  !child  ||  !name) goto badparams;
 737 
 738     constParent = copyNodeIfPresent(path, name, user, host);
 739     if (constParent  &&  CFDictionaryContainsKey(constParent, child)) {
 740         CFStringRef topKey;
 741         CFMutableDictionaryRef topValue;
 742         CFMutableDictionaryRef parent;
 743 
 744         parent = copyMutableNode(path, name, user, host, &topKey, &topValue);
 745         throwIfNull(parent, "copyMutableNode failed");
 746 
 747         CFDictionaryRemoveValue(parent, child);
 748         CFPreferencesSetValue(topKey, topValue, name, user, host);
 749 
 750         CFRelease(parent);
 751         if (topKey) CFRelease(topKey);
 752         if (topValue) CFRelease(topValue);
 753     }
 754 
 755  badparent:
 756     if (constParent) CFRelease(constParent);
 757  badparams:
 758     if (path) CFRelease(path);
 759     if (child) CFRelease(child);
 760     if (name) CFRelease(name);
 761 }
 762 
 763 
 764 
 765 JNIEXPORT void JNICALL
 766 Java_java_util_prefs_MacOSXPreferencesFile_addKeyToNode
 767 (JNIEnv *env, jobject klass, jobject jpath, jobject jkey, jobject jvalue,
 768  jobject jname, jlong juser, jlong jhost)
 769 {
 770     CFStringRef path = NULL;
 771     CFStringRef key = NULL;
 772     CFStringRef value = NULL;
 773     CFStringRef name = NULL;
 774 
 775     path = toCF(env, jpath);
 776     if (path != NULL) {
 777         key = toCF(env, jkey);
 778     }
 779     if (key != NULL) {
 780         value = toCF(env, jvalue);
 781     }
 782     if (value != NULL) {
 783         name = toCF(env, jname);
 784     }
 785     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 786     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 787     CFMutableDictionaryRef node = NULL;
 788     CFStringRef topKey;
 789     CFMutableDictionaryRef topValue;
 790 
 791     if (!path  ||  !key  || !value  ||  !name) goto badparams;
 792 
 793     // fixme optimization: check whether old value and new value are identical
 794     node = copyMutableNode(path, name, user, host, &topKey, &topValue);
 795     throwIfNull(node, "copyMutableNode failed");
 796 
 797     CFDictionarySetValue(node, key, value);
 798     CFPreferencesSetValue(topKey, topValue, name, user, host);
 799 
 800     CFRelease(node);
 801     if (topKey) CFRelease(topKey);
 802     if (topValue) CFRelease(topValue);
 803 
 804  badnode:
 805  badparams:
 806     if (path) CFRelease(path);
 807     if (key) CFRelease(key);
 808     if (value) CFRelease(value);
 809     if (name) CFRelease(name);
 810 }
 811 
 812 
 813 JNIEXPORT void JNICALL
 814 Java_java_util_prefs_MacOSXPreferencesFile_removeKeyFromNode
 815 (JNIEnv *env, jobject klass, jobject jpath, jobject jkey,
 816  jobject jname, jlong juser, jlong jhost)
 817 {
 818     CFStringRef path = NULL;
 819     CFStringRef key = NULL;
 820     CFStringRef name = NULL;
 821 
 822     path = toCF(env, jpath);
 823     if (path != NULL) {
 824         key = toCF(env, jkey);
 825     }
 826     if (key != NULL) {
 827         name = toCF(env, jname);
 828     }
 829     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 830     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 831     CFDictionaryRef constNode;
 832 
 833     if (!path  ||  !key  ||  !name) goto badparams;
 834 
 835     constNode = copyNodeIfPresent(path, name, user, host);
 836     if (constNode  &&  CFDictionaryContainsKey(constNode, key)) {
 837         CFStringRef topKey;
 838         CFMutableDictionaryRef topValue;
 839         CFMutableDictionaryRef node;
 840 
 841         node = copyMutableNode(path, name, user, host, &topKey, &topValue);
 842         throwIfNull(node, "copyMutableNode failed");
 843 
 844         CFDictionaryRemoveValue(node, key);
 845         CFPreferencesSetValue(topKey, topValue, name, user, host);
 846 
 847         CFRelease(node);
 848         if (topKey) CFRelease(topKey);
 849         if (topValue) CFRelease(topValue);
 850     }
 851 
 852  badnode:
 853     if (constNode) CFRelease(constNode);
 854  badparams:
 855     if (path) CFRelease(path);
 856     if (key) CFRelease(key);
 857     if (name) CFRelease(name);
 858 }
 859 
 860 
 861 // path must end in '/'
 862 JNIEXPORT jstring JNICALL
 863 Java_java_util_prefs_MacOSXPreferencesFile_getKeyFromNode
 864 (JNIEnv *env, jobject klass, jobject jpath, jobject jkey,
 865  jobject jname, jlong juser, jlong jhost)
 866 {
 867     CFStringRef path = NULL;
 868     CFStringRef key = NULL;
 869     CFStringRef name = NULL;
 870 
 871     path = toCF(env, jpath);
 872     if (path != NULL) {
 873         key = toCF(env, jkey);
 874     }
 875     if (key != NULL) {
 876         name = toCF(env, jname);
 877     }
 878     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 879     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 880     CFPropertyListRef value;
 881     CFDictionaryRef node;
 882     jstring result = NULL;
 883 
 884     if (!path  ||  !key  ||  !name) goto badparams;
 885 
 886     node = copyNodeIfPresent(path, name, user, host);
 887     if (node) {
 888         value = (CFPropertyListRef)CFDictionaryGetValue(node, key);
 889         if (!value) {
 890             // key doesn't exist, or other error - no Java errors available
 891             result = NULL;
 892         } else {
 893             CFStringRef cfString = copyToCFString(env, value);
 894             if ((*env)->ExceptionOccurred(env)) {
 895                 // memory error in copyToCFString
 896                 result = NULL;
 897             } else if (cfString == NULL) {
 898                 // bogus value type in prefs file - no Java errors available
 899                 result = NULL;
 900             } else {
 901                 // good cfString
 902                 result = toJavaString(env, cfString);
 903                 CFRelease(cfString);
 904             }
 905         }
 906         CFRelease(node);
 907     }
 908 
 909  badparams:
 910     if (path) CFRelease(path);
 911     if (key) CFRelease(key);
 912     if (name) CFRelease(name);
 913 
 914     return result;
 915 }
 916 
 917 
 918 typedef struct {
 919     jarray result;
 920     JNIEnv *env;
 921     CFIndex used;
 922     Boolean allowSlash;
 923 } BuildJavaArrayArgs;
 924 
 925 // CFDictionary applier function that builds an array of Java strings
 926 //   from a CFDictionary of CFPropertyListRefs.
 927 // If args->allowSlash, only strings that end in '/' are added to the array,
 928 //   with the slash removed. Otherwise, only strings that do not end in '/'
 929 //   are added.
 930 // args->result must already exist and be large enough to hold all
 931 //   strings from the dictionary.
 932 // After complete application, args->result may not be full because
 933 //   some of the dictionary values weren't convertible to string. In
 934 //   this case, args->used will be the count of used elements.
 935 static void BuildJavaArrayFn(const void *key, const void *value, void *context)
 936 {
 937     BuildJavaArrayArgs *args = (BuildJavaArrayArgs *)context;
 938     CFPropertyListRef propkey = (CFPropertyListRef)key;
 939     CFStringRef cfString = NULL;
 940     JNIEnv *env = args->env;
 941 
 942     if ((*env)->ExceptionOccurred(env)) return; // already failed
 943 
 944     cfString = copyToCFString(env, propkey);
 945     if ((*env)->ExceptionOccurred(env)) {
 946         // memory error in copyToCFString
 947     } else if (!cfString) {
 948         // bogus value type in prefs file - no Java errors available
 949     } else if (args->allowSlash != CFStringHasSuffix(cfString, CFSTR("/"))) {
 950         // wrong suffix - ignore
 951     } else {
 952         // good cfString
 953         jstring javaString;
 954         if (args->allowSlash) {
 955             CFRange range = CFRangeMake(0, CFStringGetLength(cfString) - 1);
 956             CFStringRef s = CFStringCreateWithSubstring(NULL, cfString, range);
 957             CFRelease(cfString);
 958             cfString = s;
 959         }
 960         if (CFStringGetLength(cfString) <= 0) goto bad; // ignore empty
 961         javaString = toJavaString(env, cfString);
 962         if ((*env)->ExceptionOccurred(env)) goto bad;
 963         (*env)->SetObjectArrayElement(env, args->result,args->used,javaString);
 964         if ((*env)->ExceptionOccurred(env)) goto bad;
 965         args->used++;
 966     }
 967 
 968  bad:
 969     if (cfString) CFRelease(cfString);
 970 }
 971 
 972 
 973 static jarray getStringsForNode(JNIEnv *env, jobject klass, jobject jpath,
 974                                 jobject jname, jlong juser, jlong jhost,
 975                                 Boolean allowSlash)
 976 {
 977     CFStringRef path = NULL;
 978     CFStringRef name = NULL;
 979 
 980     path = toCF(env, jpath);
 981     if (path != NULL) {
 982         name = toCF(env, jname);
 983     }
 984     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
 985     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
 986     CFDictionaryRef node;
 987     jarray result = NULL;
 988     CFIndex count;
 989 
 990     if (!path  ||  !name) goto badparams;
 991 
 992     node = copyNodeIfPresent(path, name, user, host);
 993     if (!node) {
 994         result = createJavaStringArray(env, 0);
 995     } else {
 996         count = CFDictionaryGetCount(node);
 997         result = createJavaStringArray(env, count);
 998         if (result) {
 999             BuildJavaArrayArgs args;
1000             args.result = result;
1001             args.env = env;
1002             args.used = 0;
1003             args.allowSlash = allowSlash;
1004             CFDictionaryApplyFunction(node, BuildJavaArrayFn, &args);
1005             if (!(*env)->ExceptionOccurred(env)) {
1006                 // array construction succeeded
1007                 if (args.used < count) {
1008                     // finished array is smaller than expected.
1009                     // Make a new array of precisely the right size.
1010                     jarray newresult = createJavaStringArray(env, args.used);
1011                     if (newresult) {
1012                         JVM_ArrayCopy(env,0, result,0, newresult,0, args.used);
1013                         result = newresult;
1014                     }
1015                 }
1016             }
1017         }
1018 
1019         CFRelease(node);
1020     }
1021 
1022  badparams:
1023     if (path) CFRelease(path);
1024     if (name) CFRelease(name);
1025 
1026     return result;
1027 }
1028 
1029 
1030 JNIEXPORT jarray JNICALL
1031 Java_java_util_prefs_MacOSXPreferencesFile_getKeysForNode
1032 (JNIEnv *env, jobject klass, jobject jpath,
1033  jobject jname, jlong juser, jlong jhost)
1034 {
1035     return getStringsForNode(env, klass, jpath, jname, juser, jhost, false);
1036 }
1037 
1038 JNIEXPORT jarray JNICALL
1039 Java_java_util_prefs_MacOSXPreferencesFile_getChildrenForNode
1040 (JNIEnv *env, jobject klass, jobject jpath,
1041  jobject jname, jlong juser, jlong jhost)
1042 {
1043     return getStringsForNode(env, klass, jpath, jname, juser, jhost, true);
1044 }
1045 
1046 
1047 // Returns false on error instead of throwing.
1048 JNIEXPORT jboolean JNICALL
1049 Java_java_util_prefs_MacOSXPreferencesFile_synchronize
1050 (JNIEnv *env, jobject klass,
1051  jstring jname, jlong juser, jlong jhost)
1052 {
1053     CFStringRef name = toCF(env, jname);
1054     CFStringRef user = (CFStringRef)jlong_to_ptr(juser);
1055     CFStringRef host = (CFStringRef)jlong_to_ptr(jhost);
1056     jboolean result = 0;
1057 
1058     if (name) {
1059         result = CFPreferencesSynchronize(name, user, host);
1060         CFRelease(name);
1061     }
1062 
1063     return result;
1064 }