xref: /haiku/src/system/libroot/posix/unistd/brk.c (revision c302a243e15e640fae0f689e32cdf0c18749afee)
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  */
51 static status_t
get_data_segment_base_area_info(area_info * base_area_info)52 get_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  */
84 static bool
program_break_within_resource_limits(void * program_break,void * base_address)85 program_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  */
115 static status_t
resize_area_to_address(area_info * a_info,void * address)116 resize_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  */
136 static int
brk_internal(void * addr,area_info * base_area_info)137 brk_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  */
187 int
brk(void * addr)188 brk(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  */
217 void *
sbrk(intptr_t increment)218 sbrk(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