int chown_recursive(const char* path, uid_t uid, gid_t gid) { struct dirent *dp; DIR *dir = opendir(path); if (dir == NULL) { // not a directory, carry on return -1; } char *subpath = malloc(sizeof(char)*PATH_MAX); int pathlen = strlen(path); while ((dp = readdir(dir)) != NULL) { if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) continue; if (strlen(dp->d_name) + pathlen + 2/*NUL and slash*/ > PATH_MAX) { return -1; } strcpy(subpath, path); strcat(subpath, "/"); strcat(subpath, dp->d_name); if(chown(subpath, uid, gid) < 0) { return -1; } chown_recursive(subpath, uid, gid); } free(subpath); closedir(dir); return 0; }
bool chown_recursive(const char *path, uid_t owner, uid_t group, bool include_self) { if (!path) { return false; } if (include_self && chown(path, owner, group) != 0) { return false; } DIR *dir; struct dirent *e; dir = opendir(path); if (!dir) { return false; } char *item = NULL; while ((e = readdir(dir)) != NULL) { if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) { continue; } item = string_concat(path, "/", e->d_name, NULL); if (!item) { goto fail; } if (e->d_type == DT_REG) { if (chown(item, owner, group) != 0) { goto fail; } } else if (e->d_type == DT_DIR) { if (chown_recursive(item, owner, group, true) != 0) { goto fail; } } free(item); item = NULL; } closedir(dir); return true; fail: if (item) { free(item); } closedir(dir); return false; }
jboolean native_chown(JNIEnv* env, jclass clazz, jstring name, jint uid, jint gid, jboolean recursive) { if (!name) return JNI_FALSE; const char *path = (*env)->GetStringUTFChars(env, name, NULL); LOGI("native_chown %s %d %d", path, uid, gid); if(chown(path, uid, gid) < 0) return -1; if(recursive) { return chown_recursive(path, (uid_t)uid, (uid_t)gid) >= 0; } return 0; }
OSStatus DoCopyPaths(COMMAND_PROC_ARGUMENTS) { #pragma unused (auth) #pragma unused (userData) OSStatus retval = noErr; // Pre-conditions // userData may be NULL assert(request != NULL); assert(response != NULL); // asl may be NULL // aslMsg may be NULL // Get Info from arguments and assert that it's a CFArrayRef CFArrayRef infos = CFDictionaryGetValue(request, CFSTR(kInfos)) ; assert(infos != NULL) ; assert(CFGetTypeID(infos) == CFArrayGetTypeID()) ; CFIndex nFiles = CFArrayGetCount(infos) ; CFIndex i ; int nSucceeded = 0 ; CFMutableDictionaryRef errors = CFDictionaryCreateMutable( NULL, nFiles, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ) ; for (i=0; i<nFiles; i++) { CFDictionaryRef info = CFArrayGetValueAtIndex(infos, i) ; assert(CFGetTypeID(info) == CFDictionaryGetTypeID()) ; CFStringRef sourcePath = CFDictionaryGetValue( info, CFSTR(kSourcePath) ) ; assert(CFGetTypeID(sourcePath) == CFStringGetTypeID()) ; CFStringRef destinPath = CFDictionaryGetValue( info, CFSTR(kDestinPath) ) ; assert(CFGetTypeID(destinPath) == CFStringGetTypeID()) ; Boolean ok = true ; int16_t result ; char sourcePathC[SSY_UNIX_PATH_UTILS_MAX_PATH_CHARS] ; if (ok) { ok = CFStringGetCString( sourcePath, sourcePathC, SSY_UNIX_PATH_UTILS_MAX_PATH_CHARS, kCFStringEncodingASCII ) ; if (!ok) { CFDictionaryAddValue(response, sourcePath, CFSTR("Name too long")) ; } } char destinPathC[SSY_UNIX_PATH_UTILS_MAX_PATH_CHARS] ; if (ok) { ok = CFStringGetCString( destinPath, destinPathC, SSY_UNIX_PATH_UTILS_MAX_PATH_CHARS, kCFStringEncodingASCII ) ; if (!ok) { CFDictionaryAddValue(response, destinPath, CFSTR("Name too long")) ; } } if (ok) { if (CFStringGetLength(destinPath) > 0) { // destinPath is a string if (ok) { copyfile_state_t state ; state = copyfile_state_alloc() ; result = copyfile( sourcePathC, destinPathC, state, COPYFILE_ALL + (1<<15) ) ; // (1<<15) is defined as COPYFILE_RECURSIVE, but it is only available // in the Mac OS 10.6 SDK. So I cheated. If this function is run // under Mac OS 10.5, the (1<<15) is ignored and a nonrecursive // copy is performed. /* Experiment results : How copyfile(3) handles trailing slashes If you put a trailing slash on the source path, it results in the *contents* of the source directory being copied into the destination directory. Otherwise, it results in the source directory itself being copied into the destination directory. Trailing slash on the destination path does not matter. In pictures, say that you have a source directory S containing a file F, and want to copy this to a destination directory D. copyfile ("/S", "/D", s, COPYFILE_RECURSIVE) ; // results in /D/S/F copyfile ("/S", "/D/", s, COPYFILE_RECURSIVE) ; // results in /D/S/F copyfile ("/S/", "/D", s, COPYFILE_RECURSIVE) ; // results in /D/F copyfile ("/S/", "/D/", s, COPYFILE_RECURSIVE) ; // results in /D/F */ int logLevel = (result == 0) ? ASL_LEVEL_DEBUG : ASL_LEVEL_ERR ; asl_log( asl, aslMsg, logLevel, "copyfile result=%d errno=%d\nsource: %s\ndestin: %s", result, errno, sourcePathC, destinPathC ) ; copyfile_state_free(state) ; if (result < 0) { CFDictionaryRef error = CreateErrorDictionary("copyfile", result, errno, NULL) ; CFDictionaryAddValue(errors, sourcePath, error) ; CFQRelease(error) ; ok = false ; } } uid_t destin_uid = 501 ; uid_t destin_gid = -1 ; // Do not try and change the group ID. /* Prior to BookMacster version 1.5.7, destin_gid was determined from UnixOwnerIDs() like destin_uid. However, I then found that on MacMini1-jk, the owner of the Safari folder was 501, and this caused the subsequent call to chown_recursive() to fail with result -1. As a matter of fact, I can't even do this in Terminal with sudo, as shown in this transcript… Note that jk=501. MacMini2:Volumes jk$ sudo ls -al /Volumes/MacMini1-1/Users/jk/Library/ | grep Safari Password: drwxr-xr-x@ 19 jk 501 602 Jun 11 17:29 Safari MacMini2:Volumes jk$ sudo ls -al /Volumes/MacMini1-1/Users/jk/Library/Safari/Bookmarks.plist -rw-r--r-- 1 jk staff 621 Jun 11 17:29 /Volumes/MacMini1-1/Users/jk/Library/Safari/Bookmarks.plist MacMini2:Volumes jk$ sudo chown 501:501 /Volumes/MacMini1-1/Users/jk/Library/Safari/Bookmarks.plist chown: /Volumes/MacMini1-1/Users/jk/Library/Safari/Bookmarks.plist: Operation not permitted You see all of the other folders in /Volumes/MacMini1-1/Users/jk/Library/ have gid = staff, but the Safari folder has gid=501. Note that even with sudo, the system will not let me change Bookmarks.plist gid to 501. */ if (ok) { result = UnixOwnerIDs(destinPathC, &destin_uid, NULL) ; if (result != 0) { char moreInfo[255] ; // Incorrect usage of sprintf crashed; fixed in version 18 of this tool (BookMacster 1.10.0) snprintf (moreInfo, 255, "destin_uid=%d destinPath=%s", destin_uid, destinPathC) ; CFDictionaryRef error = CreateErrorDictionary("UnixOwnerIDs", result, errno, moreInfo) ; CFDictionaryAddValue(errors, sourcePath, error) ; CFQRelease(error) ; ok = false ; } } if (ok) { result = chown_recursive(destinPathC, destin_uid, destin_gid) ; asl_log( asl, aslMsg, ASL_LEVEL_DEBUG, "chown result=%d setting [%d,%d] for %s", result, destin_uid, destin_gid, destinPathC ) ; if (result != 0) { char moreInfo[255] ; // Incorrect usage of sprintf crashed; fixed in version 18 of this tool (BookMacster 1.10.0) snprintf (moreInfo, 255, "destin_uid=%d destin_gid=%d destinPath=%s", destin_uid, destin_gid, destinPathC) ; CFDictionaryRef error = CreateErrorDictionary("chown", result, errno, moreInfo) ; CFDictionaryAddValue(errors, sourcePath, error) ; CFQRelease(error) ; ok = false ; } } } else { // destinPath is an empty string, meaning we need to delete sourcePath int result = unlink(sourcePathC) ; if (result < 0) { CFDictionaryRef error = CreateErrorDictionary("unlink", result, errno, NULL) ; CFDictionaryAddValue(errors, sourcePath, error) ; CFQRelease(error) ; ok = false ; } } } if (ok) { nSucceeded++ ; } } // Since the COMMAND_PROC_ARGUMENTS give us by BetterAuthorizationSample // does not have an explicit vehicle for returning error details, we // add our 'errors' dictionary to the 'response' dictionary. CFDictionaryAddValue(response, CFSTR(kErrors), errors) ; CFRelease(errors) ; asl_log( asl, aslMsg, ASL_LEVEL_DEBUG, "DoCopyPaths success=%d/%d requested files", nSucceeded, (int)nFiles ) ; // Return the number of file copy operations that failed retval = (OSStatus)(nFiles - nSucceeded) ; return retval ; }