| /* |
| * utils - miscellaneous device utilities for cryptsetup |
| * |
| * Copyright (C) 2004, Jana Saout <jana@saout.de> |
| * Copyright (C) 2004-2007, Clemens Fruhwirth <clemens@endorphin.org> |
| * Copyright (C) 2009-2017, Red Hat, Inc. All rights reserved. |
| * Copyright (C) 2009-2017, Milan Broz |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <sys/mman.h> |
| #include <sys/resource.h> |
| |
| #include "internal.h" |
| |
| unsigned crypt_getpagesize(void) |
| { |
| long r = sysconf(_SC_PAGESIZE); |
| return r < 0 ? DEFAULT_MEM_ALIGNMENT : r; |
| } |
| |
| static size_t get_alignment(int fd) |
| { |
| long alignment = DEFAULT_MEM_ALIGNMENT; |
| |
| #ifdef _PC_REC_XFER_ALIGN |
| alignment = fpathconf(fd, _PC_REC_XFER_ALIGN); |
| if (alignment < 0) |
| alignment = DEFAULT_MEM_ALIGNMENT; |
| #endif |
| return (size_t)alignment; |
| } |
| |
| ssize_t read_buffer(int fd, void *buf, size_t count) |
| { |
| size_t read_size = 0; |
| ssize_t r; |
| |
| if (fd < 0 || !buf) |
| return -EINVAL; |
| |
| do { |
| r = read(fd, buf, count - read_size); |
| if (r == -1 && errno != EINTR) |
| return r; |
| if (r == 0) |
| return (ssize_t)read_size; |
| if (r > 0) { |
| read_size += (size_t)r; |
| buf = (uint8_t*)buf + r; |
| } |
| } while (read_size != count); |
| |
| return (ssize_t)count; |
| } |
| |
| ssize_t write_buffer(int fd, const void *buf, size_t count) |
| { |
| size_t write_size = 0; |
| ssize_t w; |
| |
| if (fd < 0 || !buf || !count) |
| return -EINVAL; |
| |
| do { |
| w = write(fd, buf, count - write_size); |
| if (w < 0 && errno != EINTR) |
| return w; |
| if (w == 0) |
| return (ssize_t)write_size; |
| if (w > 0) { |
| write_size += (size_t) w; |
| buf = (const uint8_t*)buf + w; |
| } |
| } while (write_size != count); |
| |
| return (ssize_t)write_size; |
| } |
| |
| ssize_t write_blockwise(int fd, int bsize, void *orig_buf, size_t count) |
| { |
| void *hangover_buf = NULL, *buf = NULL; |
| int r; |
| size_t alignment, hangover, solid; |
| ssize_t ret = -1; |
| |
| if (fd == -1 || !orig_buf || bsize <= 0) |
| return -1; |
| |
| hangover = count % bsize; |
| solid = count - hangover; |
| alignment = get_alignment(fd); |
| |
| if ((size_t)orig_buf & (alignment - 1)) { |
| if (posix_memalign(&buf, alignment, count)) |
| return -1; |
| memcpy(buf, orig_buf, count); |
| } else |
| buf = orig_buf; |
| |
| if (solid) { |
| r = write_buffer(fd, buf, solid); |
| if (r < 0 || r != (ssize_t)solid) |
| goto out; |
| } |
| |
| if (hangover) { |
| if (posix_memalign(&hangover_buf, alignment, bsize)) |
| goto out; |
| |
| r = read_buffer(fd, hangover_buf, bsize); |
| if (r < 0 || r < (ssize_t)hangover) |
| goto out; |
| |
| if (r < bsize) |
| bsize = r; |
| |
| if (lseek(fd, -bsize, SEEK_CUR) < 0) |
| goto out; |
| |
| memcpy(hangover_buf, (char*)buf + solid, hangover); |
| |
| r = write_buffer(fd, hangover_buf, bsize); |
| if (r < 0 || r < (ssize_t)hangover) |
| goto out; |
| } |
| ret = count; |
| out: |
| free(hangover_buf); |
| if (buf != orig_buf) |
| free(buf); |
| return ret; |
| } |
| |
| ssize_t read_blockwise(int fd, int bsize, void *orig_buf, size_t count) |
| { |
| void *hangover_buf = NULL, *buf = NULL; |
| int r; |
| size_t alignment, hangover, solid; |
| ssize_t ret = -1; |
| |
| if (fd == -1 || !orig_buf || bsize <= 0) |
| return -1; |
| |
| hangover = count % bsize; |
| solid = count - hangover; |
| alignment = get_alignment(fd); |
| |
| if ((size_t)orig_buf & (alignment - 1)) { |
| if (posix_memalign(&buf, alignment, count)) |
| return -1; |
| } else |
| buf = orig_buf; |
| |
| r = read_buffer(fd, buf, solid); |
| if (r < 0 || r != (ssize_t)solid) |
| goto out; |
| |
| if (hangover) { |
| if (posix_memalign(&hangover_buf, alignment, bsize)) |
| goto out; |
| r = read_buffer(fd, hangover_buf, bsize); |
| if (r < 0 || r < (ssize_t)hangover) |
| goto out; |
| |
| memcpy((char *)buf + solid, hangover_buf, hangover); |
| } |
| ret = count; |
| out: |
| free(hangover_buf); |
| if (buf != orig_buf) { |
| memcpy(orig_buf, buf, count); |
| free(buf); |
| } |
| return ret; |
| } |
| |
| /* |
| * Combines llseek with blockwise write. write_blockwise can already deal with short writes |
| * but we also need a function to deal with short writes at the start. But this information |
| * is implicitly included in the read/write offset, which can not be set to non-aligned |
| * boundaries. Hence, we combine llseek with write. |
| */ |
| ssize_t write_lseek_blockwise(int fd, int bsize, void *buf, size_t count, off_t offset) |
| { |
| void *frontPadBuf = NULL; |
| int r, frontHang; |
| size_t innerCount = 0; |
| ssize_t ret = -1; |
| |
| if (fd == -1 || !buf || bsize <= 0) |
| return -1; |
| |
| if (offset < 0) |
| offset = lseek(fd, offset, SEEK_END); |
| |
| if (offset < 0) |
| return -1; |
| |
| frontHang = offset % bsize; |
| |
| if (lseek(fd, offset - frontHang, SEEK_SET) < 0) |
| return -1; |
| |
| if (frontHang) { |
| if (posix_memalign(&frontPadBuf, get_alignment(fd), bsize)) |
| return -1; |
| |
| r = read_buffer(fd, frontPadBuf, bsize); |
| if (r < 0 || r != bsize) |
| goto out; |
| |
| innerCount = bsize - frontHang; |
| if (innerCount > count) |
| innerCount = count; |
| |
| memcpy(frontPadBuf + frontHang, buf, innerCount); |
| |
| if (lseek(fd, offset - frontHang, SEEK_SET) < 0) |
| goto out; |
| |
| r = write_buffer(fd, frontPadBuf, bsize); |
| if (r < 0 || r != bsize) |
| goto out; |
| |
| buf = (char*)buf + innerCount; |
| count -= innerCount; |
| } |
| |
| ret = count ? write_blockwise(fd, bsize, buf, count) : 0; |
| if (ret >= 0) |
| ret += innerCount; |
| out: |
| free(frontPadBuf); |
| return ret; |
| } |
| |
| ssize_t read_lseek_blockwise(int fd, int bsize, void *buf, size_t count, off_t offset) |
| { |
| void *frontPadBuf = NULL; |
| int r, frontHang; |
| size_t innerCount = 0; |
| ssize_t ret = -1; |
| |
| if (fd == -1 || !buf || bsize <= 0) |
| return -1; |
| |
| if (offset < 0) |
| offset = lseek(fd, offset, SEEK_END); |
| |
| if (offset < 0) |
| return -1; |
| |
| frontHang = offset % bsize; |
| |
| if (lseek(fd, offset - frontHang, SEEK_SET) < 0) |
| return -1; |
| |
| if (frontHang) { |
| if (posix_memalign(&frontPadBuf, get_alignment(fd), bsize)) |
| return -1; |
| |
| r = read_buffer(fd, frontPadBuf, bsize); |
| if (r < 0 || r != bsize) |
| goto out; |
| |
| innerCount = bsize - frontHang; |
| if (innerCount > count) |
| innerCount = count; |
| |
| memcpy(buf, frontPadBuf + frontHang, innerCount); |
| |
| buf = (char*)buf + innerCount; |
| count -= innerCount; |
| } |
| |
| ret = read_blockwise(fd, bsize, buf, count); |
| if (ret >= 0) |
| ret += innerCount; |
| out: |
| free(frontPadBuf); |
| return ret; |
| } |
| |
| /* MEMLOCK */ |
| #define DEFAULT_PROCESS_PRIORITY -18 |
| |
| static int _priority; |
| static int _memlock_count = 0; |
| |
| // return 1 if memory is locked |
| int crypt_memlock_inc(struct crypt_device *ctx) |
| { |
| if (!_memlock_count++) { |
| log_dbg("Locking memory."); |
| if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) { |
| log_dbg("Cannot lock memory with mlockall."); |
| _memlock_count--; |
| return 0; |
| } |
| errno = 0; |
| if (((_priority = getpriority(PRIO_PROCESS, 0)) == -1) && errno) |
| log_err(ctx, _("Cannot get process priority.\n")); |
| else |
| if (setpriority(PRIO_PROCESS, 0, DEFAULT_PROCESS_PRIORITY)) |
| log_dbg("setpriority %d failed: %s", |
| DEFAULT_PROCESS_PRIORITY, strerror(errno)); |
| } |
| return _memlock_count ? 1 : 0; |
| } |
| |
| int crypt_memlock_dec(struct crypt_device *ctx) |
| { |
| if (_memlock_count && (!--_memlock_count)) { |
| log_dbg("Unlocking memory."); |
| if (munlockall() == -1) |
| log_err(ctx, _("Cannot unlock memory.\n")); |
| if (setpriority(PRIO_PROCESS, 0, _priority)) |
| log_dbg("setpriority %d failed: %s", _priority, strerror(errno)); |
| } |
| return _memlock_count ? 1 : 0; |
| } |