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