Commit 5079f36d authored by Linus Torvalds's avatar Linus Torvalds

Import 1.1.33

parent 8151e895
VERSION = 1 VERSION = 1
PATCHLEVEL = 1 PATCHLEVEL = 1
SUBLEVEL = 32 SUBLEVEL = 33
all: Version zImage all: Version zImage
......
...@@ -1772,6 +1772,9 @@ scd_open(struct inode *inode, ...@@ -1772,6 +1772,9 @@ scd_open(struct inode *inode,
int num_spin_ups; int num_spin_ups;
if (filp->f_mode & 2)
return -EACCES;
if (!sony_spun_up) if (!sony_spun_up)
{ {
num_spin_ups = 0; num_spin_ups = 0;
......
...@@ -218,12 +218,12 @@ static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect, ...@@ -218,12 +218,12 @@ static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
} }
static void hd_request (void); static void hd_request (void);
unsigned int identified [MAX_HD] = {0,}; /* 1 = drive ID already displayed */ static unsigned int identified [MAX_HD] = {0,}; /* 1 = drive ID already displayed */
unsigned int unmask_intr [MAX_HD] = {0,}; /* 1 = unmask IRQs during I/O */ static unsigned int unmask_intr [MAX_HD] = {0,}; /* 1 = unmask IRQs during I/O */
unsigned int max_mult [MAX_HD] = {0,}; /* max sectors for MultMode */ static unsigned int max_mult [MAX_HD] = {0,}; /* max sectors for MultMode */
unsigned int mult_req [MAX_HD] = {0,}; /* requested MultMode count */ static unsigned int mult_req [MAX_HD] = {0,}; /* requested MultMode count */
unsigned int mult_count [MAX_HD] = {0,}; /* currently enabled MultMode count */ static unsigned int mult_count [MAX_HD] = {0,}; /* currently enabled MultMode count */
struct request WCURRENT; static struct request WCURRENT;
static void rawstring (char *prefix, char *s, int n) static void rawstring (char *prefix, char *s, int n)
{ {
......
...@@ -1029,6 +1029,9 @@ mcd_open(struct inode *ip, struct file *fp) ...@@ -1029,6 +1029,9 @@ mcd_open(struct inode *ip, struct file *fp)
if (mcdPresent == 0) if (mcdPresent == 0)
return -ENXIO; /* no hardware */ return -ENXIO; /* no hardware */
if (fp->f_mode & 2) /* write access? */
return -EACCES;
if (!mcd_open_count && mcd_state == MCD_S_IDLE) { if (!mcd_open_count && mcd_state == MCD_S_IDLE) {
......
...@@ -3211,6 +3211,9 @@ static int sbpcd_open(struct inode *ip, struct file *fp) ...@@ -3211,6 +3211,9 @@ static int sbpcd_open(struct inode *ip, struct file *fp)
if (ndrives==0) return (-ENXIO); /* no hardware */ if (ndrives==0) return (-ENXIO); /* no hardware */
if (fp->f_mode & 2)
return -EACCES;
i = MINOR(ip->i_rdev); i = MINOR(ip->i_rdev);
if ( (i<0) || (i>=NR_SBPCD) ) if ( (i<0) || (i>=NR_SBPCD) )
{ {
......
...@@ -35,6 +35,9 @@ ...@@ -35,6 +35,9 @@
* Code to check for different video-cards mostly by Galen Hunt, * Code to check for different video-cards mostly by Galen Hunt,
* <g-hunt@ee.utah.edu> * <g-hunt@ee.utah.edu>
* *
* Code for xterm like mouse click reporting by Peter Orbaek 20-Jul-94
* <poe@daimi.aau.dk>
*
*/ */
#define CAN_LOAD_EGA_FONTS /* undefine if the user must not do this */ #define CAN_LOAD_EGA_FONTS /* undefine if the user must not do this */
...@@ -80,7 +83,7 @@ static struct termios *console_termios_locked[NR_CONSOLES]; ...@@ -80,7 +83,7 @@ static struct termios *console_termios_locked[NR_CONSOLES];
#include <linux/ctype.h> #include <linux/ctype.h>
/* Routines for selection control. */ /* Routines for selection control. */
int set_selection(const int arg); int set_selection(const int arg, struct tty_struct *tty);
int paste_selection(struct tty_struct *tty); int paste_selection(struct tty_struct *tty);
static void clear_selection(void); static void clear_selection(void);
static void highlight_pointer(const int currcons, const int where); static void highlight_pointer(const int currcons, const int where);
...@@ -153,6 +156,7 @@ static struct { ...@@ -153,6 +156,7 @@ static struct {
/* misc */ /* misc */
unsigned long vc_ques : 1; unsigned long vc_ques : 1;
unsigned long vc_need_wrap : 1; unsigned long vc_need_wrap : 1;
unsigned long vc_report_mouse : 1;
unsigned long vc_tab_stop[5]; /* Tab stops. 160 columns. */ unsigned long vc_tab_stop[5]; /* Tab stops. 160 columns. */
unsigned char * vc_translate; unsigned char * vc_translate;
unsigned char * vc_G0_charset; unsigned char * vc_G0_charset;
...@@ -196,6 +200,7 @@ static int console_blanked = 0; ...@@ -196,6 +200,7 @@ static int console_blanked = 0;
#define deccm (vc_cons[currcons].vc_deccm) #define deccm (vc_cons[currcons].vc_deccm)
#define decim (vc_cons[currcons].vc_decim) #define decim (vc_cons[currcons].vc_decim)
#define need_wrap (vc_cons[currcons].vc_need_wrap) #define need_wrap (vc_cons[currcons].vc_need_wrap)
#define report_mouse (vc_cons[currcons].vc_report_mouse)
#define color (vc_cons[currcons].vc_color) #define color (vc_cons[currcons].vc_color)
#define s_color (vc_cons[currcons].vc_s_color) #define s_color (vc_cons[currcons].vc_s_color)
#define def_color (vc_cons[currcons].vc_def_color) #define def_color (vc_cons[currcons].vc_def_color)
...@@ -775,6 +780,16 @@ static void cursor_report(int currcons, struct tty_struct * tty) ...@@ -775,6 +780,16 @@ static void cursor_report(int currcons, struct tty_struct * tty)
respond_string(buf, tty); respond_string(buf, tty);
} }
static void mouse_report(int currcons, struct tty_struct * tty,
int butt, int mrx, int mry)
{
char buf[8];
sprintf(buf, "\033[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
(char)('!' + mry));
respond_string(buf, tty);
}
static inline void status_report(int currcons, struct tty_struct * tty) static inline void status_report(int currcons, struct tty_struct * tty)
{ {
respond_string("\033[0n", tty); /* Terminal ok */ respond_string("\033[0n", tty); /* Terminal ok */
...@@ -832,6 +847,9 @@ static void set_mode(int currcons, int on_off) ...@@ -832,6 +847,9 @@ static void set_mode(int currcons, int on_off)
else else
clr_kbd(decarm); clr_kbd(decarm);
break; break;
case 9:
report_mouse = on_off;
break;
case 25: /* Cursor on/off */ case 25: /* Cursor on/off */
deccm = on_off; deccm = on_off;
set_cursor(currcons); set_cursor(currcons);
...@@ -1001,6 +1019,7 @@ static void reset_terminal(int currcons, int do_clear) ...@@ -1001,6 +1019,7 @@ static void reset_terminal(int currcons, int do_clear)
G1_charset = GRAF_TRANS; G1_charset = GRAF_TRANS;
charset = 0; charset = 0;
need_wrap = 0; need_wrap = 0;
report_mouse = 0;
disp_ctrl = 0; disp_ctrl = 0;
toggle_meta = 0; toggle_meta = 0;
...@@ -1838,8 +1857,16 @@ static inline short limit(const int v, const int l, const int u) ...@@ -1838,8 +1857,16 @@ static inline short limit(const int v, const int l, const int u)
return (v < l) ? l : ((v > u) ? u : v); return (v < l) ? l : ((v > u) ? u : v);
} }
/* invoked via ioctl(TIOCLINUX) */
int mouse_reporting_p(void)
{
int currcons = fg_console;
return ((report_mouse) ? 0 : -EINVAL);
}
/* set the current selection. Invoked by ioctl(). */ /* set the current selection. Invoked by ioctl(). */
int set_selection(const int arg) int set_selection(const int arg, struct tty_struct *tty)
{ {
unsigned short *args, xs, ys, xe, ye; unsigned short *args, xs, ys, xe, ye;
int currcons = fg_console; int currcons = fg_console;
...@@ -1863,6 +1890,11 @@ int set_selection(const int arg) ...@@ -1863,6 +1890,11 @@ int set_selection(const int arg)
ps = ys * video_size_row + (xs << 1); ps = ys * video_size_row + (xs << 1);
pe = ye * video_size_row + (xe << 1); pe = ye * video_size_row + (xe << 1);
if (report_mouse && (sel_mode & 16)) {
mouse_report(currcons, tty, sel_mode & 15, xs, ys);
return 0;
}
if (ps > pe) /* make sel_start <= sel_end */ if (ps > pe) /* make sel_start <= sel_end */
{ {
int tmp = ps; int tmp = ps;
......
...@@ -66,9 +66,10 @@ ...@@ -66,9 +66,10 @@
#undef TTY_DEBUG_HANGUP #undef TTY_DEBUG_HANGUP
#ifdef CONFIG_SELECTION #ifdef CONFIG_SELECTION
extern int set_selection(const int arg); extern int set_selection(const int arg, struct tty_struct *tty);
extern int paste_selection(struct tty_struct *tty); extern int paste_selection(struct tty_struct *tty);
extern int sel_loadlut(const int arg); extern int sel_loadlut(const int arg);
extern int mouse_reporting_p(void);
extern int shift_state; extern int shift_state;
#endif /* CONFIG_SELECTION */ #endif /* CONFIG_SELECTION */
extern int do_screendump(int arg); extern int do_screendump(int arg);
...@@ -1381,7 +1382,7 @@ static int tty_ioctl(struct inode * inode, struct file * file, ...@@ -1381,7 +1382,7 @@ static int tty_ioctl(struct inode * inode, struct file * file,
return do_get_ps_info(arg); return do_get_ps_info(arg);
#ifdef CONFIG_SELECTION #ifdef CONFIG_SELECTION
case 2: case 2:
return set_selection(arg); return set_selection(arg, tty);
case 3: case 3:
return paste_selection(tty); return paste_selection(tty);
case 4: case 4:
...@@ -1392,6 +1393,8 @@ static int tty_ioctl(struct inode * inode, struct file * file, ...@@ -1392,6 +1393,8 @@ static int tty_ioctl(struct inode * inode, struct file * file,
case 6: case 6:
put_fs_byte(shift_state,arg); put_fs_byte(shift_state,arg);
return 0; return 0;
case 7:
return mouse_reporting_p();
#endif /* CONFIG_SELECTION */ #endif /* CONFIG_SELECTION */
default: default:
return -EINVAL; return -EINVAL;
......
...@@ -268,6 +268,9 @@ static int sr_open(struct inode * inode, struct file * filp) ...@@ -268,6 +268,9 @@ static int sr_open(struct inode * inode, struct file * filp)
if(MINOR(inode->i_rdev) >= NR_SR || if(MINOR(inode->i_rdev) >= NR_SR ||
!scsi_CDs[MINOR(inode->i_rdev)].device) return -ENXIO; /* No such device */ !scsi_CDs[MINOR(inode->i_rdev)].device) return -ENXIO; /* No such device */
if (filp->f_mode & 2)
return -EACCES;
check_disk_change(inode->i_rdev); check_disk_change(inode->i_rdev);
if(!scsi_CDs[MINOR(inode->i_rdev)].device->access_count++) if(!scsi_CDs[MINOR(inode->i_rdev)].device->access_count++)
......
...@@ -537,6 +537,8 @@ void flush_old_exec(struct linux_binprm * bprm) ...@@ -537,6 +537,8 @@ void flush_old_exec(struct linux_binprm * bprm)
mpnt1 = mpnt->vm_next; mpnt1 = mpnt->vm_next;
if (mpnt->vm_ops && mpnt->vm_ops->close) if (mpnt->vm_ops && mpnt->vm_ops->close)
mpnt->vm_ops->close(mpnt); mpnt->vm_ops->close(mpnt);
if (mpnt->vm_inode)
iput(mpnt->vm_inode);
kfree(mpnt); kfree(mpnt);
mpnt = mpnt1; mpnt = mpnt1;
} }
......
...@@ -23,63 +23,11 @@ ...@@ -23,63 +23,11 @@
#include <asm/segment.h> #include <asm/segment.h>
#include <asm/system.h> #include <asm/system.h>
/*
* Fill in the supplied page for mmap
*/
static unsigned long nfs_file_mmap_nopage(struct vm_area_struct * area, static unsigned long nfs_file_mmap_nopage(struct vm_area_struct * area,
unsigned long address, unsigned long page, int error_code); unsigned long address, unsigned long page, int error_code)
extern void file_mmap_free(struct vm_area_struct * area);
extern int file_mmap_share(struct vm_area_struct * from, struct vm_area_struct * to,
unsigned long address);
struct vm_operations_struct nfs_file_mmap = {
NULL, /* open */
file_mmap_free, /* close */
nfs_file_mmap_nopage, /* nopage */
NULL, /* wppage */
file_mmap_share, /* share */
NULL, /* unmap */
};
/* This is used for a general mmap of a nfs file */
int nfs_mmap(struct inode * inode, struct file * file,
unsigned long addr, size_t len, int prot, unsigned long off)
{
struct vm_area_struct * mpnt;
if (prot & PAGE_RW) /* only PAGE_COW or read-only supported now */
return -EINVAL;
if (off & (inode->i_sb->s_blocksize - 1))
return -EINVAL;
if (!inode->i_sb || !S_ISREG(inode->i_mode))
return -EACCES;
if (!IS_RDONLY(inode)) {
inode->i_atime = CURRENT_TIME;
inode->i_dirt = 1;
}
mpnt = (struct vm_area_struct * ) kmalloc(sizeof(struct vm_area_struct), GFP_KERNEL);
if (!mpnt)
return -ENOMEM;
unmap_page_range(addr, len);
mpnt->vm_task = current;
mpnt->vm_start = addr;
mpnt->vm_end = addr + len;
mpnt->vm_page_prot = prot;
mpnt->vm_flags = 0;
mpnt->vm_share = NULL;
mpnt->vm_inode = inode;
inode->i_count++;
mpnt->vm_offset = off;
mpnt->vm_ops = &nfs_file_mmap;
insert_vm_struct(current, mpnt);
merge_segments(current->mm->mmap, NULL, NULL);
return 0;
}
static unsigned long nfs_file_mmap_nopage(struct vm_area_struct * area, unsigned long address,
unsigned long page, int error_code)
{ {
struct inode * inode = area->vm_inode; struct inode * inode = area->vm_inode;
unsigned int clear; unsigned int clear;
...@@ -126,3 +74,49 @@ static unsigned long nfs_file_mmap_nopage(struct vm_area_struct * area, unsigned ...@@ -126,3 +74,49 @@ static unsigned long nfs_file_mmap_nopage(struct vm_area_struct * area, unsigned
} }
return page; return page;
} }
struct vm_operations_struct nfs_file_mmap = {
NULL, /* open */
NULL, /* close */
nfs_file_mmap_nopage, /* nopage */
NULL, /* wppage */
NULL, /* share */
NULL, /* unmap */
};
/* This is used for a general mmap of a nfs file */
int nfs_mmap(struct inode * inode, struct file * file,
unsigned long addr, size_t len, int prot, unsigned long off)
{
struct vm_area_struct * mpnt;
if (prot & PAGE_RW) /* only PAGE_COW or read-only supported now */
return -EINVAL;
if (off & (inode->i_sb->s_blocksize - 1))
return -EINVAL;
if (!inode->i_sb || !S_ISREG(inode->i_mode))
return -EACCES;
if (!IS_RDONLY(inode)) {
inode->i_atime = CURRENT_TIME;
inode->i_dirt = 1;
}
mpnt = (struct vm_area_struct * ) kmalloc(sizeof(struct vm_area_struct), GFP_KERNEL);
if (!mpnt)
return -ENOMEM;
unmap_page_range(addr, len);
mpnt->vm_task = current;
mpnt->vm_start = addr;
mpnt->vm_end = addr + len;
mpnt->vm_page_prot = prot;
mpnt->vm_flags = 0;
mpnt->vm_share = NULL;
mpnt->vm_inode = inode;
inode->i_count++;
mpnt->vm_offset = off;
mpnt->vm_ops = &nfs_file_mmap;
insert_vm_struct(current, mpnt);
merge_segments(current->mm->mmap, NULL, NULL);
return 0;
}
...@@ -61,6 +61,8 @@ struct vm_operations_struct { ...@@ -61,6 +61,8 @@ struct vm_operations_struct {
unsigned long page); unsigned long page);
int (*share)(struct vm_area_struct * from, struct vm_area_struct * to, unsigned long address); int (*share)(struct vm_area_struct * from, struct vm_area_struct * to, unsigned long address);
int (*unmap)(struct vm_area_struct *area, unsigned long, size_t); int (*unmap)(struct vm_area_struct *area, unsigned long, size_t);
void (*swapout)(struct vm_area_struct *, unsigned long *);
unsigned long (*swapin)(struct vm_area_struct *, unsigned long);
}; };
extern unsigned long __bad_page(void); extern unsigned long __bad_page(void);
...@@ -167,7 +169,7 @@ extern int vread(char *buf, char *addr, int count); ...@@ -167,7 +169,7 @@ extern int vread(char *buf, char *addr, int count);
extern void swap_free(unsigned long page_nr); extern void swap_free(unsigned long page_nr);
extern unsigned long swap_duplicate(unsigned long page_nr); extern unsigned long swap_duplicate(unsigned long page_nr);
extern void swap_in(unsigned long *table_ptr); extern unsigned long swap_in(unsigned long entry);
extern void si_swapinfo(struct sysinfo * val); extern void si_swapinfo(struct sysinfo * val);
extern void rw_swap_page(int rw, unsigned long nr, char * buf); extern void rw_swap_page(int rw, unsigned long nr, char * buf);
...@@ -223,4 +225,12 @@ extern unsigned short * mem_map; ...@@ -223,4 +225,12 @@ extern unsigned short * mem_map;
#define SHM_SWP_TYPE 0x41 #define SHM_SWP_TYPE 0x41
extern void shm_no_page (ulong *); extern void shm_no_page (ulong *);
/* swap cache stuff (in swap.c) */
extern unsigned long * swap_cache;
extern inline unsigned long in_swap_cache (unsigned long addr)
{
return swap_cache[addr >> PAGE_SHIFT];
}
#endif #endif
...@@ -143,6 +143,7 @@ ...@@ -143,6 +143,7 @@
#define __NR_bdflush 134 #define __NR_bdflush 134
#define __NR_sysfs 135 #define __NR_sysfs 135
#define __NR_personality 136 #define __NR_personality 136
#define __NR_afs_syscall 137 /* Syscall for Andrew File System */
extern int errno; extern int errno;
......
...@@ -19,6 +19,7 @@ static int findkey (key_t key); ...@@ -19,6 +19,7 @@ static int findkey (key_t key);
static int newseg (key_t key, int shmflg, int size); static int newseg (key_t key, int shmflg, int size);
static int shm_map (struct shm_desc *shmd, int remap); static int shm_map (struct shm_desc *shmd, int remap);
static void killseg (int id); static void killseg (int id);
static unsigned long shm_swap_in(struct vm_area_struct *, unsigned long);
static int shm_tot = 0; /* total number of shared memory pages */ static int shm_tot = 0; /* total number of shared memory pages */
static int shm_rss = 0; /* number of shared memory pages that are in memory */ static int shm_rss = 0; /* number of shared memory pages that are in memory */
...@@ -375,13 +376,24 @@ static int shm_map (struct shm_desc *shmd, int remap) ...@@ -375,13 +376,24 @@ static int shm_map (struct shm_desc *shmd, int remap)
return 0; return 0;
} }
static struct vm_operations_struct shm_vm_ops = {
NULL, /* open */
NULL, /* close */
NULL, /* nopage (done with swapin) */
NULL, /* wppage */
NULL, /* share */
NULL, /* unmap */
NULL, /* swapout (hardcoded right now) */
shm_swap_in /* swapin */
};
/* /*
* This is really minimal support to make the shared mem stuff * This is really minimal support to make the shared mem stuff
* ve known by the general VM manager. It should add the vm_ops * ve known by the general VM manager. It should add the vm_ops
* field so that 'munmap()' and friends work correctly on shared * field so that 'munmap()' and friends work correctly on shared
* memory areas.. * memory areas..
*/ */
static int add_vm_area(unsigned long addr, unsigned long len) static int add_vm_area(unsigned long addr, unsigned long len, int readonly)
{ {
struct vm_area_struct * vma; struct vm_area_struct * vma;
...@@ -392,12 +404,15 @@ static int add_vm_area(unsigned long addr, unsigned long len) ...@@ -392,12 +404,15 @@ static int add_vm_area(unsigned long addr, unsigned long len)
vma->vm_task = current; vma->vm_task = current;
vma->vm_start = addr; vma->vm_start = addr;
vma->vm_end = addr + len; vma->vm_end = addr + len;
vma->vm_page_prot = PAGE_SHARED; if (readonly)
vma->vm_page_prot = PAGE_READONLY;
else
vma->vm_page_prot = PAGE_SHARED;
vma->vm_flags = VM_SHM; vma->vm_flags = VM_SHM;
vma->vm_share = NULL; vma->vm_share = NULL;
vma->vm_inode = NULL; vma->vm_inode = NULL;
vma->vm_offset = 0; vma->vm_offset = 0;
vma->vm_ops = NULL; vma->vm_ops = &shm_vm_ops;
insert_vm_struct(current, vma); insert_vm_struct(current, vma);
merge_segments(current->mm->mmap, NULL, NULL); merge_segments(current->mm->mmap, NULL, NULL);
return 0; return 0;
...@@ -476,7 +491,7 @@ int sys_shmat (int shmid, char *shmaddr, int shmflg, ulong *raddr) ...@@ -476,7 +491,7 @@ int sys_shmat (int shmid, char *shmaddr, int shmflg, ulong *raddr)
shmd->end = addr + shp->shm_npages * PAGE_SIZE; shmd->end = addr + shp->shm_npages * PAGE_SIZE;
shmd->task = current; shmd->task = current;
if ((err = add_vm_area(shmd->start, shmd->end - shmd->start))) { if ((err = add_vm_area(shmd->start, shmd->end - shmd->start, shmflg & SHM_RDONLY))) {
kfree(shmd); kfree(shmd);
return err; return err;
} }
...@@ -614,36 +629,34 @@ int shm_fork (struct task_struct *p1, struct task_struct *p2) ...@@ -614,36 +629,34 @@ int shm_fork (struct task_struct *p1, struct task_struct *p2)
} }
/* /*
* page not present ... go through shm_pages .. called from swap_in() * page not present ... go through shm_pages
*/ */
void shm_no_page (unsigned long *ptent) static unsigned long shm_swap_in(struct vm_area_struct * vma, unsigned long code)
{ {
unsigned long page; unsigned long page;
unsigned long code = *ptent;
struct shmid_ds *shp; struct shmid_ds *shp;
unsigned int id, idx; unsigned int id, idx;
id = (code >> SHM_ID_SHIFT) & SHM_ID_MASK; id = (code >> SHM_ID_SHIFT) & SHM_ID_MASK;
if (id > max_shmid) { if (id > max_shmid) {
printk ("shm_no_page: id=%d too big. proc mem corruptedn", id); printk ("shm_no_page: id=%d too big. proc mem corruptedn", id);
return; return BAD_PAGE | PAGE_SHARED;
} }
shp = shm_segs[id]; shp = shm_segs[id];
if (shp == IPC_UNUSED || shp == IPC_NOID) { if (shp == IPC_UNUSED || shp == IPC_NOID) {
printk ("shm_no_page: id=%d invalid. Race.\n", id); printk ("shm_no_page: id=%d invalid. Race.\n", id);
return; return BAD_PAGE | PAGE_SHARED;
} }
idx = (code >> SHM_IDX_SHIFT) & SHM_IDX_MASK; idx = (code >> SHM_IDX_SHIFT) & SHM_IDX_MASK;
if (idx >= shp->shm_npages) { if (idx >= shp->shm_npages) {
printk ("shm_no_page : too large page index. id=%d\n", id); printk ("shm_no_page : too large page index. id=%d\n", id);
return; return BAD_PAGE | PAGE_SHARED;
} }
if (!(shp->shm_pages[idx] & PAGE_PRESENT)) { if (!(shp->shm_pages[idx] & PAGE_PRESENT)) {
if(!(page = get_free_page(GFP_KERNEL))) { if(!(page = get_free_page(GFP_KERNEL))) {
oom(current); oom(current);
*ptent = BAD_PAGE | PAGE_ACCESSED | 7; return BAD_PAGE | PAGE_SHARED;
return;
} }
if (shp->shm_pages[idx] & PAGE_PRESENT) { if (shp->shm_pages[idx] & PAGE_PRESENT) {
free_page (page); free_page (page);
...@@ -667,10 +680,9 @@ void shm_no_page (unsigned long *ptent) ...@@ -667,10 +680,9 @@ void shm_no_page (unsigned long *ptent)
current->mm->min_flt++; current->mm->min_flt++;
page = shp->shm_pages[idx]; page = shp->shm_pages[idx];
if (code & SHM_READ_ONLY) /* write-protect */ if (code & SHM_READ_ONLY) /* write-protect */
page &= ~2; page &= ~PAGE_RW;
mem_map[MAP_NR(page)]++; mem_map[MAP_NR(page)]++;
*ptent = page; return page;
return;
} }
/* /*
......
...@@ -365,6 +365,8 @@ static void exit_mm(void) ...@@ -365,6 +365,8 @@ static void exit_mm(void)
struct vm_area_struct * next = mpnt->vm_next; struct vm_area_struct * next = mpnt->vm_next;
if (mpnt->vm_ops && mpnt->vm_ops->close) if (mpnt->vm_ops && mpnt->vm_ops->close)
mpnt->vm_ops->close(mpnt); mpnt->vm_ops->close(mpnt);
if (mpnt->vm_inode)
iput(mpnt->vm_inode);
kfree(mpnt); kfree(mpnt);
mpnt = next; mpnt = next;
} }
......
...@@ -79,6 +79,8 @@ struct symbol_table symbol_table = { 0, 0, 0, /* for stacked module support */ ...@@ -79,6 +79,8 @@ struct symbol_table symbol_table = { 0, 0, 0, /* for stacked module support */
X(do_munmap), X(do_munmap),
X(insert_vm_struct), X(insert_vm_struct),
X(zeromap_page_range), X(zeromap_page_range),
X(unmap_page_range),
X(merge_segments),
/* internal kernel memory management */ /* internal kernel memory management */
X(__get_free_pages), X(__get_free_pages),
...@@ -95,6 +97,7 @@ struct symbol_table symbol_table = { 0, 0, 0, /* for stacked module support */ ...@@ -95,6 +97,7 @@ struct symbol_table symbol_table = { 0, 0, 0, /* for stacked module support */
X(iput), X(iput),
X(namei), X(namei),
X(lnamei), X(lnamei),
X(open_namei),
/* device registration */ /* device registration */
X(register_chrdev), X(register_chrdev),
...@@ -133,6 +136,8 @@ struct symbol_table symbol_table = { 0, 0, 0, /* for stacked module support */ ...@@ -133,6 +136,8 @@ struct symbol_table symbol_table = { 0, 0, 0, /* for stacked module support */
/* process management */ /* process management */
X(wake_up), X(wake_up),
X(wake_up_interruptible), X(wake_up_interruptible),
X(sleep_on),
X(interruptible_sleep_on),
X(schedule), X(schedule),
X(current), X(current),
X(jiffies), X(jiffies),
......
...@@ -412,8 +412,9 @@ int del_timer(struct timer_list * timer) ...@@ -412,8 +412,9 @@ int del_timer(struct timer_list * timer)
return 1; return 1;
} }
} }
if (p->next || p->prev) if (timer->next || timer->prev)
printk("del_timer() called with timer not initialized\n"); printk("del_timer() called from %08lx with timer not initialized\n",
((unsigned long *) &timer)[-1]);
restore_flags(flags); restore_flags(flags);
return 0; return 0;
#else #else
......
...@@ -537,5 +537,6 @@ _sys_call_table: ...@@ -537,5 +537,6 @@ _sys_call_table:
.long _sys_bdflush .long _sys_bdflush
.long _sys_sysfs /* 135 */ .long _sys_sysfs /* 135 */
.long _sys_personality .long _sys_personality
.long 0 /* for afs_syscall */
.space (NR_syscalls-136)*4 .space (NR_syscalls-137)*4
...@@ -724,9 +724,16 @@ static int try_to_share(unsigned long to_address, struct vm_area_struct * to_are ...@@ -724,9 +724,16 @@ static int try_to_share(unsigned long to_address, struct vm_area_struct * to_are
from &= PAGE_MASK; from &= PAGE_MASK;
from_page = from + PAGE_PTR(from_address); from_page = from + PAGE_PTR(from_address);
from = *(unsigned long *) from_page; from = *(unsigned long *) from_page;
/* is the page clean and present? */ /* is the page present? */
if ((from & (PAGE_PRESENT | PAGE_DIRTY)) != PAGE_PRESENT) if (!(from & PAGE_PRESENT))
return 0;
/* if it is private, it must be clean to be shared */
if ((from_area->vm_page_prot & PAGE_COW) && (from & PAGE_DIRTY))
return 0; return 0;
/* the swap caching doesn't really handle shared pages.. */
if (in_swap_cache(from))
return 0;
/* is the page reasonable at all? */
if (from >= high_memory) if (from >= high_memory)
return 0; return 0;
if (mem_map[MAP_NR(from)] & MAP_PAGE_RESERVED) if (mem_map[MAP_NR(from)] & MAP_PAGE_RESERVED)
...@@ -856,6 +863,28 @@ static inline unsigned long get_empty_pgtable(struct task_struct * tsk,unsigned ...@@ -856,6 +863,28 @@ static inline unsigned long get_empty_pgtable(struct task_struct * tsk,unsigned
return 0; return 0;
} }
static inline void do_swap_page(struct vm_area_struct * vma,
unsigned long address, unsigned long * pge, unsigned long entry)
{
unsigned long page;
if (vma->vm_ops && vma->vm_ops->swapin)
page = vma->vm_ops->swapin(vma, entry);
else
page = swap_in(entry);
if (*pge != entry) {
free_page(page);
return;
}
page = page | vma->vm_page_prot;
if (mem_map[MAP_NR(page)] > 1 && (page & PAGE_COW))
page &= ~PAGE_RW;
++vma->vm_task->mm->rss;
++vma->vm_task->mm->maj_flt;
*pge = page;
return;
}
void do_no_page(struct vm_area_struct * vma, unsigned long address, void do_no_page(struct vm_area_struct * vma, unsigned long address,
unsigned long error_code) unsigned long error_code)
{ {
...@@ -870,9 +899,7 @@ void do_no_page(struct vm_area_struct * vma, unsigned long address, ...@@ -870,9 +899,7 @@ void do_no_page(struct vm_area_struct * vma, unsigned long address,
if (entry & PAGE_PRESENT) if (entry & PAGE_PRESENT)
return; return;
if (entry) { if (entry) {
++vma->vm_task->mm->rss; do_swap_page(vma, address, (unsigned long *) page, entry);
++vma->vm_task->mm->maj_flt;
swap_in((unsigned long *) page);
return; return;
} }
address &= PAGE_MASK; address &= PAGE_MASK;
...@@ -886,6 +913,7 @@ void do_no_page(struct vm_area_struct * vma, unsigned long address, ...@@ -886,6 +913,7 @@ void do_no_page(struct vm_area_struct * vma, unsigned long address,
page = get_free_page(GFP_KERNEL); page = get_free_page(GFP_KERNEL);
if (share_page(vma, address, error_code, page)) { if (share_page(vma, address, error_code, page)) {
++vma->vm_task->mm->min_flt; ++vma->vm_task->mm->min_flt;
++vma->vm_task->mm->rss;
return; return;
} }
if (!page) { if (!page) {
...@@ -1224,7 +1252,7 @@ void si_meminfo(struct sysinfo *val) ...@@ -1224,7 +1252,7 @@ void si_meminfo(struct sysinfo *val)
/* This handles a generic mmap of a disk file */ /* This handles a generic mmap of a disk file */
unsigned long file_mmap_nopage(struct vm_area_struct * area, unsigned long address, static unsigned long file_mmap_nopage(struct vm_area_struct * area, unsigned long address,
unsigned long page, int error_code) unsigned long page, int error_code)
{ {
struct inode * inode = area->vm_inode; struct inode * inode = area->vm_inode;
...@@ -1244,33 +1272,11 @@ unsigned long file_mmap_nopage(struct vm_area_struct * area, unsigned long addre ...@@ -1244,33 +1272,11 @@ unsigned long file_mmap_nopage(struct vm_area_struct * area, unsigned long addre
return bread_page(page, inode->i_dev, nr, inode->i_sb->s_blocksize, !(error_code & PAGE_RW)); return bread_page(page, inode->i_dev, nr, inode->i_sb->s_blocksize, !(error_code & PAGE_RW));
} }
void file_mmap_free(struct vm_area_struct * area)
{
if (area->vm_inode)
iput(area->vm_inode);
#if 0
if (area->vm_inode)
printk("Free inode %x:%d (%d)\n",area->vm_inode->i_dev,
area->vm_inode->i_ino, area->vm_inode->i_count);
#endif
}
/*
* Compare the contents of the mmap entries, and decide if we are allowed to
* share the pages
*/
int file_mmap_share(struct vm_area_struct * area1,
struct vm_area_struct * area2,
unsigned long address)
{
return 1;
}
struct vm_operations_struct file_mmap = { struct vm_operations_struct file_mmap = {
NULL, /* open */ NULL, /* open */
file_mmap_free, /* close */ NULL, /* close */
file_mmap_nopage, /* nopage */ file_mmap_nopage, /* nopage */
NULL, /* wppage */ NULL, /* wppage */
file_mmap_share, /* share */ NULL, /* share */
NULL, /* unmap */ NULL, /* unmap */
}; };
...@@ -194,6 +194,8 @@ void unmap_fixup(struct vm_area_struct *area, ...@@ -194,6 +194,8 @@ void unmap_fixup(struct vm_area_struct *area,
if (addr == area->vm_start && end == area->vm_end) { if (addr == area->vm_start && end == area->vm_end) {
if (area->vm_ops && area->vm_ops->close) if (area->vm_ops && area->vm_ops->close)
area->vm_ops->close(area); area->vm_ops->close(area);
if (area->vm_inode)
iput(area->vm_inode);
return; return;
} }
...@@ -216,8 +218,8 @@ void unmap_fixup(struct vm_area_struct *area, ...@@ -216,8 +218,8 @@ void unmap_fixup(struct vm_area_struct *area,
mpnt->vm_start = end; mpnt->vm_start = end;
if (mpnt->vm_inode) if (mpnt->vm_inode)
mpnt->vm_inode->i_count++; mpnt->vm_inode->i_count++;
insert_vm_struct(current, mpnt);
area->vm_end = addr; /* Truncate area */ area->vm_end = addr; /* Truncate area */
insert_vm_struct(current, mpnt);
} }
/* construct whatever mapping is needed */ /* construct whatever mapping is needed */
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
* This file should contain most things doing the swapping from/to disk. * This file should contain most things doing the swapping from/to disk.
* Started 18.12.91 * Started 18.12.91
*/ */
#define SWAP_CACHE_INFO
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/sched.h> #include <linux/sched.h>
...@@ -47,12 +48,114 @@ static struct swap_info_struct { ...@@ -47,12 +48,114 @@ static struct swap_info_struct {
extern int shm_swap (int); extern int shm_swap (int);
/* unsigned long *swap_cache;
* The following are used to make sure we don't thrash too much... static unsigned long swap_cache_size;
* NOTE!! NR_LAST_FREE_PAGES must be a power of 2...
*/ #ifdef SWAP_CACHE_INFO
#define NR_LAST_FREE_PAGES 32 static unsigned long add_calls_total = 0;
static unsigned long last_free_pages[NR_LAST_FREE_PAGES] = {0,}; static unsigned long add_calls_success = 0;
static unsigned long del_calls_total = 0;
static unsigned long del_calls_success = 0;
static unsigned long find_calls_total = 0;
static unsigned long find_calls_success = 0;
extern inline void show_swap_cache_info (void)
{
printk("Swap cache: add %ld/%ld, delete %ld/%ld, find %ld/%ld\n",
add_calls_total, add_calls_success,
del_calls_total, del_calls_success,
find_calls_total, find_calls_success);
}
#endif
extern inline unsigned long init_swap_cache (unsigned long mem_start,
unsigned long mem_end)
{
mem_start = (mem_start + 15) & ~15;
swap_cache = (unsigned long *) mem_start;
swap_cache_size = mem_end >> PAGE_SHIFT;
memset(swap_cache, 0, swap_cache_size * sizeof (unsigned long));
#ifdef SWAP_CACHE_INFO
printk("%ld bytes for swap cache allocated\n",
swap_cache_size * sizeof(unsigned long));
#endif
return (unsigned long) (swap_cache + swap_cache_size);
}
extern inline long find_in_swap_cache (unsigned long addr)
{
unsigned long entry;
#ifdef SWAP_CACHE_INFO
find_calls_total++;
#endif
__asm__ __volatile__ (
"xchgl %0,%1\n"
: "=m" (swap_cache[addr >> PAGE_SHIFT]),
"=r" (entry)
: "0" (swap_cache[addr >> PAGE_SHIFT]),
"1" (0)
);
#ifdef SWAP_CACHE_INFO
if (entry)
find_calls_success++;
#endif
return entry;
}
extern inline int add_to_swap_cache (unsigned long addr, unsigned long entry)
{
struct swap_info_struct * p = &swap_info[SWP_TYPE(entry)];
#ifdef SWAP_CACHE_INFO
add_calls_total++;
#endif
if ((p->flags & SWP_WRITEOK) == SWP_WRITEOK) {
__asm__ __volatile__ (
"xchgl %0,%1\n"
: "=m" (swap_cache[addr >> PAGE_SHIFT]),
"=r" (entry)
: "0" (swap_cache[addr >> PAGE_SHIFT]),
"1" (entry)
);
if (entry) {
printk("swap_cache: replacing non-NULL entry\n");
}
#ifdef SWAP_CACHE_INFO
add_calls_success++;
#endif
return 1;
}
return 0;
}
extern inline int delete_from_swap_cache(unsigned long addr)
{
unsigned long entry;
#ifdef SWAP_CACHE_INFO
del_calls_total++;
#endif
__asm__ __volatile__ (
"xchgl %0,%1\n"
: "=m" (swap_cache[addr >> PAGE_SHIFT]),
"=r" (entry)
: "0" (swap_cache[addr >> PAGE_SHIFT]),
"1" (0)
);
if (entry) {
#ifdef SWAP_CACHE_INFO
del_calls_success++;
#endif
swap_free(entry);
return 1;
}
return 0;
}
void rw_swap_page(int rw, unsigned long entry, char * buf) void rw_swap_page(int rw, unsigned long entry, char * buf)
{ {
...@@ -142,7 +245,7 @@ unsigned long swap_duplicate(unsigned long entry) ...@@ -142,7 +245,7 @@ unsigned long swap_duplicate(unsigned long entry)
} }
p = type + swap_info; p = type + swap_info;
if (offset >= p->max) { if (offset >= p->max) {
printk("swap_free: weirdness\n"); printk("swap_duplicate: weirdness\n");
return 0; return 0;
} }
if (!p->swap_map[offset]) { if (!p->swap_map[offset]) {
...@@ -193,42 +296,24 @@ void swap_free(unsigned long entry) ...@@ -193,42 +296,24 @@ void swap_free(unsigned long entry)
wake_up(&lock_queue); wake_up(&lock_queue);
} }
void swap_in(unsigned long *table_ptr) unsigned long swap_in(unsigned long entry)
{ {
unsigned long entry;
unsigned long page; unsigned long page;
entry = *table_ptr;
if (PAGE_PRESENT & entry) {
printk("trying to swap in present page\n");
return;
}
if (!entry) {
printk("No swap page in swap_in\n");
return;
}
if (SWP_TYPE(entry) == SHM_SWP_TYPE) {
shm_no_page ((unsigned long *) table_ptr);
return;
}
if (!(page = get_free_page(GFP_KERNEL))) { if (!(page = get_free_page(GFP_KERNEL))) {
oom(current); oom(current);
page = BAD_PAGE; return BAD_PAGE;
} else
read_swap_page(entry, (char *) page);
if (*table_ptr != entry) {
free_page(page);
return;
} }
*table_ptr = page | (PAGE_DIRTY | PAGE_PRIVATE); read_swap_page(entry, (char *) page);
swap_free(entry); if (add_to_swap_cache(page, entry))
return page | PAGE_PRIVATE;
swap_free(entry);
return page | PAGE_DIRTY | PAGE_PRIVATE;
} }
static inline int try_to_swap_out(unsigned long * table_ptr) static inline int try_to_swap_out(unsigned long * table_ptr)
{ {
int i; unsigned long page, entry;
unsigned long page;
unsigned long entry;
page = *table_ptr; page = *table_ptr;
if (!(PAGE_PRESENT & page)) if (!(PAGE_PRESENT & page))
...@@ -237,13 +322,14 @@ static inline int try_to_swap_out(unsigned long * table_ptr) ...@@ -237,13 +322,14 @@ static inline int try_to_swap_out(unsigned long * table_ptr)
return 0; return 0;
if (mem_map[MAP_NR(page)] & MAP_PAGE_RESERVED) if (mem_map[MAP_NR(page)] & MAP_PAGE_RESERVED)
return 0; return 0;
if ((PAGE_DIRTY & page) && delete_from_swap_cache(page)) {
return 0;
}
if (PAGE_ACCESSED & page) { if (PAGE_ACCESSED & page) {
*table_ptr &= ~PAGE_ACCESSED; *table_ptr &= ~PAGE_ACCESSED;
return 0; return 0;
} }
for (i = 0; i < NR_LAST_FREE_PAGES; i++)
if (last_free_pages[i] == (page & PAGE_MASK))
return 0;
if (PAGE_DIRTY & page) { if (PAGE_DIRTY & page) {
page &= PAGE_MASK; page &= PAGE_MASK;
if (mem_map[MAP_NR(page)] != 1) if (mem_map[MAP_NR(page)] != 1)
...@@ -256,6 +342,26 @@ static inline int try_to_swap_out(unsigned long * table_ptr) ...@@ -256,6 +342,26 @@ static inline int try_to_swap_out(unsigned long * table_ptr)
free_page(page); free_page(page);
return 1; return 1;
} }
if ((entry = find_in_swap_cache(page))) {
*table_ptr |= PAGE_DIRTY;
if (mem_map[MAP_NR(page)] != 1) {
return 0;
}
if (!entry) {
if (!(entry = get_swap_page())) {
return 0;
}
*table_ptr = entry;
invalidate();
write_swap_page(entry, (char *) (page & PAGE_MASK));
} else {
*table_ptr = entry;
invalidate();
}
free_page(page & PAGE_MASK);
return 1;
}
page &= PAGE_MASK; page &= PAGE_MASK;
*table_ptr = 0; *table_ptr = 0;
invalidate(); invalidate();
...@@ -531,8 +637,10 @@ void free_pages(unsigned long addr, unsigned long order) ...@@ -531,8 +637,10 @@ void free_pages(unsigned long addr, unsigned long order)
if (!(*map & MAP_PAGE_RESERVED)) { if (!(*map & MAP_PAGE_RESERVED)) {
save_flags(flag); save_flags(flag);
cli(); cli();
if (!--*map) if (!--*map) {
free_pages_ok(addr, order); free_pages_ok(addr, order);
delete_from_swap_cache(addr);
}
restore_flags(flag); restore_flags(flag);
if(*map == 1) { if(*map == 1) {
int j; int j;
...@@ -639,6 +747,9 @@ void show_free_areas(void) ...@@ -639,6 +747,9 @@ void show_free_areas(void)
} }
restore_flags(flags); restore_flags(flags);
printk("= %lukB)\n", total); printk("= %lukB)\n", total);
#ifdef SWAP_CACHE_INFO
show_swap_cache_info();
#endif
} }
/* /*
...@@ -654,6 +765,7 @@ static int try_to_unuse(unsigned int type) ...@@ -654,6 +765,7 @@ static int try_to_unuse(unsigned int type)
struct task_struct *p; struct task_struct *p;
nr = 0; nr = 0;
/* /*
* When we have to sleep, we restart the whole algorithm from the same * When we have to sleep, we restart the whole algorithm from the same
* task we stopped in. That at least rids us of all races. * task we stopped in. That at least rids us of all races.
...@@ -677,8 +789,15 @@ static int try_to_unuse(unsigned int type) ...@@ -677,8 +789,15 @@ static int try_to_unuse(unsigned int type)
page = *ppage; page = *ppage;
if (!page) if (!page)
continue; continue;
if (page & PAGE_PRESENT) if (page & PAGE_PRESENT) {
if (!(page = in_swap_cache(page)))
continue;
if (SWP_TYPE(page) != type)
continue;
*ppage |= PAGE_DIRTY;
delete_from_swap_cache(*ppage);
continue; continue;
}
if (SWP_TYPE(page) != type) if (SWP_TYPE(page) != type)
continue; continue;
if (!tmp) { if (!tmp) {
...@@ -898,6 +1017,7 @@ unsigned long free_area_init(unsigned long start_mem, unsigned long end_mem) ...@@ -898,6 +1017,7 @@ unsigned long free_area_init(unsigned long start_mem, unsigned long end_mem)
unsigned long mask = PAGE_MASK; unsigned long mask = PAGE_MASK;
int i; int i;
start_mem = init_swap_cache(start_mem, end_mem);
mem_map = (unsigned short *) start_mem; mem_map = (unsigned short *) start_mem;
p = mem_map + MAP_NR(end_mem); p = mem_map + MAP_NR(end_mem);
start_mem = (unsigned long) p; start_mem = (unsigned long) p;
......
...@@ -746,13 +746,13 @@ int arp_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt) ...@@ -746,13 +746,13 @@ int arp_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
entry->hlen = hlen; entry->hlen = hlen;
entry->htype = htype; entry->htype = htype;
entry->flags = ATF_COM; entry->flags = ATF_COM;
entry->timer.next = entry->timer.prev = NULL;
memcpy(entry->ha, sha, hlen); memcpy(entry->ha, sha, hlen);
entry->last_used = jiffies; entry->last_used = jiffies;
entry->dev = skb->dev; entry->dev = skb->dev;
skb_queue_head_init(&entry->skb); skb_queue_head_init(&entry->skb);
entry->next = arp_tables[hash]; entry->next = arp_tables[hash];
arp_tables[hash] = entry; arp_tables[hash] = entry;
entry->timer.next = entry->timer.prev = NULL;
sti(); sti();
} }
...@@ -837,14 +837,14 @@ int arp_find(unsigned char *haddr, unsigned long paddr, struct device *dev, ...@@ -837,14 +837,14 @@ int arp_find(unsigned char *haddr, unsigned long paddr, struct device *dev,
entry->htype = dev->type; entry->htype = dev->type;
entry->flags = 0; entry->flags = 0;
memset(entry->ha, 0, dev->addr_len); memset(entry->ha, 0, dev->addr_len);
entry->last_used = jiffies;
entry->next = arp_tables[hash];
entry->dev = dev; entry->dev = dev;
arp_tables[hash] = entry; entry->last_used = jiffies;
entry->timer.next = entry->timer.prev = NULL; entry->timer.next = entry->timer.prev = NULL;
entry->timer.function = arp_expire_request; entry->timer.function = arp_expire_request;
entry->timer.data = (unsigned long)entry; entry->timer.data = (unsigned long)entry;
entry->timer.expires = ARP_RES_TIME; entry->timer.expires = ARP_RES_TIME;
entry->next = arp_tables[hash];
arp_tables[hash] = entry;
add_timer(&entry->timer); add_timer(&entry->timer);
entry->retries = ARP_MAX_TRIES; entry->retries = ARP_MAX_TRIES;
skb_queue_head_init(&entry->skb); skb_queue_head_init(&entry->skb);
...@@ -1048,8 +1048,8 @@ static int arp_req_set(struct arpreq *req) ...@@ -1048,8 +1048,8 @@ static int arp_req_set(struct arpreq *req)
entry->ip = ip; entry->ip = ip;
entry->hlen = hlen; entry->hlen = hlen;
entry->htype = htype; entry->htype = htype;
entry->next = arp_tables[hash];
entry->timer.next = entry->timer.prev = NULL; entry->timer.next = entry->timer.prev = NULL;
entry->next = arp_tables[hash];
arp_tables[hash] = entry; arp_tables[hash] = entry;
skb_queue_head_init(&entry->skb); skb_queue_head_init(&entry->skb);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment