1/*
2 * Copyright 2015 Simon South, ssouth@simonsouth.com
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
7/*!
8 * \file brk.c
9 *
10 * This module provides \c brk and \c sbrk, functions that adjust the program
11 * break of their calling process.
12 *
13 * A process' program break is the first memory location past the end of its
14 * data segment. Moving the program break has the effect of increasing or
15 * decreasing the amount of memory allocated to the process for its data and
16 * can be used to achieve a simple form of memory management.
17 *
18 * An ELF executable may contain multiple writable segments that make up a
19 * program's data segment, each of which may be placed by Haiku's \c
20 * runtime_loader in either one or two areas in memory. Additionally, nothing
21 * stops a program from adding onto its data segment directly using \c
22 * create_area.  For these reasons this implementation avoids making
23 * assumptions about the process' data segment except that it is contiguous.
24 * \c brk and \c sbrk will function correctly regardless of the number of
25 * (adjacent) areas used to hold the process' data.
26 *
27 * Note this implementation never creates new areas; its operation is limited
28 * to expanding, contracting or deleting areas as necessary.
29 */
30
31
32#include <errno.h>
33#include <stdint.h>
34#include <sys/resource.h>
35#include <unistd.h>
36
37#include <image.h>
38#include <OS.h>
39
40#include <errno_private.h>
41
42
43/*!
44 * Locates and returns information about the base (lowest in memory) area of
45 * the contiguous set of areas that hold the calling process' data segment.
46 *
47 * \param base_area_info A pointer to an \c area_info structure to receive
48 *                       information about the area.
49 * \return \c B_OK on success; \c B_BAD_VALUE otherwise.
50 */
51static status_t
52get_data_segment_base_area_info(area_info *base_area_info)
53{
54	status_t result = B_ERROR;
55	bool app_image_found = false;
56
57	image_info i_info;
58	int32 image_cookie = 0;
59
60	/* Locate our app image and output information for the area that holds the
61	 * start of its data segment */
62	while (!app_image_found
63		&& get_next_image_info(0, &image_cookie, &i_info) == B_OK) {
64		if (i_info.type == B_APP_IMAGE) {
65			app_image_found = true;
66
67			result = get_area_info(area_for(i_info.data), base_area_info);
68		}
69	}
70
71	return result;
72}
73
74
75/*!
76 * Checks whether a proposed new program break would be valid given the
77 * resource limits imposed on the calling process.
78 *
79 * \param program_break The proposed new program break.
80 * \param base_address The base (start) address of the process' data segment.
81 * \return \c true if the program break would be valid given this process'
82 *         resource limits; \c false otherwise.
83 */
84static bool
85program_break_within_resource_limits(void *program_break, void *base_address)
86{
87	bool result = false;
88
89	struct rlimit rlim;
90
91	if (getrlimit(RLIMIT_AS, &rlim) == 0
92		&& (rlim.rlim_cur == RLIM_INFINITY
93			|| program_break < (void *)rlim.rlim_cur)
94		&& getrlimit(RLIMIT_DATA, &rlim) == 0
95		&& (rlim.rlim_cur == RLIM_INFINITY
96			|| ((rlim_t)((uint8_t *)program_break - (uint8_t *)(base_address))
97				< rlim.rlim_cur)))
98		result = true;
99
100	return result;
101}
102
103
104/*!
105 * Resizes an area, up or down as necessary, so it ends at the specified
106 * address (rounded up to the nearest page boundary).
107 *
108 * \param a_info An \c area_info structure corresponding to the area on which
109 *               to operate.
110 * \param address The new address at which the area should end. This address
111 *                will be rounded up to the nearest page boundary.
112 * \return \c B_OK on success; \c B_BAD_VALUE, \c B_NO_MEMORY or \c B_ERROR
113 *         otherwise (refer to the documentation for \c resize_area).
114 */
115static status_t
116resize_area_to_address(area_info *a_info, void *address)
117{
118	size_t new_size = (uint8_t *)address - (uint8_t *)(a_info->address);
119	size_t new_size_aligned = (new_size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
120
121	return resize_area(a_info->area, new_size_aligned);
122}
123
124
125/*!
126 * An internal method that sets the program break of the calling process to the
127 * specified address.
128 *
129 * \param addr The requested new program break. This address will be rounded up
130 *             as necessary to align the program break on a page boundary.
131 * \param base_area_info An \c area_info structure corresponding to the base
132 *                       (lowest in memory) area of the contiguous set of areas
133 *                       that hold the calling process' data segment.
134 * \return 0 on success; -1 otherwise.
135 */
136static int
137brk_internal(void *addr, area_info *base_area_info)
138{
139	int result = 0;
140
141	void *next_area_address;
142	area_id next_area;
143	area_info next_area_info;
144
145	/* First, recursively process the next (higher) adjacent area, if any */
146	next_area_address = (uint8_t *)base_area_info->address
147		+ base_area_info->size;
148	next_area = area_for(next_area_address);
149	if (next_area != B_ERROR) {
150		result = get_area_info(next_area, &next_area_info) == B_OK ?
151			brk_internal(addr, &next_area_info) : -1;
152	} else {
153		/* This is the highest ("last") area in the data segment, so any
154		 * increase in size needs to occur on this area */
155		if (addr > next_area_address) {
156			result = resize_area_to_address(base_area_info, addr) == B_OK ? 0
157				: -1;
158		}
159	}
160
161	if (result == 0) {
162		if (addr <= base_area_info->address) {
163			/* This area starts at or above the program break the caller has
164			 * requested, so the entire area can be deleted */
165			result = delete_area(base_area_info->area) == B_OK ? 0 : -1;
166		} else if (addr < next_area_address) {
167			/* The requested program break lies within this area, so this area
168			 * must be contracted */
169			result = resize_area_to_address(base_area_info, addr) == B_OK ? 0
170				: -1;
171		}
172	}
173
174	return result;
175}
176
177
178/*!
179 * Sets the calling process' program break to the specified address, if valid
180 * and within the limits of available resources, expanding or contracting the
181 * process' data segment as necessary.
182 *
183 * \param addr The requested new program break.
184 * \return 0 on success; -1 on error (in which case \c errno will be set to
185 *         \c ENOMEM).
186 */
187int
188brk(void *addr)
189{
190	int result = -1;
191
192	area_info base_area_info;
193
194	if (get_data_segment_base_area_info(&base_area_info) == B_OK
195		&& addr > base_area_info.address
196		&& program_break_within_resource_limits(addr, base_area_info.address))
197		result = brk_internal(addr, &base_area_info);
198
199	if (result == -1)
200		__set_errno(ENOMEM);
201
202	return result;
203}
204
205
206/*!
207 * Adjusts the calling process' program break up or down by the requested
208 * number of bytes, if valid and within the limits of available resources,
209 * expanding or contracting the process' data segment as necessary.
210 *
211 * \param increment The amount, positive or negative, in bytes by which to
212 *                  adjust the program break. This value will be rounded up as
213 *                  necessary to align the program break on a page boundary.
214 * \return The previous program break on success; (void *)-1 on error (in which
215 *         case \c errno will be set to \c ENOMEM).
216 */
217void *
218sbrk(intptr_t increment)
219{
220	void *result = NULL;
221
222	area_info base_area_info;
223
224	area_id next_area;
225	area_info next_area_info;
226
227	uint8_t *program_break;
228	uint8_t *new_program_break;
229
230	if (get_data_segment_base_area_info(&base_area_info) == B_OK) {
231		/* Find the current program break, which will be the memory address
232		 * just past the end of the highest ("last") of the areas that hold the
233		 * data segment */
234		next_area = base_area_info.area;
235		do {
236			if (get_area_info(next_area, &next_area_info) == B_OK) {
237				next_area = area_for((uint8_t *)(next_area_info.address)
238					+ next_area_info.size);
239			} else {
240				result = (void *)-1;
241			}
242		} while (next_area != B_ERROR && result == NULL);
243
244		if (result == NULL) {
245			program_break = (uint8_t *)(next_area_info.address)
246				+ next_area_info.size;
247			new_program_break = program_break + increment;
248
249			/* If the requested increment is zero, just return the address of
250			 * the current program break; otherwise, set a new program break
251			 * and return the address of the old one */
252			if (increment == 0
253				|| (program_break_within_resource_limits(new_program_break,
254						base_area_info.address)
255					&& brk_internal(new_program_break, &base_area_info) == 0))
256				result = program_break;
257			else
258				result = (void *)-1;
259		}
260	} else {
261		result = (void *)-1;
262	}
263
264	if (result == (void *)-1)
265		__set_errno(ENOMEM);
266
267	return result;
268}
269