It's a known problem on MacOS that the supplied command line tools (chmod) cannot remove an ACL from a symbolic link: https://discussions.apple.com/thread/1440377?start=0&tstart=0
So I thought I'd break out the C compiler and get it done .. before I realised that ACLs hadn't been invented the last time I used C on Unix.
Can anyone say what system calls (MacOS for preference, if this isn't yet a Unix-wide standard) I can draw on to make this happen?
M.
The function acl_get_link_np(path, ACL_TYPE_EXTENDED)should let you get the ACLs for a path without following symlinks.
Also, I'd think that acl_get_link_np(fd, ACL_TYPE_EXTENDED) should work if you first open the symlink with open(path, O_NOFOLLOW).
To remove all ACLs from a file, you'd use this code:
acl_t acl = acl_get_link_np (path, ACL_TYPE_EXTENDED);
if (acl) {
acl_entry_t entry;
int res = acl_get_entry (acl, ACL_FIRST_ENTRY, &entry);
bool didModify = false;
while (res == 0) {
res = acl_delete_entry (acl, entry);
if (res) break;
didModify = true;
res = acl_get_entry (acl, ACL_NEXT_ENTRY, &entry);
}
if (didModify) {
int res = acl_set_link_np (path, ACL_TYPE_EXTENDED, acl);
if (res) {
NSLog(#"%s – Write ACL: %s", path, strerror(errno));
}
}
acl_free (acl);
}
Related
I want to print ACL text like the on in "ls" command, but I have no idea how to get these permissions, any lead?
Here's an example for getting the file (POSIX) ACL in C code, and printing them:
const char *path = "/path/to/file";
acl_t acl = acl_get_link_np (path, ACL_TYPE_EXTENDED);
if (acl == NULL) {
printf("Error: %s\n", strerror(errno));
} else {
const char *desc = acl_to_text (acl, NULL);
printf("%s\n", desc);
acl_free ((void*)desc);
}
acl_free (acl);
The group will be shown as a UUID, not as a name such as "everyone", though.
If you want to parse the ACL yourself instead of getting the default printout, type man acl in Terminal.
How can I get file path to Desktop directory as a string on macOS.
I need it to be done in pure C or with some C-level framework.
If you insist on using only C (why?), then your only choice is to use deprecated APIs:
#include <limits.h>
#include <CoreServices/CoreServices.h>
...
FSRef fsref;
UInt8 path[PATH_MAX];
if (FSFindFolder(kUserDomain, kDesktopFolderType, kDontCreateFolder, &fsref) == noErr &&
FSRefMakePath(&fsref, path, sizeof(path)) == noErr)
{
// Make use of path
}
If you need a CFURL rather than a path, you can use CFURLCreateFromFSRef() rather than FSRefMakePath().
Actually, while researching this, I found an API I hadn't known about. Apparently, you can use this, which apparently comes from Cocoa but uses only C types:
#include <limits.h>
#include <NSSystemDirectories.h>
char path[PATH_MAX];
NSSearchPathEnumerationState state = NSStartSearchPathEnumeration(NSDesktopDirectory, NSUserDomainMask);
while (state = NSGetNextSearchPathEnumeration(state, path))
{
// Handle path
}
The form of the API is that it may return multiple results (one on each iteration of the loop), but you should get only one for the specific use here. In that case, you can change the while to and if.
Note that, with this API, returned paths for directories in the user domain may use "~" rather than the absolute path to the user's home directory. You'll have to resolve that yourself.
Here's a short function, which works on more Unix based systems than just macOS and returns the current user's desktop folder:
#include <limits.h>
#include <stdlib.h>
/**
* Returns the path to the current user's desktop.
*/
char *path2desktop(void) {
static char real_public_path[PATH_MAX + 1] = {0};
if (real_public_path[0])
return real_public_path;
strcpy(real_public_path, getenv("HOME"));
memcpy(real_public_path + strlen(real_public_path), "/Desktop", 8);
return real_public_path;
}
The path will only be computed once.
If the function is called more than once, the old result will be returned (not thread-safe, unless the first call was protected).
I ended with usage of Objective-C in such way:
//
// main.m
// search_path_for_dir
//
// Created by Michal Ziobro on 23/09/2016.
// Copyright © 2016 Michal Ziobro. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
if(argc != 3)
return 1;
#autoreleasepool {
NSArray *paths = NSSearchPathForDirectoriesInDomains(atoi(argv[1]), atoi(argv[2]), YES);
NSString *path = [paths objectAtIndex:0];
[path writeToFile:#"/dev/stdout" atomically:NO encoding:NSUTF8StringEncoding error:nil];
}
return 0;
}
And than from command line I can execute this program in that way:
./search_path_for_dir 12 1
12 - NSDesktopDirectory
1 - NSUserDomainMask
I am using script in C that executes this program from command line and retrieves its output.
Here's C example calling this mini Cocoa App:
CFStringRef FSGetFilePath(int directory, int domainMask) {
CFStringRef scheme = CFSTR("file:///");
CFStringRef absolutePath = FSGetAbsolutePath(directory, domainMask);
CFMutableStringRef filePath = CFStringCreateMutable(NULL, 0);
if (filePath) {
CFStringAppend(filePath, scheme);
CFStringAppend(filePath, absolutePath);
}
CFRelease(scheme);
CFRelease(absolutePath);
return filePath;
}
CFStringRef FSGetAbsolutePath(int directory, int domainMask) {
char path_cmd[BUF_SIZE];
sprintf(path_cmd, "./tools/search_path_for_dir %d %d", directory, domainMask);
char *path = exec_cmd(path_cmd);
return CFStringCreateWithCString(kCFAllocatorDefault, path, kCFStringEncodingUTF8);
}
I'm trying to compile libUnihan code with MinGW, but have run into a function which requires porting. The purpose of the function is to get a canonical path representation. It uses pwd.h (which is POSIX, and MinGW isn't) so it can account for the use of '~' to mean the home directory by retrieving a passwd struct, which contains pw_dir. I did find a little information here, and a port of realpath here, but I am still entirely at a loss as to how to deal with this. With MinGW, I still have a home directory represented by ~ and located at /home/nate, but since it isn't POSIX, I don't have pwd.h to help me find where this home directory is.
Q: How can I port the function below to work properly with MinGW?
/**
* Return the canonicalized absolute pathname.
*
* It works exactly the same with realpath(3), except this function can handle the path with ~,
* where realpath cannot.
*
* #param path The path to be resolved.
* #param resolved_path Buffer for holding the resolved_path.
* #return resolved path, NULL is the resolution is not sucessful.
*/
gchar*
truepath(const gchar *path, gchar *resolved_path){
gchar workingPath[PATH_MAX];
gchar fullPath[PATH_MAX];
gchar *result=NULL;
g_strlcpy(workingPath,path,PATH_MAX);
// printf("*** path=%s \n",path);
if ( workingPath[0] != '~' ){
result = realpath(workingPath, resolved_path);
}else{
gchar *firstSlash, *suffix, *homeDirStr;
struct passwd *pw;
// initialize variables
firstSlash = suffix = homeDirStr = NULL;
firstSlash = strchr(workingPath, DIRECTORY_SEPARATOR);
if (firstSlash == NULL)
suffix = "";
else
{
*firstSlash = 0; // so userName is null terminated
suffix = firstSlash + 1;
}
if (workingPath[1] == '\0')
pw = getpwuid( getuid() );
else
pw = getpwnam( &workingPath[1] );
if (pw != NULL)
homeDirStr = pw->pw_dir;
if (homeDirStr != NULL){
gint ret=g_sprintf(fullPath, "%s%c%s", homeDirStr, DIRECTORY_SEPARATOR, suffix);
if (ret>0){
result = realpath(fullPath, resolved_path);
}
}
}
return result;
}
The purpose is to implement ~[username]/ remapping logic. This sort of code makes sense in Linux/UNIX environments, but the most common use is just to refer to the user's own home directory.
For expediency, I'd just add support for the common case - ~/ - i.e. the current user, and not bother supporting the more general case - have it fail with an obvious error in that case.
The function to get the current user's home directory is SHGetFolderPath.
#include <windows.h>
char homeDirStr[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, homeDirStr))) {
// Do something with the path
} else {
// Do something else
}
In the case of a failed lookup of the user, the code you pasted does not try to replace that string, but simply returns NULL, so you could emulate that.
I'm trying to archive files for a cross-platform application, and it looks like Minizip (built on zlib) is about as portable as archivers come.
When I try to run the following dummy code, however, I get a system error [my executable] has stopped working. Windows can check online for a solution to the problem.
Can anyone help me see how to use this library? — (there's no doc or tutorial anywhere that I can find)
zip_fileinfo zfi;
int main()
{
zipFile zf = zipOpen("myarch.zip",APPEND_STATUS_ADDINZIP);
int ret = zipOpenNewFileInZip(zf,
"myfile.txt",
&zfi,
NULL, 0,
NULL, 0,
"my comment for this interior file",
Z_DEFLATED,
Z_NO_COMPRESSION
);
zipCloseFileInZip(zf);
zipClose(zf, "my comment for exterior file");
return 0;
}
Specs: Msys + MinGW, Windows 7, using zlibwapi.dll from zlib125dll.zip/dll32
Since I found this question via Google and it didn't contain any complete, working code, I am providing some here for future visitors.
int CreateZipFile (std::vector<wstring> paths)
{
zipFile zf = zipOpen(std::string(destinationPath.begin(), destinationPath.end()).c_str(), APPEND_STATUS_CREATE);
if (zf == NULL)
return 1;
bool _return = true;
for (size_t i = 0; i < paths.size(); i++)
{
std::fstream file(paths[i].c_str(), std::ios::binary | std::ios::in);
if (file.is_open())
{
file.seekg(0, std::ios::end);
long size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> buffer(size);
if (size == 0 || file.read(&buffer[0], size))
{
zip_fileinfo zfi = { 0 };
std::wstring fileName = paths[i].substr(paths[i].rfind('\\')+1);
if (S_OK == zipOpenNewFileInZip(zf, std::string(fileName.begin(), fileName.end()).c_str(), &zfi, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION))
{
if (zipWriteInFileInZip(zf, size == 0 ? "" : &buffer[0], size))
_return = false;
if (zipCloseFileInZip(zf))
_return = false;
file.close();
continue;
}
}
file.close();
}
_return = false;
}
if (zipClose(zf, NULL))
return 3;
if (!_return)
return 4;
return S_OK;
}
The minizip library does come with examples; minizip.c for zipping and miniunz.c for unzipping. Both are command line utilities that show how to use the library. They are a mess though.
You also need to fill the zfi zip_fileinfo. At the very least you should initialize the structure to zero. zfi contains information about the file you want to store using zipOpenNewFileInZip. The structure should contain the date and attributes of "myfile.txt".
I recommend using PKWARE Desktop to diagnosis zip issues. It shows the structure/properties of the files in the ZIP and the ZIP file itself. When I opened the myarch.zip it told me there were errors. I drilled down into the file properties and found that the attributes were off.
The minizip lib is well documented. Just open the zip.h for details.
I can tell you here, you may have passed a wrong parameter for zipOpen. (APPEND_STATUS_ADDINZIP requires an existing zip file!)
Also, please check whether zipOpen returns a valid zipFile handle.
I'm currently working on program, which must display information about mounted flash drive. I want to display full space, free space, file system type and volume name. But problem is that, i can't find any API through which i can get volume name(volume label). Is there any api to do this?
p.s. full space, free space and file system type i'm getting via statfs function
Assuming that you work on a recent desktop-like distribution (Fedora, Ubuntu, etc.), you have HAL daemon running and a D-Bus session.
Within org.freedesktop.UDisks namespace you can find the object that represents this drive (say org/freedekstop/UDisks/devices/sdb/. It implements org.freedesktop.UDisks.interface. This interface has all the properties that you can dream of, including UUID (IdUuid), FAT label (IdLabel), all the details about filesystem, SMART status (if the drive supports that) etc. etc.
How to use D-Bus API in C is a topic for another question. I assume that's been already discussed in detail -- just search [dbus] and [c] tags.
Flash drives are generally FAT32, which means the "name" that you're looking for is probably the FAT drive label. The most common linux command to retrieve that information is mlabel from the mtools package.
The command looks like this:
[root#localhost]$ mlabel -i /dev/sde1 -s ::
Volume label is USB-DISK
This program works by reading the raw FAT header of the filesystem and retrieving the label from that data. You can look at the source code for the applciation to see how you can replicate the parsing of FAT data in your own application... or you can simply execute run the mlabel binary and read the result into your program. The latter sounds simpler to me.
To call the methods:
kernResult = self->FindEjectableCDMedia(&mediaIterator);
if (KERN_SUCCESS != kernResult) {
printf("FindEjectableCDMedia returned 0x%08x\n", kernResult);
}
kernResult = self->GetPath(mediaIterator, bsdPath, sizeof(bsdPath));
if (KERN_SUCCESS != kernResult) {
printf("GetPath returned 0x%08x\n", kernResult);
}
and the methods:
// Returns an iterator across all DVD media (class IODVDMedia). Caller is responsible for releasing
// the iterator when iteration is complete.
kern_return_t ScanPstEs::FindEjectableCDMedia(io_iterator_t *mediaIterator)
{
kern_return_t kernResult;
CFMutableDictionaryRef classesToMatch;
// CD media are instances of class kIODVDMediaTypeROM
classesToMatch = IOServiceMatching(kIODVDMediaClass);
if (classesToMatch == NULL) {
printf("IOServiceMatching returned a NULL dictionary.\n");
} else {
CFDictionarySetValue(classesToMatch, CFSTR(kIODVDMediaClass), kCFBooleanTrue);
}
kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, mediaIterator);
return kernResult;
}
// Given an iterator across a set of CD media, return the BSD path to the
// next one. If no CD media was found the path name is set to an empty string.
kern_return_t GetPath(io_iterator_t mediaIterator, char *Path, CFIndex maxPathSize)
{
io_object_t nextMedia;
kern_return_t kernResult = KERN_FAILURE;
DADiskRef disk = NULL;
DASessionRef session = NULL;
CFDictionaryRef props = NULL;
char * bsdPath = '\0';
*Path = '\0';
nextMedia = IOIteratorNext(mediaIterator);
if (nextMedia) {
CFTypeRef bsdPathAsCFString;
bsdPathAsCFString = IORegistryEntryCreateCFProperty(nextMedia,CFSTR(kIOBSDNameKey),kCFAllocatorDefault,0);
if (bsdPathAsCFString) {
//strlcpy(bsdPath, _PATH_DEV, maxPathSize);
// Add "r" before the BSD node name from the I/O Registry to specify the raw disk
// node. The raw disk nodes receive I/O requests directly and do not go through
// the buffer cache.
//strlcat(bsdPath, "r", maxPathSize);
size_t devPathLength = strlen(bsdPath);
if (CFStringGetCString( (CFStringRef)bsdPathAsCFString , bsdPath + devPathLength,maxPathSize - devPathLength, kCFStringEncodingUTF8)) {
qDebug("BSD path: %s\n", bsdPath);
kernResult = KERN_SUCCESS;
}
session = DASessionCreate(kCFAllocatorDefault);
if(session == NULL) {
qDebug("Can't connect to DiskArb\n");
return -1;
}
disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsdPath);
if(disk == NULL) {
CFRelease(session);
qDebug( "Can't create DADisk for %s\n", bsdPath);
return -1;
}
props = DADiskCopyDescription(disk);
if(props == NULL) {
CFRelease(session);
CFRelease(disk);
qDebug("Can't get properties for %s\n",bsdPath);
return -1;
}
CFStringRef daName = (CFStringRef )CFDictionaryGetValue(props, kDADiskDescriptionVolumeNameKey);
CFStringGetCString(daName,Path,sizeof(Path),kCFStringEncodingUTF8);
if(daName) {
qDebug("%s",Path);
CFRetain(daName);
}
CFRelease(daName);
CFRelease(props);
CFRelease(disk);
CFRelease(session);
CFRelease(bsdPathAsCFString);
}
IOObjectRelease(nextMedia);
}
return kernResult;
}