/*
 *Komentarze: Grzegorz Jakacki
 */
asmlinkage unsigned long sys_brk(unsigned long brk)
{
	unsigned long rlim;
	unsigned long newbrk, oldbrk;

	/* 
	 * Jezeli nowa wartosc ,,break'' jest zbyt mala 
	 * (ponizej konca kodu), to sie wycofuj sie.
	 */
	
	if (brk < current->mm->end_code)
		return current->mm->brk;
	
	/* 
	 * Zmiana wartosci ,,break'' jest istotna o tyle, o ile
	 * trzeba zwolnic lub przydzielic pamiec. Poniewaz zwalnia/przydziela
	 * sie wielokrotnosci niepodzielnych stron, nalezy obliczyc
	 * zaokraglenia nowej i starej wartosci ,,break'' do pelnej strony.
	 * Do tego sluzy makro PAGE_ALIGN (zdefiniowane w
	 * include/asm-386/page.h), ktore zaokragla (w gore) adresy 
	 * w ten sposob, ze pokazuja na pierwszy adres na stronie, np:

	newbrk = PAGE_ALIGN(brk);
	oldbrk = PAGE_ALIGN(current->mm->brk);

	/*
	 * Jezeli zaokraglone wartosci sa rowne, to wystarczy tylko zmienic
	 * wartosc break i wrocic (= zmiana w zakresie jednej 
	 * strony --- nie trzba nic zwalniac/przydzielac)
	 */
	if (oldbrk == newbrk)
		return current->mm->brk = brk;

	/*
	 * Teraz wiadomo, ze na pewno trzeba bedzie zwolnic albo przydzielic 
	 * pamiec. Sprzwdzamy, czy nowa wartosc break jest mniejsza od 
	 * aktualnej.
	 */

	if (brk <= current->mm->brk) {
		/*
		 * TAK. Bedziemy zwalniac.
		 * Ustaw nowa wartosc ,,break''
		 */
		current->mm->brk = brk;
		/*
		 * Zwolnij pamiec procesu o dlugosci (oldbrk-newbrk) poczawszy 
		 * od addr (zob. mm/mmap.c:785)
		 */
		do_munmap(newbrk, oldbrk-newbrk);
		return brk;
	}
	/* 
	 * Teraz bedziemy zwiekszac break, wiec trzeba bedzie sprawdzic
	 * dwie rzeczy. Po pierwsze, czy nie przkroczymy limitu pamieci.
	 * Na rlimit bedzie wyznaczony obowiazujacy limit
	 * przylaczonej pamieci operacyjnej dla biezacego procesu w bajtach.
	 */
	rlim = current->rlim[RLIMIT_DATA].rlim_cur;
  
	/*
 	 * Tu jest sztuczka. rlim jest typu unsigned long, 
	 * a current->...rlim_cur jest typu signed long.
	 * RLIM_INFINITY jest najwieksza wartoscia typu long (sigend) int. 
	 * Jezeli rlim >= RLIM_INFINITY, (tak na prawde, to moze byc
	 * co najwyzej rowne) to rlim jest ustawiany na ~0, czyli na
	 * najwieksza wartosc typu unsigned int. To jest
	 * najwieksza liczba, o jakiej mozna sobie pomyslec (przy
	 * adresowaniu na procesorze i386) wiec w praktyce oznacza 
	 * brak ograniczenia (rozmiar przestrzeni adresowej procesora 
	 * wyraza sie wlasnie ta liczba + 1).
	 * /
	if (rlim >= RLIM_INFINITY)
		rlim = ~0;

	/*
	 * Jezeli rozmiar poszerzonego segmentu danych bylby zbyt duzy, 
	 * (w sensie ograniczen nalozonych na proces) wycofuj sie.
	 */
	if (brk - current->mm->end_code > rlim)
		return current->mm->brk;

	/*
	 * Do szczescia pozostalo niewiele --- trzeba sprawdzic, czy
	 * w wirtualnej przestrzni adresowej, ktora chcielibysmy przydzielic,
	 * juz czegos nie ma.
	 * 
	 * find_vma_intersection(proces, pocz, kon) znajduje (i zwraca) pierwszy 
	 * (o najmniejszym adresie) segment biezacego procesu, 
	 * ktory przecina sie z przedzialem [pocz..kon-1]
	 * lub NULL, jezeli takiego segmentu nie ma
	 * (zob. include/linux/mm.h:358)
	 * 
	 * Trzeba stwierdzic, czy we fragmencie przestrzeni adresowej
	 * przydzielanej w wyniku zmiany wartosci break
	 * nie znajduje sie juz jakis segment pamieci procesu.
	 * 
	 * Jezeli funkcja find_vma_intersect 
	 * zwroci nie-NULL, to znaczy, ze cos sie znalazlo i trzeba
	 * sie wycofac.
	 */

	if (find_vma_intersection(current, oldbrk, newbrk+PAGE_SIZE))
		return current->mm->brk;

	/* 
	 * Ostatnia proba: czy wystarczy pamieci. Stwierdzony powyzej fakt, ze
	 * mamy przestrzen adresowa (wirtualna) nie znaczy jeszcze,
	 * ze znajdzie sie autentyczna (fizyczna) pamiec, ktora bedzie 
	 * mozna zamapowac w przestrzen wirtualna procesu.
	 * 
	 * vm_enough_memory(p) sprawdza, czy znajdzie sie p stron pamieci.
	 * >> PAGE_SHIFT --- dzielenie przez rozmiar strony
	 * UWAGA: (newbrk-oldbrk) jest zawsze wielokrotnoscia rozmiaru strony
	 * (bo byly zaokraglane przez PAGE_ALIGN)
	 */

	if (!vm_enough_memory((newbrk-oldbrk) >> PAGE_SHIFT))
		return current->mm->brk;

	/*
	 * No to lu!
	 */

	current->mm->brk = brk;

	/*
	 * Teraz wystarczy przylaczyc do procesu jakas pamiec dlugosci
	 * (newbrk - odlbrk) pod adres oldbrk. Pieczolowite testy
	 * przeprowadzane powyzej gwarantuja, ze operacja sie powiedzie.
	 */
	do_mmap(NULL, oldbrk, newbrk-oldbrk,
		PROT_READ|PROT_WRITE|PROT_EXEC,
		MAP_FIXED|MAP_PRIVATE, 0);
	return brk;
}