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