/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include "uunxapi.hxx" #include "system.hxx" #include "unixerrnostring.hxx" #include #include #include #include #include #include #include #include #ifdef ANDROID #include #endif OString osl::OUStringToOString(std::u16string_view s) { return rtl::OUStringToOString(s, osl_getThreadTextEncoding()); } #if HAVE_FEATURE_MACOSX_SANDBOX #include #include #include static NSUserDefaults *userDefaults = NULL; static bool isSandboxed = false; static void do_once() { SecCodeRef code; OSStatus rc = SecCodeCopySelf(kSecCSDefaultFlags, &code); SecStaticCodeRef staticCode; if (rc == errSecSuccess) rc = SecCodeCopyStaticCode(code, kSecCSDefaultFlags, &staticCode); CFDictionaryRef signingInformation; if (rc == errSecSuccess) rc = SecCodeCopySigningInformation(staticCode, kSecCSRequirementInformation, &signingInformation); CFDictionaryRef entitlements = NULL; if (rc == errSecSuccess) entitlements = (CFDictionaryRef) CFDictionaryGetValue(signingInformation, kSecCodeInfoEntitlementsDict); if (entitlements != NULL) if (CFDictionaryGetValue(entitlements, CFSTR("com.apple.security.app-sandbox")) != NULL) isSandboxed = true; if (isSandboxed) userDefaults = [NSUserDefaults standardUserDefaults]; } typedef struct { NSURL *scopeURL; NSAutoreleasePool *pool; } accessFilePathState; static accessFilePathState * prepare_to_access_file_path( const char *cpFilePath ) { static pthread_once_t once = PTHREAD_ONCE_INIT; pthread_once(&once, &do_once); NSURL *fileURL = nil; NSData *data = nil; BOOL stale; accessFilePathState *state; if (!isSandboxed) return NULL; // If malloc() fails we are screwed anyway state = (accessFilePathState*) malloc(sizeof(accessFilePathState)); state->pool = [[NSAutoreleasePool alloc] init]; state->scopeURL = nil; if (userDefaults != nil) fileURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:cpFilePath]]; if (fileURL != nil) data = [userDefaults dataForKey:[@"bookmarkFor:" stringByAppendingString:[fileURL absoluteString]]]; if (data != nil) state->scopeURL = [NSURL URLByResolvingBookmarkData:data options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&stale error:nil]; if (state->scopeURL != nil) [state->scopeURL startAccessingSecurityScopedResource]; return state; } static void done_accessing_file_path( const char * /*cpFilePath*/, accessFilePathState *state ) { if (!isSandboxed) return; int saved_errno = errno; if (state->scopeURL != nil) [state->scopeURL stopAccessingSecurityScopedResource]; [state->pool release]; free(state); errno = saved_errno; } #else typedef void accessFilePathState; #define prepare_to_access_file_path( cpFilePath ) nullptr #define done_accessing_file_path( cpFilePath, state ) ((void) cpFilePath, (void) state) #endif #ifdef MACOSX /* * Helper function for resolving Mac native alias files (not the same as unix alias files) * and to return the resolved alias as OString */ static OString macxp_resolveAliasAndConvert(OString const & p) { char path[PATH_MAX]; if (p.getLength() < PATH_MAX) { strcpy(path, p.getStr()); macxp_resolveAlias(path, PATH_MAX); return path; } return p; } #endif /* MACOSX */ int osl::access(const OString& pstrPath, int mode) { OString fn = pstrPath; #ifdef ANDROID if (fn == "/assets" || fn.startsWith("/assets/")) { struct stat stat; if (lo_apk_lstat(fn.getStr(), &stat) == -1) return -1; if (mode & W_OK) { errno = EACCES; return -1; } return 0; } #endif #ifdef MACOSX fn = macxp_resolveAliasAndConvert(fn); #endif accessFilePathState *state = prepare_to_access_file_path(fn.getStr()); int result = ::access(fn.getStr(), mode); int saved_errno = errno; if (result == -1) SAL_INFO("sal.file", "access(" << fn << ",0" << std::oct << mode << std::dec << "): " << UnixErrnoString(saved_errno)); else SAL_INFO("sal.file", "access(" << fn << ",0" << std::oct << mode << std::dec << "): OK"); done_accessing_file_path(fn.getStr(), state); errno = saved_errno; return result; } namespace { OString toOString(OString const & s) { return s; } OString toOString(std::u16string_view s) { return osl::OUStringToOString(s); } template T fromOString(OString const &) = delete; template<> OString fromOString(OString const & s) { return s; } template<> OUString fromOString(OString const & s) { return OStringToOUString(s, osl_getThreadTextEncoding()); } template bool realpath_(const T& pstrFileName, T& ppstrResolvedName) { OString fn = toOString(pstrFileName); #if defined ANDROID || defined(EMSCRIPTEN) #if defined ANDROID if (fn == "/assets" || fn.startsWith("/assets/")) #else if (fn == "/instdir" || fn.startsWith("/instdir/")) #endif { if (osl::access(fn, F_OK) == -1) return false; ppstrResolvedName = pstrFileName; return true; } #endif // ANDROID || EMSCRIPTEN #ifdef MACOSX fn = macxp_resolveAliasAndConvert(fn); #endif accessFilePathState *state = prepare_to_access_file_path(fn.getStr()); char rp[PATH_MAX]; bool bRet = realpath(fn.getStr(), rp); int saved_errno = errno; if (!bRet) SAL_INFO("sal.file", "realpath(" << fn << "): " << UnixErrnoString(saved_errno)); else SAL_INFO("sal.file", "realpath(" << fn << "): OK"); done_accessing_file_path(fn.getStr(), state); if (bRet) { ppstrResolvedName = fromOString(OString(rp)); } errno = saved_errno; return bRet; } } bool osl::realpath(const OUString& pustrFileName, OUString& ppustrResolvedName) { return realpath_(pustrFileName, ppustrResolvedName); } bool osl::realpath(const OString& pstrFileName, OString& ppstrResolvedName) { return realpath_(pstrFileName, ppstrResolvedName); } int stat_c(const char* cpPath, struct stat* buf) { #ifdef ANDROID if (strncmp(cpPath, "/assets", sizeof("/assets")-1) == 0 && (cpPath[sizeof("/assets")-1] == '\0' || cpPath[sizeof("/assets")-1] == '/')) return lo_apk_lstat(cpPath, buf); #endif accessFilePathState *state = prepare_to_access_file_path(cpPath); int result = stat(cpPath, buf); int saved_errno = errno; if (result == -1) SAL_INFO("sal.file", "stat(" << cpPath << "): " << UnixErrnoString(saved_errno)); else SAL_INFO("sal.file", "stat(" << cpPath << "): OK"); done_accessing_file_path(cpPath, state); errno = saved_errno; return result; } int lstat_c(const char* cpPath, struct stat* buf) { #ifdef ANDROID if (strncmp(cpPath, "/assets", sizeof("/assets")-1) == 0 && (cpPath[sizeof("/assets")-1] == '\0' || cpPath[sizeof("/assets")-1] == '/')) return lo_apk_lstat(cpPath, buf); #endif accessFilePathState *state = prepare_to_access_file_path(cpPath); int result = lstat(cpPath, buf); int saved_errno = errno; if (result == -1) SAL_INFO("sal.file", "lstat(" << cpPath << "): " << UnixErrnoString(saved_errno)); else SAL_INFO("sal.file", "lstat(" << cpPath << "): OK"); done_accessing_file_path(cpPath, state); errno = saved_errno; return result; } namespace { template int lstat_(const T& pstrPath, struct stat& buf) { OString fn = toOString(pstrPath); #ifdef MACOSX fn = macxp_resolveAliasAndConvert(fn); #endif return lstat_c(fn.getStr(), &buf); } } int osl::lstat(const OUString& pustrPath, struct stat& buf) { return lstat_(pustrPath, buf); } int osl::lstat(const OString& pstrPath, struct stat& buf) { return lstat_(pstrPath, buf); } int osl::mkdir(const OString& path, mode_t mode) { accessFilePathState *state = prepare_to_access_file_path(path.getStr()); int result = ::mkdir(path.getStr(), mode); int saved_errno = errno; if (result == -1) SAL_INFO("sal.file", "mkdir(" << path << ",0" << std::oct << mode << std::dec << "): " << UnixErrnoString(saved_errno)); else SAL_INFO("sal.file", "mkdir(" << path << ",0" << std::oct << mode << std::dec << "): OK"); done_accessing_file_path(path.getStr(), state); errno = saved_errno; return result; } int open_c(const OString& path, int oflag, int mode) { accessFilePathState *state = prepare_to_access_file_path(path.getStr()); int result = open(path.getStr(), oflag, mode); int saved_errno = errno; if (result == -1) SAL_INFO("sal.file", "open(" << path << ",0" << std::oct << oflag << ",0" << mode << std::dec << "): " << UnixErrnoString(saved_errno)); else SAL_INFO("sal.file", "open(" << path << ",0" << std::oct << oflag << ",0" << mode << std::dec << ") => " << result); #if HAVE_FEATURE_MACOSX_SANDBOX if (isSandboxed && result != -1 && (oflag & O_CREAT) && (oflag & O_EXCL)) { // A new file was created. Check if it is outside the sandbox. // (In that case it must be one the user selected as export or // save destination in a file dialog, otherwise we wouldn't // have been able to create it.) Create and store a security // scoped bookmark for it so that we can access the file in // the future, too. (For the "Recent Files" functionality.) const char *sandbox = [NSHomeDirectory() UTF8String]; if (!(strncmp(sandbox, path.getStr(), strlen(sandbox)) == 0 && path[strlen(sandbox)] == '/')) { auto cpPath = path.getStr(); NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:cpPath]]; NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:nil]; if (data != NULL) { [userDefaults setObject:data forKey:[@"bookmarkFor:" stringByAppendingString:[url absoluteString]]]; } } } #endif done_accessing_file_path(path.getStr(), state); errno = saved_errno; return result; } int utime_c(const char *cpPath, struct utimbuf *times) { accessFilePathState *state = prepare_to_access_file_path(cpPath); int result = utime(cpPath, times); done_accessing_file_path(cpPath, state); return result; } int ftruncate_with_name(int fd, sal_uInt64 uSize, const OString& path) { /* When sandboxed on macOS, ftruncate(), even if it takes an * already open file descriptor which was returned from an open() * call already checked by the sandbox, still requires a security * scope bookmark for the file to be active in case the file is * one that the sandbox doesn't otherwise allow access to. Luckily * LibreOffice usually calls ftruncate() through the helpful C++ * abstraction layer that keeps the pathname around. */ OString fn(path); #ifdef MACOSX fn = macxp_resolveAliasAndConvert(fn); #endif accessFilePathState *state = prepare_to_access_file_path(fn.getStr()); int result = ftruncate(fd, uSize); int saved_errno = errno; if (result < 0) SAL_INFO("sal.file", "ftruncate(" << fd << "," << uSize << "): " << UnixErrnoString(saved_errno)); else SAL_INFO("sal.file", "ftruncate(" << fd << "," << uSize << "): OK"); done_accessing_file_path(fn.getStr(), state); errno = saved_errno; return result; } std::string UnixErrnoString(int nErrno) { // Errnos from and on Linux and // on macOS. switch (nErrno) { case EPERM: return "EPERM"; case ENOENT: return "ENOENT"; case ESRCH: return "ESRCH"; case EINTR: return "EINTR"; case EIO: return "EIO"; case ENXIO: return "ENXIO"; case E2BIG: return "E2BIG"; case ENOEXEC: return "ENOEXEC"; case EBADF: return "EBADF"; case ECHILD: return "ECHILD"; case EAGAIN: return "EAGAIN"; case ENOMEM: return "ENOMEM"; case EACCES: return "EACCES"; case EFAULT: return "EFAULT"; #ifdef ENOTBLK case ENOTBLK: return "ENOTBLK"; #endif case EBUSY: return "EBUSY"; case EEXIST: return "EEXIST"; case EXDEV: return "EXDEV"; case ENODEV: return "ENODEV"; case ENOTDIR: return "ENOTDIR"; case EISDIR: return "EISDIR"; case EINVAL: return "EINVAL"; case ENFILE: return "ENFILE"; case EMFILE: return "EMFILE"; case ENOTTY: return "ENOTTY"; case ETXTBSY: return "ETXTBSY"; case EFBIG: return "EFBIG"; case ENOSPC: return "ENOSPC"; case ESPIPE: return "ESPIPE"; case EROFS: return "EROFS"; case EMLINK: return "EMLINK"; case EPIPE: return "EPIPE"; case EDOM: return "EDOM"; case ERANGE: return "ERANGE"; case EDEADLK: return "EDEADLK"; case ENAMETOOLONG: return "ENAMETOOLONG"; case ENOLCK: return "ENOLCK"; case ENOSYS: return "ENOSYS"; case ENOTEMPTY: return "ENOTEMPTY"; case ELOOP: return "ELOOP"; case ENOMSG: return "ENOMSG"; case EIDRM: return "EIDRM"; #ifdef ECHRNG case ECHRNG: return "ECHRNG"; #endif #ifdef EL2NSYNC case EL2NSYNC: return "EL2NSYNC"; #endif #ifdef EL3HLT case EL3HLT: return "EL3HLT"; #endif #ifdef EL3RST case EL3RST: return "EL3RST"; #endif #ifdef ELNRNG case ELNRNG: return "ELNRNG"; #endif #ifdef EUNATCH case EUNATCH: return "EUNATCH"; #endif #ifdef ENOCSI case ENOCSI: return "ENOCSI"; #endif #ifdef EL2HLT case EL2HLT: return "EL2HLT"; #endif #ifdef EBADE case EBADE: return "EBADE"; #endif #ifdef EBADR case EBADR: return "EBADR"; #endif #ifdef EXFULL case EXFULL: return "EXFULL"; #endif #ifdef ENOANO case ENOANO: return "ENOANO"; #endif #ifdef EBADRQC case EBADRQC: return "EBADRQC"; #endif #ifdef EBADSLT case EBADSLT: return "EBADSLT"; #endif #ifdef EBFONT case EBFONT: return "EBFONT"; #endif case ENOSTR: return "ENOSTR"; case ENODATA: return "ENODATA"; case ETIME: return "ETIME"; case ENOSR: return "ENOSR"; #ifdef ENONET case ENONET: return "ENONET"; #endif #ifdef ENOPKG case ENOPKG: return "ENOPKG"; #endif #ifdef EREMOTE case EREMOTE: return "EREMOTE"; #endif case ENOLINK: return "ENOLINK"; #ifdef EADV case EADV: return "EADV"; #endif #ifdef ESRMNT case ESRMNT: return "ESRMNT"; #endif #ifdef ECOMM case ECOMM: return "ECOMM"; #endif case EPROTO: return "EPROTO"; case EMULTIHOP: return "EMULTIHOP"; #ifdef EDOTDOT case EDOTDOT: return "EDOTDOT"; #endif case EBADMSG: return "EBADMSG"; case EOVERFLOW: return "EOVERFLOW"; #ifdef ENOTUNIQ case ENOTUNIQ: return "ENOTUNIQ"; #endif #ifdef EBADFD case EBADFD: return "EBADFD"; #endif #ifdef EREMCHG case EREMCHG: return "EREMCHG"; #endif #ifdef ELIBACC case ELIBACC: return "ELIBACC"; #endif #ifdef ELIBBAD case ELIBBAD: return "ELIBBAD"; #endif #ifdef ELIBSCN case ELIBSCN: return "ELIBSCN"; #endif #ifdef ELIBMAX case ELIBMAX: return "ELIBMAX"; #endif #ifdef ELIBEXEC case ELIBEXEC: return "ELIBEXEC"; #endif case EILSEQ: return "EILSEQ"; #ifdef ERESTART case ERESTART: return "ERESTART"; #endif #ifdef ESTRPIPE case ESTRPIPE: return "ESTRPIPE"; #endif #ifdef EUSERS case EUSERS: return "EUSERS"; #endif case ENOTSOCK: return "ENOTSOCK"; case EDESTADDRREQ: return "EDESTADDRREQ"; case EMSGSIZE: return "EMSGSIZE"; case EPROTOTYPE: return "EPROTOTYPE"; case ENOPROTOOPT: return "ENOPROTOOPT"; case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT"; #endif #ifdef EOPNOTSUPP case EOPNOTSUPP: return "EOPNOTSUPP"; #endif case EPFNOSUPPORT: return "EPFNOSUPPORT"; case EAFNOSUPPORT: return "EAFNOSUPPORT"; case EADDRINUSE: return "EADDRINUSE"; case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; case ENETDOWN: return "ENETDOWN"; case ENETUNREACH: return "ENETUNREACH"; case ENETRESET: return "ENETRESET"; case ECONNABORTED: return "ECONNABORTED"; case ECONNRESET: return "ECONNRESET"; case ENOBUFS: return "ENOBUFS"; case EISCONN: return "EISCONN"; case ENOTCONN: return "ENOTCONN"; #ifdef ESHUTDOWN case ESHUTDOWN: return "ESHUTDOWN"; #endif #ifdef ETOOMANYREFS case ETOOMANYREFS: return "ETOOMANYREFS"; #endif case ETIMEDOUT: return "ETIMEDOUT"; case ECONNREFUSED: return "ECONNREFUSED"; #ifdef EHOSTDOWN case EHOSTDOWN: return "EHOSTDOWN"; #endif case EHOSTUNREACH: return "EHOSTUNREACH"; case EALREADY: return "EALREADY"; case EINPROGRESS: return "EINPROGRESS"; case ESTALE: return "ESTALE"; #ifdef EUCLEAN case EUCLEAN: return "EUCLEAN"; #endif #ifdef ENOTNAM case ENOTNAM: return "ENOTNAM"; #endif #ifdef ENAVAIL case ENAVAIL: return "ENAVAIL"; #endif #ifdef EISNAM case EISNAM: return "EISNAM"; #endif #ifdef EREMOTEIO case EREMOTEIO: return "EREMOTEIO"; #endif case EDQUOT: return "EDQUOT"; #ifdef ENOMEDIUM case ENOMEDIUM: return "ENOMEDIUM"; #endif #ifdef EMEDIUMTYPE case EMEDIUMTYPE: return "EMEDIUMTYPE"; #endif case ECANCELED: return "ECANCELED"; #ifdef ENOKEY case ENOKEY: return "ENOKEY"; #endif #ifdef EKEYEXPIRED case EKEYEXPIRED: return "EKEYEXPIRED"; #endif #ifdef EKEYREVOKED case EKEYREVOKED: return "EKEYREVOKED"; #endif #ifdef EKEYREJECTED case EKEYREJECTED: return "EKEYREJECTED"; #endif #ifdef EOWNERDEAD case EOWNERDEAD: return "EOWNERDEAD"; #endif #ifdef ENOTRECOVERABLE case ENOTRECOVERABLE: return "ENOTRECOVERABLE"; #endif #ifdef ERFKILL case ERFKILL: return "ERFKILL"; #endif #ifdef EHWPOISON case EHWPOISON: return "EHWPOISON"; #endif #ifdef EPROCLIM case EPROCLIM: return "EPROCLIM"; #endif #ifdef EBADRPC case EBADRPC: return "EBADRPC"; #endif #ifdef ERPCMISMATCH case ERPCMISMATCH: return "ERPCMISMATCH"; #endif #ifdef EPROGUNAVAIL case EPROGUNAVAIL: return "EPROGUNAVAIL"; #endif #ifdef EPROGMISMATCH case EPROGMISMATCH: return "EPROGMISMATCH"; #endif #ifdef EPROCUNAVAIL case EPROCUNAVAIL: return "EPROCUNAVAIL"; #endif #ifdef EFTYPE case EFTYPE: return "EFTYPE"; #endif #ifdef EAUTH case EAUTH: return "EAUTH"; #endif #ifdef ENEEDAUTH case ENEEDAUTH: return "ENEEDAUTH"; #endif #ifdef EPWROFF case EPWROFF: return "EPWROFF"; #endif #ifdef EDEVERR case EDEVERR: return "EDEVERR"; #endif #ifdef EBADEXEC case EBADEXEC: return "EBADEXEC"; #endif #ifdef EBADARCH case EBADARCH: return "EBADARCH"; #endif #ifdef ESHLIBVERS case ESHLIBVERS: return "ESHLIBVERS"; #endif #ifdef EBADMACHO case EBADMACHO: return "EBADMACHO"; #endif #ifdef ENOATTR case ENOATTR: return "ENOATTR"; #endif #ifdef EQFULL case EQFULL: return "EQFULL"; #endif default: char* str = strerror(nErrno); return std::to_string(nErrno) + " (" + std::string(str) + ")"; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */