! 1976-spec Filestore filing system ! Provides a collection of procedures performing file operations ! corresponding to the filestore remote commands. Each operation ! has a corresponding %integerfn, which performs the required filestore ! action and then returns a completion status to its caller. These ! %fns are prefixed with "FS ". A number of other procedures are ! also provided which perform miscellaneous operations which are not ! exported as external commands. These are prefixed with "FSx ". ! All other procedures are intended for internal consumption only. %option "-nocheck-nostack-low" %include "Config.Inc" %include "System:COMMON" %include "System:Utility.Inc" %include "System:Schedule.Inc" %include "System:Errors.Inc" ! Packed culprits %externalintegerspec err d %externalintegerspec err n1 %externalintegerspec err n2 %externalintegerspec err pling %include "Inc:Util.Imp" !include "I:UTIL.INC" %ownrecord(common fm)%name common %externalrecord(common fm)%mapspec common area %externalroutinespec print client address(%integer context) ! Global parameter -- indicates which (local) port has received the ! file system request (-ve for internal). %externalinteger ether context = 0 ! Write lock on file system: ! = 0 : disallow operations which would write to disc ! # 0 : allow all operations ! Initially allows all operations. Cleared down in FSx initialise if ! a disc transfer fails or a directory is corrupt. %externalinteger file system writeable = 1 ! Bitmap manipulation. Procedures for allocating, deallocating and ! checking disc extents. Byte operations are performed, where possible, ! with individual bit operations being performed only when this is not ! possible. Bitmaps are scanned sequentially from the start..... %routine free extent(%integer start, free size, %bytename bitmap) ! Deallocate the blocks specified by and %integer i, bit, byte, size %bytename b %byte x %label error start = start & h; free size = free size & h size = free size !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Free "); write(size, 0) !D printstring(" block"); print symbol('s') %if size # 1 !D printstring(" starting at "); write(start, 0) !D newline !D %finish %unless 0 <= start < fp size %and size > 0 %c %and 0 < start + size <= fp size %start pdate printstring("*** Free extent -- illegal extent: ") write(start, 0); space; write(size, 0) newline %return %finish ! First the non-byte-aligned part at the start bit = start & 7; byte = start >> 3 b == bitmap[byte] %if bit = 0 %start b == b[-1] %else %while size # 0 %and bit # 0 %cycle x = 1 << bit -> error %if b & x = 0 b = b & (\ x) bit = (bit + 1) & 7 size = size - 1 %repeat %finish ! Then the byte-aligned part in the middle, taking ! it a full byte at a time. %while size >> 3 # 0 %cycle b == b[1] -> error %if b # 255 b = 0 size = size - 8 %repeat ! Finally the non-full-byte part at the end. ! 0 <= size <= 7 by now. bit = 0; b == b[1] %while size > 0 %cycle x = 1 << bit -> error %if b & x = 0 b = b !! x bit = bit + 1 size = size - 1 %repeat %return error: ! Come here if any of the blocks weren't allocated %for i = 0, 1, last partition %cycle ! Decide which partition we were dealing with %if bitmap == common_partition(i)_bitmap_b(0) %start pdate printstring("Extent not claimed: ") write(i, 0); print symbol(':') write(start, 0); print string(" (") write(free size, 0); print string("):") write(free size - size, 0) newline %return %finish %repeat ! Fall through here if we couldn't find which partition ! the bitmap corresponded to. pdate printstring("*** Unknown bitmap -- extent not claimed: ") write(start, 0); printstring(" (") write(size, 0); print symbol(')') newline %end %predicate check and allocate(%integer start, size, %bytename bitmap) ! Check if the any of the extent specified by and ! has been allocated. Mark it as allocated, regardless. ! NB should only be called during file system initialisation. %integer set = 0, bit, byte %bytename b %byte x start = start & h; size = size & h %unless 0 <= start < fp size %and size > 0 %c %and 0 < start + size <= fp size %start pdate printstring("*** Check and allocate -- illegal extent: ") write(start, 0); space; write(size, 0) newline %false %finish ! First the non-byte-aligned part at the start bit = start & 7; byte = start >> 3 b == bitmap[byte] %if bit = 0 %start b == b[-1] %else %while size > 0 %and bit # 0 %cycle x = 1 << bit set = 1 %if b & x # 0 b = b ! x bit = (bit + 1) & 7 size = size - 1 %repeat %finish ! Then the byte-aligned part in the middle, taking ! it a full byte at a time. %while size >> 3 # 0 %cycle b == b[1] set = 1 %if b # 0 b = 255 size = size - 8 %repeat ! Finally the non-full-byte part at the end ! 0 <= size <= 7 by now bit = 0; b == b[1] %while size > 0 %cycle x = 1 << bit set = 1 %if b & x # 0 b = b ! x bit = bit + 1 size = size - 1 %repeat %if set = 0 %then %false %else %true %end %routine allocate extent(%integer desired, %bytename bitmap, %integername actual, start) ! Try to allocate contiguous blocks in ! returning and to indicate result. ! Allocate largest possible if no extent would be enough. ! < 0 indicates that the disc is full. %integer pos, biggest, biggest pos, found, size, bit, byte %bytename b %byte x %label try again, got one, done, the lot, no more !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Allocate "); write(desired, 0) !D newline !D %finish desired = 8192 %if desired > 8192 pos = 0; biggest = 0 try again: ! Find the first unallocated block in the bitmap. ! First look at the non-byte-aligned part, starting ! at the current position. -> no more %if pos > fp size byte = pos >> 3; bit = pos & 7 b == bitmap[byte] %if bit = 0 %start b == b[-1] %else %while size # 0 %and bit # 0 %cycle -> got one %if b & (1 << bit) = 0 bit = (bit + 1) & 7 pos = pos + 1 -> no more %if pos > fp size %repeat %finish ! Then the byte-aligned part in the middle, taking ! it a full byte at a time. %cycle b == b[1] %exit %if b # 255 pos = pos + 8 -> no more %if pos > fp size %repeat ! A block was unallocated somewhere in the last byte ! checked. Find it. bit = 0 %cycle -> got one %if b & (1 << bit) = 0 ! Must be true somewhere, since b # 255 bit = bit + 1 pos = pos + 1 %repeat got one: ! Found an unallocated block. Now find out how many ! contiguous blocks there are. found = pos size = 0 ! First the non-byte-aligned part at the start bit = pos & 7; byte = pos >> 3 b == bitmap[byte] %if bit = 0 %start b == b[-1] %else %while bit # 0 %cycle %if b & (1 << bit) = 0 %then size = size + 1 %c %else -> the lot bit = (bit + 1) & 7 %repeat %finish ! Then the byte-aligned part in the middle, taking it ! a full byte at a time %cycle b == b[1] %exit %if b # 0 size = size + 8 -> the lot %if size >= desired %repeat ! One of the blocks in the last byte was allocated. Find ! out how many were unallocated. bit = 0 %cycle x = 1 << bit %if b & (1 << bit) = 0 %then size = size + 1 %c %else -> the lot ! %else part holds somewhere, since b # 0 bit = bit + 1 %repeat the lot: ! We now know how large the unallocated hole was. ! Determine if it is big enough. If not, was it bigger than ! the largest we already know about? %if size >= desired %start ! Big enough actual = size start = found -> done %finish %if size > biggest %start ! Larger than the ones we already know about biggest = size biggest pos = found %finish ! Now set a new starting position and go looking for another ! unallocated hole. pos = found + size -> try again no more: ! We've searched the entire bitmap for a large enough ! hole. We'll just have to return the largest we know about, ! since there weren't any sufficiently large. %if biggest = 0 %start ! No holes found!? The partition must be full start = -1 actual = -1 %return %else ! The largest we know about start = biggest pos actual = biggest %finish done: ! We've found our hole. Now mark it all as allocated. ! First the non-byte-aligned part at the start. size = actual pos = start bit = pos & 7; byte = pos >> 3 b == bitmap[byte] %if bit = 0 %start b == b[-1] %else %while size # 0 %and bit # 0 %cycle x = 1 << bit b = b ! x bit = (bit + 1) & 7 size = size - 1 %repeat %finish ! Then the byte-aligned part in the middle, taking ! it a full byte at a time. %while size >> 3 # 0 %cycle b == b[1] b = 255 size = size - 8 %repeat ! Finally the non-full-byte part at the end. ! 0 <= size <= 7 by now. bit = 0; b == b[1] %while size > 0 %cycle x = 1 << bit b = b ! x bit = bit + 1 size = size - 1 %repeat %end ! Special buffer allocation/deallocation. Special buffers are used ! to implement pseudo-files, such as DIRECTORY and all the ones ! "owned" by user $. %owninteger special mask = 0 %integerfn allocate special ! Find a free special buffer %integer i, x %for i = 1, 1, specials %cycle ! Scan the allocation map, looking for a free one. x = 1 << i %if special mask & x = 0 %start ! Found one. Allocate it, zap it and ! return its index. special mask = special mask ! x common_specials(i) = 0 %result = i %finish %repeat %result = no buffer %end %routine free special(%integer which) ! Mark a special buffer as free. special mask = special mask & (\ (1 << which)) %end %routine add special(%record(special fm)%name b, %string(255) text) ! Add text to special buffer b, character at a time until either ! the text is exhausted or we have run out of buffer. %integer i, j j = b_bytes %if text # "" %start %for i = 1, 1, length(text) %cycle j = j + 1 b_b(j) = charno(text, i) %exit %if j = 4096 %repeat %finish b_bytes = j %end %routine spaces special(%record(special fm)%name b, %integer n) ! Add n spaces to special buffer b. %integer j j = b_bytes %while n > 0 %cycle j = j + 1 b_b(j) = ' ' %exit %if j = 4096 n = n - 1 %repeat b_bytes = j %end ! Disc I/O requests %include "SYSTEM:CACHE.INC" %integerfn read block(%integer partition, %integer block, %record(buffer fm)%name buffer) ! Read the specified block from the specified partition into ! the buffer provided. Note that the block address is given ! within the partition, not absolutely -- this allows an extra ! consistency check. block = block & h !D %if common_diags & disc diags # 0 %start !D pdate !D printstring("Read block "); write(block, 0) !D printstring(" from partition "); write(partition, 0) !D newline !D %finish ! Check that the specified block lies within the specified ! partition's boundaries. %result = data address error %unless 0 <= block < fp size ! Bump the monitor count and do the transfer common_monitor_file reads = common_monitor_file reads + 1 %result = make disc request(1, p start(partition) + block, addr(buffer_b(0)), D read, 0) %end %integerfn write block(%integer partition, %integer block, %record(buffer fm)%name buffer) ! Write and verify the specified block. Much the same as ! read block. block = block & h !D %if common_diags & disc diags # 0 %start !D pdate !D printstring("Write block "); write(block, 0) !D printstring(" to partition "); write(partition, 0) !D newline !D %finish %result = data address error %unless 0 <= block < fp size common_monitor_file writes = common_monitor_file writes + 1 %result = make disc request(1, p start(partition) + block, addr(buffer_b(0)), D write ! D verify, 0) %end %integerfn read directory(%record(dir info fm)%name dir) ! Read and verify the reqired directory. Much the same as ! read block, above. !D %if common_diags & disc diags # 0 %start !D pdate !D printstring("Read directory ") !D write(dir_partition, 0); print symbol('.') !D write(dir_user no, 0) !D newline !D %finish %result = directory address error %unless 0 < dir_user no <= u per p %c %and 0 <= dir_partition <= last partition common_monitor_dir reads = common_monitor_dir reads + 1 %result = make disc request(4, pd start(dir_partition) + 4 * dir_user no - 4, addr(dir_d), D read ! D verify, 1) %end %integerfn write directory(%record(dir info fm)%name dir) ! Write and verify the specified directory. Again, much the ! same as read block. !D %if common_diags & disc diags # 0 %start !D pdate !D printstring("Write directory ") !D write(dir_partition, 0); print symbol('.') !D write(dir_user no, 0) !D newline !D %finish %result = directory address error %unless 0 < dir_user no <= u per p %c %and 0 <= dir_partition <= last partition common_monitor_dir writes = common_monitor_dir writes + 1 %result = make disc request(4, pd start(dir_partition) + 4 * dir_user no - 4, addr(dir_d), D write ! D verify, 1) %end %integerfn read system block(%integer block, count, %record(*)%name buffer) ! Read and verify blocks from the system area at the end of the ! disc (currently only the bad block lists and system password live there). %if common_diags & disc diags # 0 %start pdate printstring("Read system block "); write(block, 0) printstring(" ("); write(count, 0); print symbol(')') newline %finish %result = size error %if count <= 0 %result = data address error %if block < 0 %c %or block + count > head size %result = make disc request(count, sy2 start + block, addr(buffer), %c D read ! D verify, 0) %end %integerfn write system block(%integer block, count, %record(*)%name buffer) ! Write and verify blocks to the system area. %if common_diags & disc diags # 0 %start pdate printstring("Write system block "); write(block, 0) printstring(" ("); write(count, 0); print symbol(')') newline %finish %result = size error %if count <= 0 %result = data address error %if block < 0 %c %or block + count > head size %result = make disc request(count, sy2 start + block, addr(buffer), %c D write ! D verify, 0) %end %integerfn read boot area(%integer block, %bytename buffer) ! Read and verify blocks from the system area at the start of the ! disc (the boot area). %if common_diags & disc diags # 0 %start pdate printstring("Read boot block "); write(block, 0) newline %finish %result = data address error %unless 0 <= block < head size %result = make disc request(1, sy1 start + block, addr(buffer), %c D read ! D verify, 0) %end %integerfn write boot area(%integer block, %bytename buffer) ! Write and verify blocks to the boot area. %if common_diags & disc diags # 0 %start pdate printstring("Write boot block "); write(block, 0) newline %finish %result = data address error %unless 0 <= block < head size %result = make disc request(1, sy1 start + block, addr(buffer), %c D write ! D verify, 0) %end ! Bad block list handling. Four copies of the bad block lists are ! maintained in the system area on the disc. They are all read in ! and the one with the highest stamp value us used. %constinteger bad limit = 512 - 3 %recordformat bad fm((%integer stamp, %integerarray bad(1 : bad limit), %integer last bad, checksum) %c %or %integerarray x(1 : 512)) %%ownrecord(bad fm)%array bad blocks(1 : 4) = 0(*) %ownrecord(bad fm)%name bad block list %owninteger newest bad block list = -1 %externalrecord(bad fm)%spec dc bad list %integerfn bad checksum(%record(bad fm)%name b) ! Calculate the checksum for the bad block lists %integer i, c c = 0 c = c + b_x(i) %for i = 1, 1, 512 %result = c %end %routine read bad block list ! Read the bad block lists, verify their checksums, and note ! the one with the highest stamp. %integer i, x, stamp, check %for i = 1, 1, 4 %cycle ! First get all the lists x = read system block(bad area(i), 4, bad blocks(i)) %if x # success %start ! Failed to read this one. Zap the store copy. pdate printstring("*** Failed to read bad block area "); write(i, 0) newline bad blocks(i) = 0 %finish %repeat stamp = -1 %for i = 1, 1, 4 %cycle ! Now validate the checksums (should be zero) and ! find which list has the highest stamp. bad block list == bad blocks(i) check = bad checksum(bad block list) %if check # 0 %start pdate printstring("*** Bad block area "); write(i, 0) printstring(" checksum error") newline %else %if bad block list_stamp > stamp stamp = bad block list_stamp newest bad block list = i %finish %repeat %if stamp < 0 %start ! Disaster. Log it, then protect the file system. pdate printstring("*** No readable bad block list") newline bad block list == nil file system writeable = 0 %else ! Found one useable one. bad block list == bad blocks(newest bad block list) pdate printstring("Bad block list "); write(newest bad block list, 0) printstring(" shows "); write(bad block list_last bad, 0) printstring(" error") print symbol('s') %if bad block list_last bad # 1 print symbol(':'); newline %for i = 1, 1, bad block list_last bad %cycle write(bad block list_bad(i), 7) newline %if i & 15 = 0 %repeat newline %if bad block list_last bad & 15 # 0 dc bad list = bad block list %finish %end %routine add to bad block list(%integer block) ! Dynamically add a block to a one of the bad block ! lists, bumping its stamp. %integer i, x, oldest, oldest stamp %if bad blocks(newest bad block list)_last bad = bad limit %start pdate printstring("*** Bad block list is full -- failed to add ") write(block, 0) newline %return %finish oldest stamp = infinity %for i = 1, 1, 4 %cycle %if i # newest bad block list %c %and bad blocks(i)_stamp < oldest stamp %start oldest = i oldest stamp = bad blocks(i)_stamp %finish %repeat bad blocks(oldest) = bad blocks(newest bad block list) bad block list == bad blocks(oldest) %for i = bad block list_last bad, -1, 1 %cycle %if bad block list_bad(i) > block %start bad block list_bad(i + 1) = bad block list_bad(i) %else %if bad block list_bad(i) = block pdate printstring("*** Block "); write(block, 0) printstring(" already in bad block list ") write(oldest, 0); newline bad block list_stamp = 0; ! Reuse this one -- it's corrupted bad block list == bad blocks(newest bad block list) %return %else bad block list_bad(i + 1) = block -> inserted %finish %repeat bad block list_bad(1) = block inserted: bad block list_last bad = bad block list_last bad + 1 bad block list_stamp = bad block list_stamp + 1 bad block list_checksum = 0 bad block list_checksum = -bad checksum(bad block list) dc bad list = bad block list newest bad block list = oldest x = write system block(bad area(oldest), 4, bad block list) %if x # success %start pdate printstring("*** Failed to write bad block list (") write(oldest, 0); print symbol(')') newline %return %finish pdate printstring("*** Bad block "); write(block, 0) printstring(" inserted in bad block list "); write(oldest, 0) newline %end %routine P reach add to bad block list(%integer partition, block) ! Required because the compiler can't reach otherwise.... add to bad block list(p start(partition) + block) %end %recordformat pass fm((%integer pass1, pass2) %c %or %integerarray x(1 : 128)) %integerfn set system pass(%integer new pass) %record(pass fm) p = 0 %integer x, OK = 0 p_pass1 = new pass p_pass2 = -new pass x = write system block(pass area(1), 1, p) %if x # success %start pdate printstring("***Failed to write pass area 1"); newline OK = x %finish x = write system block(pass area(2), 1, p) %if x # success %start pdate printstring("***Failed to write pass area 2"); newline OK = x %finish %result = x %if x # success common_system pass = new pass %result = success %end %routine get system pass(%integername pass) %record(pass fm) p = 0 %integer x, p1, p2, i, j x = read system block(pass area(1), 1, p) %if x # success %start pdate printstring("***Failed to read pass area 1"); newline p1 = 0 %else j = 0 j = j + p_x(i) %for i = 1, 1, 128 %if j = 0 %start p1 = p_pass1 %else pdate printstring("*** Checksum error for pass area 1") newline p1 = 0 %finish %finish x = read system block(pass area(2), 1, p) %if x # success %start pdate printstring("***Failed to read pass area 2"); newline p2 = 0 %else j = 0 j = j + p_x(i) %for i = 1, 1, 128 %if j = 0 %start p2 = p_pass1 %else pdate printstring("*** Checksum error for pass area 2") newline p2 = 0 %finish %finish %if 0 # p1 = p2 %start pass = p1 %else pdate printstring("*** Password areas disagree") newline %if p1 # 0 %start pass = p1 %else %if p2 # 0 pass = p2 %else pdate printstring("*** Password not set") newline %finish %finish %end ! Miscellaneous file system constants %constinteger dollars = 16_F9FF; ! $$$ packed %constinteger dots = 16_F396; ! ... packed %constinteger subliminal 1 = dollars << 16 ! dots; ! $$$... %constinteger subliminal 2 = dots << 16 ! dots; ! ...... %constinteger ANON = 16_087F5780; ! ANON packed %constinteger GDMR = 16_2C6D7080; ! Ditto GDMR %constinteger RWT = 16_742C0000; ! ... RWT %constinteger SYSTEM = 16_7ABB7DD5; ! ... SYSTEM %constinteger temporary = 62400; ! $ packed %constinteger F permission = 3 %constinteger R permission = 2 %constinteger O permission = 1 %constinteger N permission = 0 %constinteger owner permission shift = 14 %constinteger public permission shift = 12 %constinteger archive bit shift = 11 %constinteger log bit shift = 10 %constinteger log bit = 1 << log bit shift ! Fields are: owner access; public access; archive; log; pseudo-user; size ! OO PP A L P ---size---- %constinteger files mask = 2_00 00 0 0 0 0 1111 1111 %constinteger bytes mask = 2_00 00 0 0 0 1 1111 1111 %constinteger permissions mask = 2_11 11 1 1 0 0 0000 0000 ! 16_FFFF0000 %constinteger FRA = 2_11 10 1 0 0 0 0000 0000 ! 16_FFFF0000 %constinteger FNA = 2_11 00 1 0 0 0 0000 0000 ! 16_FFFF0000 {** FRIG **} %constinteger pseudo user = 2_00 00 0 0 1 0 0000 0000 %constinteger time mask = 2_0000 0 111 1111 1111 %constinteger transient file = 16_8000 ! 16_FFFF0000 {** FRIG **} %constinteger dud file = 16_4000 ! Miscellaneous utility stuff %routine stamp(%record(stamp fm)%name s) ! Fill a time stamp record with the current date/time time stamp(s_date, s_time) %end %string(31)%fn show stamp(%record(stamp fm)%name s) ! Unpack the contents of a time stamp record %result = unpack date(s_date) . " " . unpack time(s_time) %end %string(3)%fn unpack perms(%short mask) ! Unpack the permissions fields from a proterction mask %conststring(1)%array p(0 : 3) = "N", "O", "R", "F" %conststring(1)%array a(0 : 1) = "V", "A" %result = p(mask >> owner permission shift & 3) . %c p(mask >> public permission shift & 3) . %c a(mask >> archive bit shift & 1) %end %routine pad(%integer desired, %record(buffer fm)%name b) ! Pad a buffer to the desired size by adding trailing spaces %integername bytes bytes == b_bytes b_b(bytes) = ' ' %and bytes = bytes + 1 %while bytes < desired %end %routine add text(%string(255) s, %record(buffer fm)%name b) ! Add text to a buffer %integer i, j j = b_bytes %if s # "" %start %for i = 1, 1, length(s) %cycle b_b(j) = charno(s, i) j = j + 1 %repeat %finish b_bytes = j %end ! File system procedures start here..... %integerfn file size(%integer which, %record(directory fm)%name dir, %integername blocks, extents) ! Find the number of blocks and extents in the specified file. %record(entry fm)%name f %integer p extents, files, i files = dir_header_files & files mask %unless 0 <= files <= file limit %start ! Directory claims to have too many files in it. pdate printstring("*** Directory ") printstring(unpack(dir_header_owner, 0)) printstring(" corrupt (file size): bad file limit ") write(files, 0) printstring(" ("); write(file limit, 0) print symbol(')') newline err d = dir_header_owner err n1 = 0; err n2 = 0; err pling = 0 %result = directory corrupt & xs error %finish %if files * 4 >= dir_file(files)_extents %start ! Files and extents overlap. pdate printstring("*** Directory ") printstring(unpack(dir_header_owner, 0)) printstring(" corrupt (file size): overlap ") write(files, 0) write(dir_file(files)_extents, 1) newline err d = dir_header_owner err n1 = 0; err n2 = 0; err pling = 0 %result = directory corrupt & xs error %finish %result = file not found %unless 0 < which <= files ! We know the file slot is valid, so find the extents and ! count them up.... f == dir_file(which) %if which = 1 %start p extents = extent limit + 1 %else p extents = dir_file(which - 1)_extents %finish blocks = 0 i = f_extents %while i < p extents %cycle blocks = blocks + (dir_extent(i)_size & h) i = i + 1 %repeat extents = p extents - f_extents %result = success %end %integerfn delete file(%integer slot, %record(dir info fm)%name dir info) ! Delete the file in the specified slot of the directory supplied. ! Free the allocated extents then shuffle those belonging to the other ! files in the directory so as to obliterate them. Note that it is OK ! to do things in this order, since if we crash before the directory has ! been written out then when we are rebooted the file will exist in its ! pre-deletion state. We assume that the user has the requisite ! authority to delete files from the directory. %record(Xno info fm)%name Xno info %record(extent fm)%name e %record(header fm)%name h %record(entry fm)%name f %integer files, extents, p extents, t extents, i, q restore, x %bytename bitmap %result = not writeable %if file system writeable = 0 !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Delete file "); write(slot, 0) !D newline !D %finish h == dir info_d_header files = h_files & files mask %unless 0 < slot <= files %start ! Specified file slot is outwith the range of those ! which would be legal in this directory pdate printstring("*** Directory ") printstring(unpack(dir info_d_header_owner, 0)) printstring(" corrupt (delete): bad file slot ") write(slot, 0) printstring(" ("); write(files, 0) print symbol(')') newline err d = dir info_d_header_owner err n1 = 0; err n2 = 0; err pling = 0 %result = directory corrupt & xs error %finish %if files * 4 >= dir info_d_file(files)_extents %start ! Files and extents overlap in directory pdate printstring("*** Directory ") printstring(unpack(dir info_d_header_owner, 0)) printstring(" corrupt (delete): overlap ") write(files, 0) write(dir info_d_file(files)_extents, 1) newline err d = dir info_d_header_owner err n1 = 0; err n2 = 0; err pling = 0 %result = directory corrupt & xs error %finish f == dir info_d_file(slot) ! Check that user has full owner authority %result = no authority %c %unless f_perms >> owner permission shift & 3 = F permission %for i = 1, 1, Xnos %cycle ! If the file is currently open somewhere then we must ! leave it around until the transaction is closed. We ! transform it into a special temporary ($$$.........) ! whence it will be deleted at some future date. Note that ! this is the only case where files with duplicate names ! can come to exist in a directory. Xno info == common_Xno(i) %if Xno info_Uno >= 0 %and Xno info_dir info == dir info %c %and Xno info_file slot = slot %start ! Found it owned by some Xno, so merely change its name. ! Note that this may involve a quota adjustment. !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Open as Xno "); write(i, 0) !D printstring(", -> Subliminal") !D newline !D %finish %if f_name1 >> 16 < temporary %c %and f_status & transient file = 0 %start ! Must restore quota x = file size(slot, dir info_d, t extents, q restore) %result = x %if x # success dir info_d_header_quota left = dir info_d_header_quota left %c + q restore %finish f_name1 = subliminal 1 f_name2 = subliminal 2 dir info_written = 1 %result = 1 {** Special for Logoff **} %finish %repeat ! Find extent limit for file %if slot = 1 %then p extents = extent limit + 1 %c %else p extents = dir info_d_file(slot - 1)_extents f == dir info_d_file(slot) bitmap == common_partition(dir info_partition)_bitmap_b(0) q restore = 0 %for i = f_extents, 1, p extents - 1 %cycle ! Cycle round freeing file's extents, counting up the ! blocks used in case the user's quota requires adjustment e == dir info_d_extent(i) q restore = q restore + e_size %if e_size = 0 %start ! Found an extent with no blocks allocated !! %if f_extents # p extents - 1 %start ! More than one extent to the file. Complain. pdate printstring("*** Delete file ") printstring(unpack(dir info_d_header_owner, 0)) print symbol(':') printstring(unpack(f_name1, f_name2)) printstring(" -- illegal null extent") newline !! %finish ! Else must be null file -- treat it as OK, since we may ! have decided that null files are allowable (see close). %else ! Free the extent if the file didn't overlap another ! somewhere (shouldn't happen, of course.....) free extent(e_start, e_size, bitmap) %if f_status & dud file = 0 e_start = 0; e_size = 0; ! Just in case.... %finish %repeat ! Restore user's quota if this wasn't a temporary file h_quota left = h_quota left + q restore %c %if f_name1 >> 16 < temporary %and f_status & transient file = 0 %if slot = files %start ! File occupies most recent slot in directory. Easy case, ! do it as a special. h_files = h_files - 1 dir info_written = 1 %result = success %finish ! Not the last in the directory, so we must shuffle.... t extents = dir info_d_file(files)_extents extents = p extents - f_extents %if extents # 0 %start dir info_d_extent(i + extents) = dir info_d_extent(i) %c %for i = f_extents - 1, -1, t extents %finish %for i = slot + 1, 1, files %cycle f == dir info_d_file(i - 1) f = dir info_d_file(i) f_extents = f_extents + extents %repeat ! One fewer files now, of course. h_files = h_files - 1 dir info_written = 1 ! Since we've moved some files in the directory we have to check ! all the Xno records, since they refer to files by directory slot. ! If we find any referring to a shuffled file then we must adjust it. %for i = 1, 1, Xnos %cycle Xno info == common_Xno(i) %if Xno info_dir info == dir info %start ! Xno refers to this directory. Adjust the Xno. Xno info_file slot = Xno info_file slot - 1 %c %if Xno info_file slot > slot %finish %repeat %result = success %end %integerfn directory stamp ! LRU stamp for directory cache %owninteger stamp = 0 stamp = stamp + 1 %result = stamp %end %integerfn get directory(%integer owner, partition, user no) ! Find a directory in the cache. Bring it in if it isn't ! already there. is packed username. is ! user's slot in the user register. %integer i, possible = -1, files %integer stamp = infinity %record(dir info fm)%name d %record(entry fm)%name file %result = directory address error %unless 0 < user no <= u per p %c %and 0 <= partition <= last partition ! First of all, have a look through the cache for the directory. ! If we find it bump its reference count and return its cache index. ! On the fly, note the least recently used directory with a zero ! reference count, in case we need one to throw out..... %for i = 1, 1, dirs %cycle d == common_dir info(i) %if d_owner = owner %start ! Found the directory in the cache d_ref count = d_ref count + 1 d_stamp = directory stamp %result = i %else %if d_ref count = 0 %and d_stamp < stamp ! Not this one, but it does have a zero ref count possible = i stamp = d_stamp %finish %repeat %result = no buffer %if possible < 0 ! We didn't find the directory, but we did find a candidate ! for replacement. Note our interest in the slot, then get ! the directory in off the disc. d == common_dir info(possible) d_partition = partition d_user no = user no d_ref count = 1 d_owner = owner d_written = 0 d_stamp = directory stamp i = read directory(d) %result = i %if i < 0 ! Now some consistency checks on the directory contents. %if 0 # d_d_header_directory size # directory size %start ! Directory size is wrong. It should have been 0 or 2048. ! We really want to use this field for something more useful, ! such as a checksum.... pdate printstring("*** Directory ") printstring(unpack(d_d_header_owner, 0)) printstring(" corrupt (get directory): bad size ") write(d_d_header_directory size, 0); newline ! err d = d_d_header_owner ! err n1 = 0; err n2 = 0; err pling = 0 ! %result = directory corrupt & xs error %finish files = d_d_header_files & files mask %unless 0 <= files <= file limit %start ! Directory claims to have an unreasonable number of ! files in it. pdate printstring("*** Directory ") printstring(unpack(d_d_header_owner, 0)) printstring(" corrupt (get directory): bad file limit ") write(files, 0) printstring(" ("); write(file limit, 0) print symbol(')') newline err d = d_d_header_owner err n1 = 0; err n2 = 0; err pling = 0 %result = directory corrupt & xs error %finish %if files # 0 %start %if files * 4 >= d_d_file(files)_extents %start ! Files and extents overlap. pdate printstring("*** Directory ") printstring(unpack(d_d_header_owner, 0)) printstring(" corrupt (get directory): overlap ") write(files, 0) write(d_d_file(files)_extents, 1) newline err d = d_d_header_owner err n1 = 0; err n2 = 0; err pling = 0 %result = directory corrupt & xs error %finish %finish %result = possible %end %routine remember directory(%record(dir info fm)%name d) ! We have been handed a pointer to the directory record ! by somebody. Note our interest in case it goes away ! while we are still interested in it. d_ref count = d_ref count + 1 d_stamp = directory stamp %end %integerfn forget directory(%record(dir info fm)%name d) ! We are no longer interested in this directory. Decrement the ! ref count. Delete temporaries if it goes to zero. Write ! out the directory if the written flag is set. %record(entry fm)%name file %record(header fm)%name header %integer i, k %integer x = success %if d_ref count <= 0 %start ! Reference count is just about to go negative?! pdate printstring("*** Refcount going negative for ") printstring(unpack(d_owner, 0)) newline d_ref count = 2; ! Lock it down forever more (we hope!) !? %result = refcount negative %finish %if d_ref count = 1 %start ! We are the last lot to have had an interest in ! the directory, so we now run around deleting ! temporaries and transients. header == d_d_header i = 1 %while i <= header_files & files mask %cycle file == d_d_file(i) %if file_name1 >> 16 >= temporary %or %c file_status & transient file # 0 %start k = delete file(i, d) %if common_diags & fsys diags # 0 %start pdate printstring("Delete file: ") write(k, 0) newline %finish i = i + 1 %if k # success ! Note that the above condition will also hold ! if the file being "deleted" was successfully ! turned into $$$......... -- this shouldn't ! happen, of course, since it implies that ! someone else has an unregistered interest ! in the directory...... d_written = 1 %else i = i + 1 %finish %repeat %finish ! Write the directory if the written flag is set. Only clear ! it if the write succeeded. x = write directory(d) %if d_written # 0 d_written = 0 %if x = success ! Finally, register that we are no longer interested in the ! directory by decrementing the reference count, and as a ! parting gesture set the LRU stamp. zz: d_ref count = d_ref count - 1 d_stamp = directory stamp %result = x %end %externalroutine FSx display directory ! Dump a directory's contents onto the console. %record(dir info fm)%name dir info %record(directory fm)%name d %record(entry fm)%name file %record(header fm)%name header %record(extent fm)%name extent %string(127) s %integer x, i, j prompt("Directory: "); read(x) %return %unless 0 < x <= dirs dir info == common_dir info(x) d == dir info_d header == d_header printstring("Directory "); printstring(unpack(header_owner, 0)) newline %if header_files & files mask = 0 %start printstring("No files"); newline %return %finish x = extent limit %for i = 1, 1, header_files & files mask %cycle file == d_file(i) printstring("File "); write(i, 0); printstring(": ") printstring(unpack(file_name1, file_name2)) print symbol('?') %if file_status & dud file # 0 print symbol('!') %if file_status & transient file # 0 newline %for j = file_extents, 1, x %cycle extent == d_extent(j) printstring(" Extent "); write(j - file_extents, 2) printstring(" ("); write(j, 2); printstring(") ") write(extent_start & h, 5); write(extent_size, 5) newline %repeat x = file_extents - 1 %repeat %end %integerfn allocate Uno ! Allocate a Uno record for the user. Unallocated ones are ! known by having a null (packed) logged-on owner. %record(Uno info fm)%name Uno info %integer i %for i = 1, 1, Unos %cycle ! Try them all.... Uno info == common_Uno(i) %if Uno info_l owner = 0 %start ! Got one. Mark it as in use (with an invalid owner ! name) and return the Uno slot to the caller Uno info_l owner = -1 stamp(Uno info_logon stamp) Uno info_active stamp = Uno info_logon stamp Uno info_Xno active stamp = 0 %result = i %finish %repeat %result = no Uno %end %integerfn validate Uno(%integer Uno, zero status) ! Check a Uno to see if it has been allocated to the user on ! the ether port specified by ether context. %record(Uno info fm)%name Uno info %if Uno = 0 %start ! ANON -- special case, since some actions are permitted ! (eg OpenR, ReadFile), while others aren't (eg Quote) %result = zero status %if zero status # success Uno info == common_Uno(0) %else ! Not ANON -- must be a valid Uno which has been allocated to ! the caller's ether context %result = illegal Uno %unless 0 < Uno <= Unos Uno info == common_Uno(Uno) %result = illegal Uno %if Uno info_l owner = 0 { Not in use } %result = illegal Uno %if Uno info_context # ether context %finish stamp(Uno info_active stamp) %result = success %end %externalintegerfn FSx clone Uno(%integer Uno, context) ! For some internal operations (eg internal copy) we want a ! Uno record which has all the caller's attributes (eg owner, ! default directory, password) EXCEPT for the ether context ! which we want not to be valid for the outside universe. ! This avoids internal operations being splatted by the ! user logging off.... %record(Uno info fm)%name s Uno info, c Uno info %integer allocated, x x = validate Uno(Uno, success) %result = x %if x # success s Uno info == common_Uno(Uno) ! Caller's Uno is OK, so allocate a new one.... allocated = allocate Uno %result = allocated %if allocated < success c Uno info == common_Uno(allocated) ! Clone the caller's Uno, and set the context ! to that supplied c Uno info = s Uno info c Uno info_context = context ! Note interest in logon and default directories remember directory(c Uno info_l directory) remember directory(c Uno info_d directory) %result = allocated %end %integerfn allocate Xno ! Allocate a Xno record for the user. Unallocated ones are ! known by having a negative associated Uno. %record(Xno info fm)%name Xno info %integer i %for i = 1, 1, Xnos %cycle Xno info == common_Xno(i) %if Xno info_Uno < 0 %start ! Found a free one. Claim it (NB can't use -ve Uno ! since that means unallocated (because we can't use ! zero to mean this since that is a valid Uno!)) by ! setting an invalid +ve Uno. Xno info_Uno = Unos + 1 Xno info_context = ether context stamp(Xno info_opened stamp) Xno info_active stamp = Xno info_opened stamp %result = i %finish %repeat %result = -1 %end %integerfn validate Xno(%integer Xno) ! Check a Xno to see if it has been allocated to the user on ! the ether port specified by ether context. %record(Xno info fm)%name Xno info %unless 0 < Xno <= Xnos %start !D pdate !D printstring("*** Invalid Xno (range) ") !D write(Xno, 0) !D newline %result = illegal Xno %finish Xno info == common_Xno(Xno) %if Xno info_Uno < 0 %start !D pdate !D printstring("*** Invalid Xno (not allocated) ") !D write(Xno, 0) !D newline %result = illegal Xno %finish %result = illegal Xno %if Xno info_context # ether context stamp(Xno info_active stamp) stamp(common_Uno(Xno info_Uno)_Xno active stamp) %result = success %end %integerfn check conflicts(%integer o, n1, n2, pling, flags) ! Check the Xno table to see if the file is already open ! in a conflicting mode. ! pling = 0 : ignore '!' files ! pling # 0 : consider '!' files %record(Xno info fm)%name Xno info %record(directory fm)%name d %record(entry fm)%name file %integer i %for i = 1, 1, Xnos %cycle Xno info == common_Xno(i) %if Xno info_Uno > 0 %start ! Xno allocated, check owner d == Xno info_dir info_d %if d_header_owner = o %start ! Correct owner, now check filename file == d_file(Xno info_file slot) %if file_name1 = n1 %and file_name2 = n2 %start ! Check the pling state %if pling # 0 %or file_status & transient file = 0 %start ! Same file, check the access %if Xno info_flags & flags # 0 %start ! Conflicting mode of access err d = o err n1 = n1; err n2 = n2 err pling = 0 %result = conflicting access & xs error %finish %finish %finish %finish %finish %repeat ! Got here, so there wasn't a conflict %result = success %end %integerfn check password(%integer password, %record(directory fm)%name dir) ! Check if supplied password matches either that in the directory ! or the system password. Either is OK. Null directory password ! matches everything. %result = success %if dir_header_password = 0 %c %or dir_header_password = password %c %or password = common_system pass %result = no authority %end %integerfn find owner(%integer owner, %integername part, entry) ! Search the user register for a (packed) username. Return ! partition and slot therein if found. %record(register fm)%name r %for part = 0, 1, last partition %cycle r == common_partition(part)_register %for entry = 1, 1, u per p %cycle %result = success %if r_owner(entry) = owner %repeat %repeat err d = owner err n1 = 0; err n2 = 0 err pling = 0 %result = owner not found & xs error %end %integerfn find file(%record(directory fm)%name dir, %integer n1, n2, mask) ! Find a file in a directory. '!' files may be ignored by setting ! the appropriate bit in mask. %record(header fm)%name h %record(entry fm)%name e %integer i, files h == dir_header files = h_files & files mask %unless 0 <= files <= file limit %start ! Directory claims to have an unreasonable number ! of files. pdate printstring("*** Directory ") printstring(unpack(dir_header_owner, 0)) printstring(" corrupt (find file): bad file limit ") write(files, 0) printstring(" ("); write(file limit, 0) print symbol(')') newline err d = dir_header_owner err n1 = 0; err n2 = 0; err pling = 0 %result = directory corrupt & xs error %finish %if files = 0 %start err d = dir_header_owner err n1 = n1; err n2 = n2 %result = file not found %finish ! Directory has some files in it. Search them, starting with ! the most recent -- this guarantees that '!' files will be ! found before their non-'!' namesakes (for, eg, deletion). %for i = files, -1, 1 %cycle e == dir_file(i) %result = i %if e_name1 = n1 %c %and e_name2 = n2 %c %and e_status & mask = 0 %repeat err d = dir_header_owner err n1 = n1; err n2 = n2 %result = file not found %end ! File access reporting %routine log access(%record(Uno info fm)%name Uno info, %integer directory name, %record(entry fm)%name file, %string(15) operation) %return %if file_perms & log bit = 0 pdate printstring(unpack(Uno info_l owner, 0)) print symbol('!') %if Uno info_q pass = common_system pass printstring(" at "); print client address(ether context) printstring(" -- "); printstring(unpack(directory name, 0)) print symbol(':'); printstring(unpack(file_name1, file_name2)) print symbol('?') %if file_status & dud file # 0 print symbol('!') %if file_status & transient file # 0 printstring(" -- "); printstring(operation) newline %end ! Initialisation %externalroutine FSx initialise file system ! Set it all going. Read the bad block lists. Scan ! all the user directories, building the bitmaps and ! checking for inconsistencies. %record(Uno info fm)%name Uno %record(dir info fm)%name d %record(partition fm)%name p %record(directory fm)%name dir %record(header fm)%name header %record(extent fm)%name extent %record(entry fm)%name file %record(register fm)%name register %integer i, j, k, x, f, e, dud, write flag, total files, extent barrier %integer owners, allocated, p extent, files, extents, total extents %integer bad owners, bad blocks, first bad, last bad %bytename bitmap %record(bitmap fm) multiple %label get next user, get next user x, no dud common == common area common_Xno(i)_Uno = -1 %for i = 1, 1, Xnos get system pass(common_system pass) read bad block list d == common_dir info(1) %for j = 0, 1, last partition %cycle p == common_partition(j); p = 0 bitmap == p_bitmap_b(0) register == p_register ! Having previously read in the bad block list, we now check ! the bounds of each partition's directory and data areas against ! it. We allocate bad blocks and mark bad directories in the ! user register. bad blocks = 0; bad owners = 0 %if bad block list ## nil %and bad block list_last bad # 0 %start first bad = p start(j) last bad = first bad + fp size - 1 %for k = 1, 1, bad block list_last bad %cycle %if first bad <= bad block list_bad(k) <= last bad %start bad blocks = bad blocks + 1 %if check and allocate(bad block list_bad(k) - first bad, 1, bitmap) %start pdate printstring("*** Bad block ") write(bad block list_bad(k), 0) printstring(" already allocated") newline %finish %finish %repeat first bad = pd start(j) last bad = first bad + dp size - 1 %for k = 1, 1, bad block list_last bad %cycle %if first bad <= bad block list_bad(k) <= last bad %start bad owners = bad owners + 1 register_owner((bad block list_bad(k) %c - first bad) // 4 + 1) = -1 %finish %repeat %finish ! Now read each directory in turn, adding any users found to ! the register and allocating the blocks in their files. If ! any are found to overlap a second pass will be required. pdate printstring("Initialising for partition "); write(j, 0) printstring(" (max: "); write(fp size - bad blocks, 0) printstring(" blocks, ") write(u per p - bad owners, 0); printstring(" owners)") newline owners = 0; allocated = 0 dud = 0; total extents = 0 total files = 0 multiple = 0 p_bitmap_b(bitmap size) = 255; ! last 0 .. 7 blocks are never used %for k = 1, 1, u per p %cycle %continue %if register_owner(k) < 0 { Bad } d_partition = j; d_user no = k x = read directory(d) %if x # 0 %start pdate printstring("Read directory ") write(j, 0) print symbol('.') write(k, 0) printstring(" fails ") write(x, 0) newline file system writeable = 0 %continue %finish dir == d_d header == dir_header write flag = 0 %if header_owner # 0 %start !D pdate !D printstring("User "); printstring(unpack(header_owner, 0)) !D printstring(", quota "); write(header_quota left & h, 0) !D newline owners = owners + 1 register_owner(k) = header_owner files = header_files & files mask %if 0 < files <= file limit %start ! Not an unreasonable number of files. So far so good... total files = total files + files p extent = extent limit + 1 extent barrier = dir_file(files)_extents %unless 0 < extent barrier <= extent limit %start ! Files and extents overlap. pdate printstring("*** ") printstring(unpack(header_owner, 0)) printstring(" -- corrupt directory (extents)") newline -> get next user %finish f = 1 ! Now, for each file in turn allocate its extents %while f <= files %cycle file == dir_file(f) e = p extent - 1 extents = 0 %while e >= file_extents %cycle %if e >= extent barrier %start extent == dir_extent(e) extents = extents + 1 %if extent_size = 0 %start !! %if file_extents # p extent - 1 %start pdate printstring("*** ") printstring(unpack(header_owner, 0)) print symbol(':') printstring(unpack(file_name1, file_name2)) printstring(" -- illegal null extent ") write(extents, 0); print symbol(':') write(extent_start & h, 0) newline file system writeable = 0 !! %finish ! Else must be a null file -- allow it %else ! Allocate the extent. Allocate it in the ! duplicates bitmap if it has already been ! allocated in the normal one. %if check and allocate(extent_start, %c extent_size, bitmap) %start dud = dud + 1 %if check and allocate(extent_start, %c extent_size, %c multiple_b(0)) %start ! Dummy -- mark extent multiply allocated %finish %finish allocated = allocated + (extent_size & h) %finish %else pdate printstring("*** ") printstring(unpack(header_owner, 0)) printstring(" -- corrupt directory (file)") newline file system writeable = 0 -> get next user %finish e = e - 1 %repeat p extent = file_extents f = f + 1 total extents = total extents + extents %repeat %else %if files # 0 ! Non-zero, but unreasonable pdate printstring("*** Corrupt directory ") write(j, 0) print symbol('.') write(k, 0) newline file system writeable = 0 %finish %finish get next user: %repeat ! If we haven't found any dud files, or the file system has ! been marked as protected anyway (due to a problem reading in ! a directory) then we skip the second pass. -> no dud %if dud = 0 %or file system writeable = 0 pdate printstring("*** Partition has overlapping extents") newline dud = 0 %for k = 1, 1, u per p %cycle %unless 1 <= k <= 128 %start pdate printstring("Compyler bug, k = "); write(k, 0) newline %exit %finish %continue %if register_owner(k) <= 0 { Bad or not in use } d_partition = j; d_user no = k x = read directory(d) %if x # 0 %start pdate printstring("Read directory ") write(j, 0) print symbol('.') write(k, 0) printstring(" fails ") write(x, 0) newline file system writeable = 0 %continue %finish dir == d_d header == dir_header write flag = 0 files = header_files & files mask %if 0 < files <= file limit %start p extent = extent limit + 1 extent barrier = dir_file(files)_extents -> get next user x %unless 0 < extent barrier <= extent limit f = 1 %while f <= files %cycle file == dir_file(f) e = p extent - 1 extents = 0 %while e >= file_extents %cycle %if e >= extent barrier %start extent == dir_extent(e) extents = extents + 1 %if extent_size > 0 %start %if check and allocate(extent_start, %c extent_size, %c multiple_b(0)) %start %if file_status & dud file = 0 %start ! File not already marked as dud pdate printstring("*** ") printstring(unpack(header_owner, 0)) print symbol(':') printstring(unpack(file_name1, file_name2)) printstring(" -- multiple allocation: extent ") write(extents, 0) printstring(", start ") write(extent_start, 0) printstring(", finish ") write(extent_start + extent_size - 1, 0) printstring(", size ") write(extent_size, 0) newline file_status = file_status ! dud file write flag = 1 %finish dud = dud + 1 %finish %finish %else ! Dubious directory file system writeable = 0 -> get next user x %finish e = e - 1 %repeat p extent = file_extents f = f + 1 %repeat x = write directory(d) %if write flag # 0 %finish ! Else corrupt directory get next user x: %repeat no dud: ! Directory scan passes complete for this partition pdate write(owners, 0); printstring(" owner") print symbol('s') %if owners # 1; printstring(", ") write(allocated, 0); printstring(" block") print symbol('s') %if allocated # 1 printstring(" ("); write(total files, 0) printstring(" file"); print symbol('s') %if total files # 1 printstring(", "); write(total extents, 0) printstring(" extent"); print symbol('s') %if total extents # 1 printstring(") allocated") %if dud # 0 %start printstring(", ") write(dud, 0) printstring(" dud extent") print symbol('s') %if dud # 1 %finish newline %repeat ! Directories checked, bitmaps and register built. ! Now log on ANON permanently, to avoid special cases elsewhere. x = find owner(anon, i, j) %if x = success %start x = get directory(anon, i, j) %if x < success %start pdate printstring("*** Failed to read ANON directory ***") newline file system writeable = 0 %return %finish Uno == common_Uno(0) Uno_l owner = anon Uno_d owner = anon Uno_q pass = 0 Uno_l directory == common_dir info(x) Uno_d directory == Uno_l directory Uno_context = 0 stamp(Uno_logon stamp) Uno_active stamp = Uno_logon stamp Uno_Xno active stamp = 0 %else pdate printstring("*** No ANON ***") newline file system writeable = 0 %finish common_monitor = 0 pdate printstring("File system initialised") printstring(" read-only") %if file system writeable = 0 newline ! Finally set system availability (if it has not been set already ! from the console during initialisation). common_system open = -1 %if common_system open = 0 %end %externalintegerfn FS new owner(%integer Uno, %string(255) s owner, %integer quota) ! Add a new user to the file system %integer x, i, j, dir, user no, partition %integer owner, blank %record(dir info fm)%name d %record(directory fm)%name directory %record(partition fm)%name p %record(header fm)%name header %record(register fm)%name register %record(Uno info fm)%name Uno info %label got one %result = not writeable %if file system writeable = 0 x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) %result = no authority %if Uno info_q pass # common_system pass ! Creator must be privileged. Now validate username and quota quota = default quota %if quota = 0 ! Extract partition number and user name from first param %result = param error & p1 error %unless 2 <= length(s owner) <= 7 partition = charno(s owner, 1) - '0' %result = param error & p1 error %unless 0 <= partition <= last partition x = pack(substring(s owner, 2, length(s owner)), owner, blank) %result = param error & p1 error %if x # success %or blank # 0 p == common_partition(partition) ! Check to see if owner exists already. We insist that ! owner names are unique. x = find owner(owner, i, j) %if x = success %start err d = owner err n1 = 0; err n2 = 0; err pling = 0 %result = user exists & xs error %finish ! OK to create. Find a free slot in the register. register == p_register %for user no = 1, 1, u per p %cycle -> got one %if register_owner(user no) = 0 %repeat err d = owner err n1 = 0; err n2 = 0; err pling = 0 %result = no slot & xs error got one: ! Found a register slot. Get a directory slot. dir = get directory(owner, partition, user no) %result = dir %if dir < 0 d == common_dir info(dir) directory == d_d %if directory_header_owner # 0 %start ! Directory claims to be owned already?! x = forget directory(d) err d = owner err n1 = 0; err n2 = 0; err pling = 0 %result = file exists & xs error %finish ! Initialise directory header directory = 0 header == directory_header header_owner = owner header_password = 0 header_quota left = quota header_perms = FNA; ! Was FRA header_directory size = 2048 d_written = 1 register_owner(user no) = owner pdate printstring(unpack(Uno info_l owner, 0)) printstring(" created new user: "); printstring(unpack(header_owner, 0)) printstring(" (") write(partition, 0) print symbol('.') write(user no, 0) printstring("), quota "); write(header_quota left & h, 0) newline %result = forget directory(d) %end %externalintegerfn FS change quota(%integer Uno, %string(255) s owner, %integer delta) ! Modify a user's quota %integer x, dir, user no, partition %integer owner, blank %record(dir info fm)%name d %record(directory fm)%name directory %record(header fm)%name header %record(Uno info fm)%name Uno info %result = not writeable %if file system writeable = 0 x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) %result = no authority %if Uno info_q pass # common_system pass ! Privileged callers only. Now find the target. x = pack(s owner, owner, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(owner, partition, user no) err d = owner %and %result = x & p1 error %if x # success dir = get directory(owner, partition, user no) %result = dir %if dir < 0 d == common_dir info(dir) directory == d_d header == directory_header header_quota left <- header_quota left + delta d_written = 1 pdate printstring(unpack(Uno info_l owner, 0)) printstring(" setting new quota: "); printstring(unpack(header_owner, 0)) printstring(" (") write(partition, 0) print symbol('.') write(user no, 0) printstring("), quota "); write(header_quota left & h, 0) newline %result = forget directory(d) %end %externalintegerfn FS partition(%integer Uno, partition, %integername bytes, %record(buffer fm)%name b) ! Return a list of the users on the specified half-partition. %record(register fm)%name register %integer who %integer x, half, i x = validate Uno(Uno, success) %result = x %if x # success half = (partition & 1) * 64 partition = partition >> 1 %result = param error & p1 error %unless 0 <= partition <= last partition register == common_partition(partition)_register b_bytes = 0 %for i = half + 1, 1, half + 64 %cycle %if i <= u per p %then who = register_owner(i) %c %else who = 0 %if who = 0 %then add text("---", b) %c %else add text(unpack(who, 0), b) pad(8 * (i - half) - 1, b) %if i & 7 = 0 %then add text(snl, b) %c %else add text(" ", b) %repeat bytes = b_bytes %result = success %end %externalintegerfn FS logon(%string(255) ownername, %string(255) password, %integername Uno) ! Log on a user. Check username and password, and if OK set ! up defaults. %record(dir info fm)%name dir info %record(directory fm)%name dir %record(header fm)%name header %record(Uno info fm)%name Uno info %integer x, part = -1, entry %integer owner, pass, blank x = pack(ownername, owner, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(owner, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = owner %result = x & xs error %finish !! password = endecrypt(ownername, password, 0) pass = encrypt(password) %result = logins disabled %if common_system open & allow logins = 0 %c %and pass # common_system pass ! Username exists. Now get a Uno, check the password, and if OK ! set up the defaults. Uno = allocate Uno %result = no Uno %if Uno < 0 Uno info == common_Uno(Uno) Uno info_context = ether context x = get directory(owner, part, entry) Uno info_l owner = 0 %and %result = x %if x < success dir info == common_dir info(x) dir == dir info_d header == dir_header ! Check to see whether the user is pseudo- or real %if header_perms & pseudo user # 0 %and ether context > 0 %c %and pass # common_system pass %start ! Not allowed, report it and dump everything pdate printstring("Logon pseudo-user "); printstring(unpack(owner, 0)) printstring(" at "); print client address(ether context) printstring(" rejected"); newline x = forget directory(dir info) Uno info_l owner = 0 %result = no authority %finish Uno info_l owner = 0 !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Logon "); printstring(unpack(header_owner, 0)) !D printstring(", quota "); write(header_quota left & h, 0) !D printstring(", Uno "); write(Uno, 0) !D newline !D %finish %if check password(pass, dir) = success %start ! Password is acceptable. Set up defaults. %if pass = common_system pass %start pdate printstring(unpack(owner, 0)) printstring(" at "); print client address(ether context) printstring(" using system pass") newline %else %if pass # 0 %and dir_header_password = 0 ! Null directory password requires null quoted password Uno info_l owner = 0 x = forget directory(dir info) %result = no authority %finish Uno info_l owner = owner Uno info_d owner = owner Uno info_q pass = pass Uno info_l directory == dir info Uno info_d directory == dir info common_monitor_logons = common_monitor_logons + 1 ! Note a second interest in the initial directory (once for ! the logon directory pointer and once more for the ! default directory pointer). remember directory(Uno info_d directory) %result = success %finish ! Bad password. Dump the directory and bounce the request. x = forget directory(dir info) Uno info_l owner = 0 %result = no authority %end %integerfnspec FS Uclose(%integer Xno) %externalintegerfn FS logoff(%integer Uno) ! Log off a user. UClose any open transactions. %record(Uno info fm)%name Uno info %record(Xno info fm)%name Xno info %integer x, i !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Logoff "); write(Uno, 0) !D newline !D %finish x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) %for i = 1, 1, Xnos %cycle Xno info == common_Xno(i) x = FS Uclose(i) %if Xno info_Uno = Uno { File in use } %repeat x = forget directory(Uno info_d directory) x = forget directory(Uno info_l directory) Uno info_l owner = 0 %result = x %end %integerfn delete user(%integer Uno, %string(255) user) %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %integer n1, n2, x, part, entry Uno info == common_Uno(Uno) ! We must be privileged %result = no authority %unless Uno info_q pass = common_system pass ! Find out if the user exists. If so, read in the directory. x = pack(user, n1, n2) %result = x & p1 error %if x < success %result = param error & p1 error %if n2 # 0 x = find owner(n1, part, entry) %if x < success %start err n1 = 0; err n2 = 0; err pling = 0 err d = n1 %result = x & xs error %finish x = get directory(n1, part, entry) %result = x %if x < success dir info == common_dir info(x) ! Directory must not be in use by someone else %if dir info_ref count # 1 %start x = forget directory(dir info) err d = n1 err n1 = 0; err n2 = 0; err pling = 0 %result = directory in use & xs error %finish ! Directory must be empty %if dir info_d_header_files & files mask # 0 %start x = forget directory(dir info) err d = n1 err n1 = 0; err n2 = 0; err pling = 0 %result = directory not empty & xs error %finish ! All OK, so zap the directory and register dir info_d_header_owner = 0 dir info_owner = 0 dir info_written = 1 common_partition(part)_register_owner(entry) = 0 pdate printstring(unpack(Uno info_l owner, 0)) printstring(" deleting user ") printstring(unpack(n1, 0)) newline %result = forget directory(dir info) %end %externalintegerfn FS delete(%integer Uno, %string(255) filename) ! Delete a file. Check user's authority, and if OK search ! for the file. Delete it if all is OK. %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %integer n1, n2, user, blank %integer x, z, slot, part, entry %string(255) user name %result = not writeable %if file system writeable = 0 x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if filename -> user name . (":") . filename %start ! Username supplied as part of filename. Use the ! corresponding directory (if it exists). If the ! resulting filename is null then we delete the user. %result = delete user(Uno, username) %if filename = "" %result = param error %if username = "" x = pack(user name, user, blank) %result = x & p1 error %if x # success %result = param error %if blank # 0 x = find owner(user, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = user %result = x & xs error %finish x = get directory(user, part, entry) %result = x %if x < success dir info == common_dir info(x) %if dir info ## Uno info_l directory %start ! Not logon directory, so password must match before ! deletion is allowed. x = check password(Uno info_q pass, dir info_d) z = forget directory(dir info) %and %result = x %if x # success %finish %else ! No directory supplied, so use the current default. dir info == Uno info_d directory %if dir info ## Uno info_l directory %start ! Not logon directory, so password must match before ! deletion is allowed. x = check password(Uno info_q pass, dir info_d) %result = x %if x # success %finish ! Note our interest in the directory remember directory(dir info) %finish %if charno(filename, length(filename)) = '!' %start ! Remove the '!' from '!'-files. Fail the request ! if this leaves a null name. length(filename) = length(filename) - 1 x = forget directory(dir info) %and %result = param error & p1 error %c %if filename = "" %finish x = pack(filename, n1, n2) z = forget directory(dir info) %and %result = x & p1 error %if x # success ! Authority and filename are OK. Try to find the file. slot = find file(dir info_d, n1, n2, 0) %if slot < success %start err d = dir info_d_header_owner err n1 = n1; err n2 = n2 x = forget directory(dir info) %result = slot & xs error %finish log access(Uno info, dir info_d_header_owner, dir info_d_file(slot), "Delete") x = delete file(slot, dir info) z = forget directory(dir info) %result = x %if x < success %result = z %end %integerfn rename user(%record(Uno info fm)%name Uno info, %string(255) from, to) ! Change an owner's name. This required modifying the ! directory (and the cache entry) and the register. %record(partition fm)%name p %record(dir info fm)%name dir info %integer f, t, x, part, entry, i %string(255) q %result = not writeable %if file system writeable = 0 ! Privileged users only. %result = no authority %unless Uno info_q pass = common_system pass ! Resultant name must be of the form : (ie ':' must ! be present and no filename is allowed). Note that has ! already been checked before we are called. %result = param error & p2 error %unless to -> to .(":"). q %result = param error & p2 error %if q # "" x = pack(from, f, i) %result = param error & p1 error %if x # success %or i # 0 x = pack(to, t, i) %result = param error & p2 error %if x # success %or i # 0 ! Both usernames are OK. Check that the original username exists ! and that the new username doesn't. x = find owner(t, part, entry) %if x = success %start err n1 = 0; err n2 = 0; err pling = 0 err d = t %result = user exists & xs error %finish x = find owner(f, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = f %result = x & xs error %finish x = get directory(f, part, entry) %result = x %if x < success ! All OK, so make the changes. pdate printstring(unpack(Uno info_l owner, 0)) printstring(" renaming ") printstring(unpack(f, 0)) printstring(" to ") printstring(unpack(t, 0)) newline dir info == common_dir info(x) dir info_d_header_owner = t dir info_owner = t p == common_partition(part) p_register_owner(entry) = t dir info_written = 1 %result = forget directory(dir info) %end %externalintegerfn FS rename(%integer Uno, %string(255) from, to) ! Rename a file (or possibly a user). Caller must have appropriate ! authority, the source filename must exist, and the destination ! filename must not. Note special treatment of '!'-files. %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %record(entry fm)%name file %integer x, z, f1, f2, t1, t2, found from, part, entry, size, new q %integer user, blank %string(255) user name %result = not writeable %if file system writeable = 0 x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if from -> user name . (":") . from %start ! Username supplied. If the filename is blank then we ! are renaming the user. %result = param error & p1 error %if username = "" %result = rename user(Uno info, user name, to) %if from = "" ! Renaming the file. Is the username acceptable? x = pack(user name, user, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(user, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = user %result = x & xs error %finish x = get directory(user, part, entry) %result = x %if x < success dir info == common_dir info(x) ! Username exists. Check the password if we didn't ! log in as that user. %if dir info ## Uno info_l directory %start x = check password(Uno info_q pass, dir info_d) z = forget directory(dir info) %and %result = x %if x # success %finish %else ! No username given, so use the default. dir info == Uno info_d directory %if dir info ## Uno info_l directory %start ! Not the owner we logged in as, so check the password. x = check password(Uno info_q pass, dir info_d) %result = x %if x # success %finish remember directory(dir info) %finish %if charno(from, length(from)) = '!' %start length(from) = length(from) - 1 %if from = "" %start x = forget directory(dir info) %result = param error & p1 error %finish %finish ! Validate the source filename x = pack(from, f1, f2) z = forget directory(dir info) %and %result = x & p1 error %if x # success ! Source OK, how about the destination? First try to split off an ! owner name. If there is one, then it must be the same one as for the ! source (owner names are unique). %if to -> user name . (":") . to %start x = pack(user name, user, blank) %if x # success %or blank # 0 %or user # dir info_d_header_owner %start z = forget directory(dir info) %result = param error & p2 error %finish %finish x = pack(to, t1, t2) z = forget directory(dir info) %and %result = x & p2 error %if x # success ! Does the source exist? found from = find file(dir info_d, f1, f2, 0) %if found from < success %start err d = dir info_d_header_owner err n1 = f1; err n2 = f2 z = forget directory(dir info) %result = found from & xs error %finish ! Does the destination exist in its non-'!' form? x = find file(dir info_d, t1, t2, transient file) %if x >= success %start !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("File exists (non-'!') ") !D write(x, 0) !D newline !D %finish err d = dir info_d_header_owner err n1 = t1; err n2 = t2 err pling = dir info_d_file(x)_status & transient file z = forget directory(dir info) %result = file exists & xs error %finish ! Check again on the existence of the destination filename, ! this time including '!'-files among those searched. The only ! case where the destination filename is allowed to exist is ! if we are renaming a '!'-file to its non-'!' verion, in which ! case the source and destination file slots will be the same. ! Renaming to the same name as an already-existing '!'-file is ! not allowed. x = find file(dir info_d, t1, t2, 0) %if success <= x # found from %start !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("File exists ('!') ") !D write(x, 0) !D newline !D %finish err d = dir info_d_header_owner err n1 = t1; err n2 = t2 err pling = dir info_d_file(x)_status & transient file z = forget directory(dir info) %result = file exists & xs error %finish ! The destination filename doesn't exist, so we can go ahead with ! the renaming. Find out how big the file is, so that we can ! adjust the user's quota if the file's permanence changes. file == dir info_d_file(found from) x = file size(found from, dir info_d, size, z) %if x # success %start z = forget directory(dir info) %result = x %finish new q = dir info_d_header_quota left & h ! Restore quota if file was not temporary or transient new q = new q + size %c %if f1 >> 16 < temporary %and file_status & transient file = 0 ! Deduct quota if file will not be temporary new q = new q - size %if t1 >> 16 < temporary ! If the user's quota is insufficient to permit the renaming then ! fail the request. Otherwise finish the remaining processing. %if new q < 0 %start z = forget directory(dir info) err d = dir info_d_header_owner err n1 = t1; err n2 = t2 %result = no quota & xs error %finish dir info_d_header_quota left = new q log access(Uno info, dir info_d_header_owner, file, "Rename") file_name1 = t1 file_name2 = t2 file_status = file_status & (\transient file) dir info_written = 1 %result = forget directory(dir info) %end %externalintegerfn FS dchange(%integer Uno, %string(255) filename, %string(255) date and time) ! Change the timestamp associated with a file %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %record(entry fm)%name file %integer n1, n2, user, blank %integer x, z, slot, part, entry %short packed date, packed time %string(255) user name, date, time %result = not writeable %if file system writeable = 0 x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) ! First split the date and time into its component parts date and time = date . " " . time %c %while date and time -> date .(" "). time %result = param error & p2 error %c %unless date and time -> date .(" "). time ! Pack the date and time and then repack them. If we get what we ! started with, then they were OK, otherwise there was an error. packed date = pack date(date) %result = param error & p2 error %unless date = unpack date(packed date) packed time = pack time(time) %result = param error & p2 error %unless time = unpack time(packed time) ! Date and time OK, now find the file %if filename -> user name . (":") . filename %start ! Username was supplied. First check that a filename was supplied. %result = param error & p1 error %if filename = "" ! OK, so validate username and get its corresponding directory. %result = param error & p1 error %if username = "" x = pack(user name, user, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(user, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = user %result = x & xs error %finish x = get directory(user, part, entry) %result = x %if x < success dir info == common_dir info(x) ! If it isn't the directory we logged on to, then we must ! check the password. %if dir info ## Uno info_l directory %start x = check password(Uno info_q pass, dir info_d) z = forget directory(dir info) %and %result = x %if x # success %finish %else ! No directory given, so use the default. dir info == Uno info_d directory %if dir info ## Uno info_l directory %start ! Not our logon directory -- check the password x = check password(Uno info_q pass, dir info_d) %result = x %if x # success %finish remember directory(dir info) %finish ! Lose the '!' from the filename, if there is one, then see if ! it is valid. %if charno(filename, length(filename)) = '!' %start length(filename) = length(filename) - 1 x = forget directory(dir info) %and %result = param error & p1 error %c %if filename = "" %finish x = pack(filename, n1, n2) z = forget directory(dir info) %and %result = x & p1 error %if x # success ! Filename is valid, but does it exist? slot = find file(dir info_d, n1, n2, 0) %if slot < success %start x = forget directory(dir info) err d = dir info_d_header_owner err n1 = n1; err n2 = n2 %result = slot & xs error %finish ! File exists, so set its timestamp. file == dir info_d_file(slot) file_date = packed date file_time = file_time & (\ time mask) ! packed time log access(Uno info, dir info_d_header_owner, file, "DChange") %result = forget directory(dir info) %end %integerfn set perms(%string(255) permissions, %shortname mask) ! Convert permissions from textual form to a mask. Either ! only the permissions are specified ! or only the archive status is specified ! or both are specified. ! Full access for owner allows read/write/delete ! Full access for world allows read/write but not delete ! Note that file creation required owner authority for the ! directory, and hence non-owners may (DA) modify files in ! a directory but not (SQ) overwrite them. %integer owner, public, archive, log, l, ch l = length(permissions) %result = param error %unless 0 < l <= 3 ! Obtain current values from mask owner = mask >> owner permission shift & 3 public = mask >> public permission shift & 3 archive = mask >> archive bit shift & 1 log = mask >> log bit shift & 1 %if l >= 2 %start ! Permissions must have been specified ch = charno(permissions, 1) %if ch = 'F' %or ch = 'f' %start owner = 3 %else %if ch = 'R' %or ch = 'r' owner = 2 ! %else %if ch = 'O' %or ch = 'o' ! owner = 1 %else %if ch = 'N' %or ch = 'n' owner = 0 %else %result = param error %finish ch = charno(permissions, 2) %if ch = 'F' %or ch = 'f' %start public = 3 %else %if ch = 'R' %or ch = 'r' public = 2 ! %else %if ch = 'O' %or ch = 'o' ! public = 1 %else %if ch = 'N' %or ch = 'n' public = 0 %else %result = param error %finish owner = public %if owner < public ! Get the archive status, if it has been specified ch = charno(permissions, 3) %if l = 3 %else ! Only the archive status specified ch = charno(permissions, 1) %finish %if l & 1 # 0 %start ! Archive status has been specified %if ch = 'A' %or ch = 'a' %start archive = 1 %else %if ch = 'V' %or ch = 'v' archive = 0 %else %if ch = 'L' %or ch = 'l' log = 1 %else %if ch = 'Q' %or ch = 'q' log = 0 %else %result = param error %finish %finish ! All relevant information extracted, so set the new values mask <- mask & (\ permissions mask) %c ! owner << owner permission shift %c ! public << public permission shift %c ! archive << archive bit shift %c ! log << log bit shift %result = success %end %externalintegerfn FS permit(%integer Uno, %string(255) filename, permissions) ! Set access on file, or set default access %string(255) owner name %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %record(entry fm)%name file %integer x, z, part, entry, slot %integer n1, n2, owner, blank %result = not writeable %if file system writeable = 0 x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) ! Split off owner name from filename, if possible owner name = "" %unless filename -> owner name . (":") . filename %if owner name = "" %start ! No owner name given, so use default dir info == Uno info_d directory %if dir info ## Uno info_l directory %start ! Must check password, since not logon directory x = check password(Uno info_q pass, dir info_d) %result = x %if x # success %finish remember directory(dir info) %else ! Ownername supplied, so validate it and get its directory x = pack(owner name, owner, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(owner, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = owner %result = x & xs error %finish x = get directory(owner, part, entry) %result = x %if x < success dir info == common_dir info(x) ! Must check password if not logon directory %if dir info ## Uno info_l directory %start x = check password(Uno info_q pass, dir info_d) z = forget directory(dir info) %and %result = x %if x # success %finish %finish %if filename = "" %start ! Set default access (could be to make a pseudo-user) %if permissions = "P" %or permissions = "G" %start ! (Un)make a pseudo-user %result = no authority %unless Uno info_q pass = common_system pass pdate printstring(unpack(Uno info_l owner, 0)); printstring(" at ") print client address(ether context); printstring(" setting ") printstring(unpack(dir info_d_header_owner, 0)) printstring(" as a ") %if permissions = "P" %start dir info_d_header_perms = dir info_d_header_perms ! pseudo user printstring("pseudo-") %else dir info_d_header_perms = dir info_d_header_perms & (\ pseudo user) printstring("real ") %finish printstring("user"); newline dir info_written = 1 x = forget directory(dir info) %result = success %finish x = set perms(permissions, dir info_d_header_perms) x = forget directory(dir info) %and %result = param error & p2 error %c %if x # success %else ! Set file access -- first find if the file exists. x = pack(filename, n1, n2) z = forget directory(dir info) %and %result = x & p1 error %c %if x # success slot = find file(dir info_d, n1, n2, transient file) %if slot < success %start x = forget directory(dir info) err d = dir info_d_header_owner err n1 = n1; err n2 = n2 %result = slot & xs error %finish file == dir info_d_file(slot) ! File exists, so change its protection. We have already ! checked that we have the requisite owner authority. x = set perms(permissions, file_perms) x = forget directory(dir info) %and %result = param error & p2 error %c %if x # success %finish dir info_written = 1 x = forget directory(dir info) %result = success %end %externalintegerfn FS finfo(%integer Uno, %string(255) ownername, %integer file number, %integername bytes, %record(buffer fm)%name b) ! Information on a selected file in a directory. Only files to which ! the caller has sufficient authority will be included in the list. %record(directory fm)%name directory %record(header fm)%name header %record(dir info fm)%name dir info %record(Uno info fm)%name Uno info %record(extent fm)%name e %record(entry fm)%name f %integer owner, blank %integer part, entry, x, d, i, extents, blocks, files, oa, which %short date, time x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if ownername # "" %start ! Owner name supplied. See if it exists. x = pack(ownername, owner, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(owner, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = owner %result = x & xs error %finish d = get directory(owner, part, entry) %result = d %if d < success dir info == common_dir info(d) %else ! No owner, so use current default owner = Uno info_d owner dir info == Uno info_d directory remember directory(dir info) %finish directory == dir info_d ! Determine ownership authority wrt directory %if dir info == Uno info_l directory %start ! Logon directory, so must be owner oa = success %else ! Not logon directory, so must check password oa = check password(Uno info_q pass, directory) %finish b_bytes = 0 header == directory_header files = header_files & files mask %if file number = 0 %start ! Enquiring about general directory information, so assemble it time stamp(date, time) add text(unpack(header_owner, 0), b) add text(" (", b) add text(itos(dir info_partition, 0), b) add text(".", b) add text(itos(dir info_user no, 0), b) add text(") at ", b) add text(unpack time(time), b) add text(" on ", b) add text(unpack date(date), b) add text(" -- files: ", b) extents = 0; blocks = 0 %if files # 0 %start %for i = extent limit, -1, directory_file(files)_extents %cycle e == directory_extent(i) extents = extents + 1 blocks = blocks + (e_size & h) %repeat %finish add text(itos(files, 0), b) add text(", extents: ", b) add text(itos(extents, 0), b) add text(", blocks: ", b) add text(itos(blocks, 0), b) add text("/", b) add text(itos(header_quota left & h + blocks, 0), b) %else %if 0 < file number <= files ! Enquiring about a specific file in the directory. The slot ! number specifies which file is required. Note that we only ! count accessible files for this purpose, and that the most ! recent file is deemeed to be in slot 1 -- this means that ! we have to start our scan at the bottom end if the directory's ! file list, since files are added to the %end of the list. which = files %while which > 0 %and file number > 0 %cycle ! Look for file in accessible slots f == directory_file(which) %if oa = success %start ! Owner of directory, so count all the files in it file number = file number - 1 which = which - 1 %else ! Not the owner, so count only those with public access file number = file number - 1 %c %if f_perms >> public permission shift & 3 >= R permission which = which - 1 %finish %repeat ! We've either found the file or run out of slots %if which = 0 %and file number # 0 %start ! Run out of slots. Return an empty packet b_bytes = 0 %else ! Found the file, so assemble the details add text(unpack(f_name1, f_name2), b) add text("?", b) %if f_status & dud file # 0 add text("!", b) %if f_status & transient file # 0 pad(16, b) add text(unpack perms(f_perms), b) add text(" ", b) add text(unpack date(f_date), b) add text(" ", b) add text(unpack time(f_time & time mask), b) x = file size(which + 1, directory, blocks, extents) i = forget directory(dir info) %and %result = x %if x # success add text(" ", b) add text(itos(blocks, 0), b) add text("(", b) add text(itos(extents, 0), b) add text(")", b) %finish %else ! Requested slot is outwith the number of files in the directory, ! so we don't even need to bother looking. b_bytes = 0 %finish i = forget directory(dir info) bytes = b_bytes %result = success %end %externalintegerfn FS ninfo(%integer Uno, %string(255) filename, %integername bytes, %record(buffer fm)%name b) %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %record(directory fm)%name directory %record(entry fm)%name file %integer n1, n2, x, owner, part, entry, it, blocks, extents, pling %string(255) username x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if filename -> username . (":") . filename %start x = pack(username, owner, n2) %result = param error & p1 error %if x # success %or n2 # 0 x = find owner(owner, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = owner %result = x & xs error %finish x = get directory(owner, part, entry) %result = x %if x < success dir info == common_dir info(x) %else %result = not logged on %if Uno = 0 dir info == Uno info_d directory remember directory(dir info) %finish %if filename = "" %or filename = "!" %start x = forget directory(dir info) %result = param error & p1 error %finish %if charno(filename, length(filename)) = '!' %start length(filename) = length(filename) - 1 pling = 0 %else pling = transient file %finish x = pack(filename, n1, n2) %if x # success %start x = forget directory(dir info) %result = param error & p1 error %finish directory == dir info_d it = find file(directory, n1, n2, pling) %if it < success %start x = forget directory(dir info) %result = it & xs error %finish file == directory_file(it) b_bytes = 0 add text(unpack(n1, n2), b) add text("?", b) %if file_status & dud file # 0 add text("!", b) %if file_status & transient file # 0 pad(16, b) add text(unpack perms(file_perms), b) add text(" ", b) add text(unpack date(file_date), b) add text(" ", b) add text(unpack time(file_time & time mask), b) x = file size(it, directory, blocks, extents) it = forget directory(dir info) %and %result = x %if x # success add text(" ", b) add text(itos(blocks, 0), b) add text("(", b) add text(itos(extents, 0), b) add text(")", b) bytes = b_bytes x = forget directory(dir info) %result = success %end %externalintegerfn FS pass(%integer Uno, %string(255) password, username) ! Set a user's password. Note that we change the password ! in the logon directory (hence guaranteeing that the ! caller has sufficient authority), %not the default directory. ! If the username is specified we change the password for that user. %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %record(directory fm)%name directory %record(header fm)%name header %integer x, user, blank, part, entry %result = not writeable %if file system writeable = 0 x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) ! Uno OK, so encrypt the password and change it in the ! logon directory. %if username = "" %start ! Null username, change own password dir info == Uno info_l directory remember directory(dir info) %else ! Someone else's password %result = no authority %unless Uno info_q pass = common_system pass x = pack(username, user, blank) %result = param error & p2 error %if x # success x = find owner(user, part, entry) %if x # success %start err d = user err n1 = 0; err n2 = 0 err pling = 0 %result = owner not found & xs error %finish x = get directory(user, part, entry) %result = x %if x < success dir info == common_dir info(x) pdate printstring(unpack(Uno info_l owner, 0)) printstring(" changing ") printstring(unpack(user, 0)) printstring("'s password") newline %finish directory == dir info_d header == directory_header !! password = endecrypt(unpack(Uno info_l owner, 0), password, 0) header_password = encrypt(password) dir info_written = 1 %result = forget directory(dir info) %end %externalintegerfn FS quote(%integer Uno, %string(255) password) ! Quote a password. %record(Uno info fm)%name Uno info %integer x, pass x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) !! password = endecrypt(unpack(Uno info_l owner, 0), password, 0) pass = encrypt(password) Uno info_q pass = pass %if pass = common_system pass %start pdate printstring(unpack(Uno info_l owner, 0)) printstring(" at "); print client address(ether context) printstring(" quoting system pass") newline %finish %result = success %end %externalintegerfn FS setdir(%integer Uno, %string(255) ownername) ! Set a new default directory. If none was specified then ! reset to the logon directory. %record(Uno info fm)%name Uno info %integer x, i, part = -1, entry %integer owner, blank x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) %if ownername = "" %start ! Resetting to logon directory x = forget directory(Uno info_d directory) Uno info_d directory == Uno info_l directory Uno info_d owner = Uno info_l owner remember directory(Uno info_l directory) %result = success %finish ! Setting to somewhere else. First find out if it exists. x = pack(ownername, owner, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(owner, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = owner %and %result = x & xs error %finish Uno info == common_Uno(Uno) x = get directory(owner, part, entry) %result = x %if x < success ! Got the new default. Forget the old one and set the new one. i = forget directory(Uno info_d directory) Uno info_d directory == common_dir info(x) Uno info_d owner = owner %result = success %end %integerfn special file(%integer Uno, %string(255) which special, %integername Xno, block count, pad count) ! Implements the pseudo-files "owned" by "user" $. %record(special fm)%name sb %record(port fm)%name port info %record(Uno info fm)%name Uno info, Uno X %record(Xno info fm)%name Xno info, Xno X %record(dir info fm)%name dir info %record(header fm)%name header %record(entry fm)%name file %string(255) s %integer i, x, bm upper case(which special) Uno info == common_Uno(Uno) Xno = allocate Xno %result = no Xno %if Xno < 0 Xno info == common_Xno(Xno) ! The first few pseudo-files already exist in store, as they are ! merely the file system's internal tables. %if which special = "TRACE" %start ! Read the trace buffer Xno info_Uno = Uno Xno info_special buffer = -1 Xno info_next to send == byteinteger(addr(common_trace)) Xno info_file slot = -1 Xno info_dir info == nil Xno info_forget == nil Xno info_flags = Xno read x = 64 * (tbuffs + 1) + 8 block count = x >> 9 + 1 Xno info_blocks to go = block count Xno info_bytes = rem(x, 512) pad count = 512 - Xno info_bytes %result = success %else %if which special = "BADLIST" ! Read the bad block list %result = disc error %if bad block list == nil Xno info_Uno = Uno Xno info_special buffer = -1 Xno info_next to send == byteinteger(addr(bad block list)) Xno info_file slot = -1 Xno info_dir info == nil Xno info_forget == nil Xno info_flags = Xno read Xno info_blocks to go = 4; block count = 4 Xno info_bytes = 512; pad count = 0 %result = success %else %if which special -> ("BITMAP.") . which special ! Read a bitmap %result = param error %if length(which special) # 1 bm = charno(which special, 1) - '0' %result = param error %unless 0 <= bm <= last partition Xno info_Uno = Uno Xno info_special buffer = -1 Xno info_next to send == common_partition(bm)_bitmap_b(0) Xno info_file slot = -1 Xno info_dir info == nil Xno info_forget == nil Xno info_flags = Xno read x = bitmap size block count = x >> 9 + 1 Xno info_blocks to go = block count Xno info_bytes = rem(x, 512) pad count = 512 - Xno info_bytes %result = success %finish ! The remainder require that we allocate a special buffer into ! which we will write the contents of our pseudo-file. x = allocate special Xno info_Uno = -1 %and %result = x %if x < success ! Got a special buffer. Set up the Xno to use it. sb == common_specials(x) Xno info_Uno = Uno Xno info_special buffer = x Xno info_next to send == common_specials(x)_b(1) Xno info_file slot = -1 Xno info_dir info == nil Xno info_forget == nil Xno info_flags = Xno read ! Now decide which special file is required %if which special = "UNOS" %start ! Format the Uno table %for i = 0, 1, Unos %cycle Uno X == common_Uno(i) %if Uno X_l owner # 0 %start add special(sb, itos(i, 2)) add special(sb, itos(Uno X_context, 3)) %if 0 < Uno X_context <= ports %start port info == common_port info(Uno X_context) spaces special(sb, 2) add special(sb, itox2(port info_remote)) add special(sb, itos(port info_port, 2)) %else spaces special(sb, 7) %finish s = unpack(Uno X_l owner, 0) spaces special(sb, 7 - length(s)); add special(sb, s) %if Uno X_q pass = common_system pass %start add special(sb, "!") %else spaces special(sb, 1) %finish s = unpack(Uno X_d owner, 0) spaces special(sb, 7 - length(s)); add special(sb, s) spaces special(sb, 2) add special(sb, show stamp(Uno X_logon stamp)) spaces special(sb, 2) add special(sb, show stamp(Uno X_active stamp)) spaces special(sb, 2) add special(sb, show stamp(Uno X_Xno active stamp)) add special(sb, snl) %finish %repeat %else %if which special = "XNOS" ! Format the Xno table %for i = 1, 1, Xnos %cycle Xno X == common_Xno(i) %if Xno X_Uno >= 0 %start add special(sb, itos(i, 2)) add special(sb, itos(Xno X_Uno, 2)) add special(sb, itos(Xno X_context, 2)) spaces special(sb, 1) %if Xno X_file slot <= 0 %start add special(sb, "Special file") spaces special(sb, 32) %else x = 20 dir info == Xno X_dir info s = unpack(dir info_owner, 0) add special(sb, s); x = x - length(s) add special(sb, ":") file == dir info_d_file(Xno X_file slot) s = unpack(file_name1, file_name2) add special(sb, s); x = x - length(s) add special(sb, "?") %and x = x - 1 %c %if file_status & dud file # 0 add special(sb, "!") %and x = x - 1 %c %if file_status & transient file # 0 spaces special(sb, x) add special(sb, itos(Xno X_next file block, 4)) %if Xno X_next extent block <= h %start add special(sb, itos(Xno X_next extent block, 4)) %else add special(sb, " ...") %finish add special(sb, itos(Xno X_next disc block, 5)) add special(sb, itos(Xno X_extent no + 1, 2)) add special(sb, "/") add special(sb, itos(Xno X_extents, 2)) %finish add special(sb, itos(Xno X_blocks, -6)) spaces special(sb, 2) add special(sb, show stamp(Xno X_active stamp)) spaces special(sb, 1) add special(sb, "R") %if Xno X_flags & Xno read # 0 add special(sb, "W") %if Xno X_flags & Xno write # 0 add special(sb, "L") %if Xno X_flags & Xno last # 0 add special(sb, snl) %finish %repeat %else %if which special = "DIRECTORIES" ! Format the directory cache table %for i = 1, 1, dirs %cycle dir info == common_dir info(i) %if dir info_owner # 0 %start add special(sb, itos(i, 3)) spaces special(sb, 3) s = unpack(dir info_owner, 0) add special(sb, s); spaces special(sb, 8 - length(s)) add special(sb, "(") add special(sb, itos(dir info_partition, -1)) add special(sb, ".") add special(sb, itos(dir info_user no, -3)) add special(sb, ")") add special(sb, itos(dir info_ref count, 4)) add special(sb, itos(dir info_stamp, 9)) add special(sb, " ** written **") %if dir info_written # 0 add special(sb, snl) %finish %repeat %else %if which special = "PORTS" ! Format the ether port table %for i = 1, 1, ports %cycle port info == common_port info(i) %if port info_state # 0 %start add special(sb, itos(i, 2)) spaces special(sb, 2) add special(sb, itox2(port info_remote)) add special(sb, itos(port info_port, 2)) spaces special(sb, 2) add special(sb, show stamp(port info_opened stamp)) spaces special(sb, 2) add special(sb, show stamp(port info_active stamp)) add special(sb, snl) %finish %repeat %else ! Unknown, so free the special buffer and the Xno free special(x) Xno info_Uno = -1 %result = not implemented %finish ! Set block count and pad count for the resultant "file" block count = sb_bytes >> 9 + 1 Xno info_blocks to go = block count Xno info_bytes = rem(sb_bytes, 512) pad count = 512 - Xno info_bytes %result = success %end %integerfn special directory(%integer Uno, %record(dir info fm)%name dir info, %integer owner, %integername Xno, block count, pad count) ! Implements the special file "directory", providing a list of the ! (accessible) files in the given directory, one file per line. %record(Uno info fm)%name Uno info %record(Xno info fm)%name Xno info %record(special fm)%name sb %record(entry fm)%name file %integer x, z, i Uno info == common_Uno(Uno) Xno = allocate Xno z = forget directory(dir info) %and %result = Xno %if Xno < success Xno info == common_Xno(Xno) ! Got an Xno. Try for a special buffer. x = allocate special %if x < success %start z = forget directory(dir info) Xno info_Uno = -1 %result = x %finish ! Set up the Xno to use our special buffer sb == common_specials(x) Xno info_Uno = Uno Xno info_special buffer = x Xno info_next to send == common_specials(x)_b(1) Xno info_file slot = -1 Xno info_dir info == nil Xno info_forget == dir info Xno info_flags = Xno read ! Now cycle round all the files in the directory, adding them if ! the caller is the owner of the directory or if the files ! are not protected against the world. %for i = dir info_d_header_files & files mask, -1, 1 %cycle file == dir info_d_file(i) %if owner = success %c %or file_perms >> public permission shift & 3 %c >= R permission %start add special(sb, unpack(file_name1, file_name2)) add special(sb, "?") %if file_status & dud file # 0 add special(sb, "!") %if file_status & transient file # 0 add special(sb, snl) %finish %repeat ! Finally, set the block and pad count for the "file" block count = sb_bytes >> 9 + 1 Xno info_blocks to go = block count Xno info_bytes = rem(sb_bytes, 512) pad count = 512 - Xno info_bytes %result = success %end %externalintegerfn FS openr(%integer Uno, %string(255) filename, %integername Xno, block count, pad count) ! Open file for reading (writing not allowed). %string(255) user name %record(Uno info fm)%name Uno info %record(Xno info fm)%name Xno info %record(dir info fm)%name dir info %record(directory fm)%name directory %record(header fm)%name header %record(entry fm)%name file %integer file index, x, z, part, entry, i, p extents, owner %integer p1, p2, d1, blank !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("OpenR "); printstring(filename) !D newline !D %finish !%result = param error & p1 error %if filename = "" x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if filename -> user name . (":") . filename %start ! File is not in default directory !%result = param error & p1 error %if filename = "" %if user name = "$" %start ! Special file wanted %result = special file(Uno, filename, Xno, block count, pad count) %finish %result = param error & p1 error %if username = "" ! Find out if the specified owner exists x = pack(user name, d1, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(d1, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = d1 %result = x & xs error %finish x = get directory(d1, part, entry) %result = x %if x < success dir info == common_dir info(x) %else ! Use the default directory. Note that we don't allow ! ANON to read files from its default directory. %result = not logged on %if Uno = 0 dir info == Uno info_d directory remember directory(dir info) %finish directory == dir info_d %if dir info == Uno info_l directory %start ! Logon directory, so must have owner authority owner = success %else ! Not logon directory, so check the password owner = check password(Uno info_q pass, directory) %finish upper case(filename) %result = special directory(Uno, dir info, owner, Xno, block count, pad count) %c %if filename = "" %or filename = "." %or filename = "DIRECTORY" ! Now have a look for the file in the directory x = pack(filename, p1, p2) z = forget directory(dir info) %and %result = x & p1 error %if x # success file index = find file(directory, p1, p2, transient file) %if file index < 0 %start ! Not there err d = directory_header_owner z = forget directory(dir info) err n1 = p1; err n2 = p2 %result = file not found & xs error %finish ! Found it. Does the caller have read authority? Quoting the ! system password will do too. file == directory_file(file index) %unless (owner = success %and %c (file_perms >> owner permission shift) & 3 >= R permission) %c %or (owner # success %and %c (file_perms >> public permission shift) & 3 >= R permission) %c %or Uno info_q pass = common_system pass %c %start ! No. err d = directory_header_owner z = forget directory(dir info) err n1 = p1; err n2 = p2 %result = file not found & xs error %finish ! Authority OK, check for conflicts x = check conflicts(directory_header_owner, p1, p2, 0, Xno write) z = forget directory(dir info) %and %result = x %if x # success log access(Uno info, directory_header_owner, file, "OpenR") ! Set up the Xno Xno = allocate Xno %if Xno < 0 %start z = forget directory(dir info) %result = no Xno %finish Xno info == common_Xno(Xno) Xno info_next file block = 1; ! Start at the beginning Xno info_next extent block = infinity - 1; ! Force read of first extent Xno info_flags = Xno read; ! Read only Xno info_dir info == dir info Xno info_file slot = file index Xno info_bytes = file_bytes & bytes mask; ! Extract bytes in last block Xno info_bytes = 512 %if Xno info_bytes = 0; ! 0 means a full block header == directory_header ! Now we have to find out how big the file is. First find where ! its extents are in the directory. %if file index = 1 %start Xno info_extents = extent limit + 1 - file_extents p extents = extent limit + 1 %else Xno info_extents = directory_file(file index - 1)_extents %c - file_extents p extents = directory_file(file index - 1)_extents %finish Xno info_extent no = Xno info_extents; ! Start at highest (first) Xno info_extent = 0; ! Zap the Xno extent record Xno info_next disc block = 0; ! and the disc address Xno info_Uno = Uno; ! Attach Xno to its Uno ! Now find out how big the file is. block count = 0 i = file_extents %while i < p extents %cycle block count = block count + (directory_extent(i)_size & h) i = i + 1 %repeat ! Got all the information, so set the block and pad counts ! and then we're all done. pad count = 512 - Xno info_bytes Xno info_blocks = block count %result = success %end %integerfn uniquify(%string(255) filename, %record(directory fm)%name d, %integername name1, name2) ! Create a filename which is guaranteed to be unique in the ! directory. We do this by prepending (?) a special string whose ! first three characters are guaranteed not to appear as the first ! three characters in any other filename in the directory. Note that ! we must preserve the tail of the supplied filename, since the ! laser printer despooler will want to know about the filename's ! extension so as to be able to handle it correctly. %string(255) it %integer i, files, x %label next one !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Uniquify "); printstring(filename) !D newline !D %finish %result = param error %if length(filename) > 12 ! If the given filename is too long then take only the tail end of it. filename = substring(filename, length(filename) - 8, length(filename)) %c %if length(filename) > 9 ! Now construct a first guess at the uniquified filename it = "AAA........." length(it) = 12 - length(filename) it = it . filename x = pack(it, name1, name2) %result = x %if x # success !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring(it); printstring(" packed as ") !D phex(name1); space !D phex(name2); newline !D %finish ! First guess is a valid filename, so scan the directory looking ! for the initial three-letter sequence. If we find it then we ! twiddle the guess and try again. files = d_header_files & files mask next one: %for i = 1, 1, files %cycle %if d_file(i)_name1 = name1 %start ! Sequence already exists, so try another one. name1 = name1 + 16_10000 -> next one %finish %repeat %result = success %end %externalintegerfn FS openw(%integer Uno, %string(255) filename, %integer block count, %integername Xno) ! Open a file for writing (reading not allowed). The file is ! created in its '!' form (if it doesn't already exist as such). ! When the file is closed it will be truncated to the length ! it last had (resetting may shorten it). Note that the caller ! must have owner authority wrt the directory into which the file ! is to be written. %record(directory fm)%name dir %record(header fm)%name header %record(entry fm)%name file %record(extent fm)%name extent %record(dir info fm)%name dir info %record(Uno info fm)%name Uno info %record(Xno info fm)%name Xno info %integer n1, n2, user, blank %integer x, z, files, last extent, part, entry, a start, a size, perms %integer blocks, extents %short date, time %string(255) user name %result = not writeable %if file system writeable = 0 !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("OpenW "); printstring(filename) !D printstring(", "); write(block count, 0) !D newline !D %finish x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if filename -> user name . (":") . filename %start ! Owner name explicitly given, so see if it exists. %result = param error & p1 error %if username = "" %result = not implemented %if username = "$"; ! Specials x = pack(user name, user, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(user, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = user %result = x & xs error %finish x = get directory(user, part, entry) %result = x %if x < success dir info == common_dir info(x) ! Owner exists, so check whether caller has owner authority ! wrt directory. If it was the logon directory then that ! follows by definition, otherwise we must check the password. %if dir info ## Uno info_l directory %start x = check password(Uno info_q pass, dir info_d) z = forget directory(dir info) %and %result = x %if x # success %finish %else ! No owner supplied, so use default %result = not logged on %if Uno = 0 dir info == Uno info_d directory %if dir info ## Uno info_l directory %start ! Not logon directory, so check password x = check password(Uno info_q pass, dir info_d) %result = x %if x # success %finish remember directory(dir info) %finish dir == dir info_d header == dir_header files = header_files & files mask ! Convert ASCII form of filename to internal packed form, at the ! same time performing any required uniquification. %if filename = "" %or filename = "!" %start x = uniquify("", dir, n1, n2) %else %if charno(filename, 1) = '!' x = uniquify(substring(filename, 2, length(filename)), dir, n1, n2) %else x = pack(filename, n1, n2) %finish z = forget directory(dir info) %and %result = x & p1 error %if x # success ! Check that we aren't conflicting with someone else. x = check conflicts(header_owner, n1, n2, 1, Xno write) z = forget directory(dir info) %and %result = x %if x # success ! Find whether the file exists. If it does, then it must be ! in its non-'!' form, and must have full access permission to ! the owner (thus it is possible to protect files against ! accidental overwriting. x = find file(dir info_d, n1, n2, 0) %if x > success %start ! File exists. Make sure it isn't a '!'-file, and that there ! is full access to owner. file == dir info_d_file(x) %if file_status & transient file # 0 %start ! '!' form already exists z = forget directory(dir info) err d = dir info_d_header_owner err n1 = n1; err n2 = n2 err pling = 1 %result = file exists %else %unless file_perms >> owner permission shift & 3 = F permission ! No authority to overwrite existing version z = forget directory(dir info) %result = no authority %finish ! File already exists, so inherit access mask and size perms = file_perms & permissions mask z = file size(x, dir info_d, blocks, extents) block count = blocks + default allocation %if block count = 0 %else ! File is new, so use default access mask perms = header_perms & permissions mask %finish !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Permissions will be ") !D phex(perms) !D newline !D %finish ! Default the initial allocation if none was supplied block count = default allocation %if block count <= 0 ! Check user's quota before allowing initial allocation %if block count > dir info_d_header_quota left & h %start z = forget directory(dir info) err d = dir info_d_header_owner err n1 = n1; err n2 = n2 %result = no quota & xs error %finish Xno = allocate Xno z = forget directory(dir info) %and %result = no Xno %if Xno < 0 Xno info == common_Xno(Xno) %if files = 0 %start ! Directory is currently empty files = 1 last extent = extent limit + 1 %else ! Directory has file in it, so make sure there is a free ! slot for the file and at least one extent. Each file header ! requires space equivalent to 4 extents in the directory. We ! must allow 4 for each file which exists already, 4 for the file ! we are about to create, 4 for the directory header and 1 for the ! first extent of the new file. file == dir_file(files) last extent = file_extents %if files * 4 + 4 + 4 + 1 >= last extent %start ! Files and extents would overlap. Xno info_Uno = -1; ! Free the Xno err d = dir info_d_header_owner err n1 = n1; err n2 = n2 err pling = file_status & transient file z = forget directory(dir info) %result = no slot & xs error %finish ! Space for the file, so bump the count for the directory files = files + 1 %finish time stamp(date, time) file == dir_file(files) ! Allocate the first extent in the file file_extents = last extent - 1 allocate extent(block count, common_partition(dir info_partition)_bitmap_b(0), a size, a start) %if a start < 0 %start ! We've run out of space on the disc, so bounce the request Xno info_Uno = -1 %result = disc full %finish !D %if common_diags & fsys diags # 0 %start !D pdate !D write(a size, 0); printstring(" allocated at ") !D write(a start, 0) !D newline !D %finish ! Set up extent pointer in directory extent == dir_extent(file_extents) extent_start <- a start extent_size = a size ! Set up file header in directory file_name1 = n1 file_name2 = n2 file_perms = perms file_status = time ! transient file file_date = date ! Bump file count in header, remembering to preserve the default ! protection in the top bits. header_files = header_files + 1 log access(Uno info, dir_header_owner, file, "OpenW") ! Set up the Xno Xno info_file slot = files Xno info_Uno = Uno Xno info_dir info == dir info Xno info_next file block = 1; ! Start at first block Xno info_next extent block = -1; ! To start in new extent Xno info_next disc block = a start - 1; ! To start in new extent Xno info_extent = extent; ! Copy from directory Xno info_extent no = 0; ! First extent Xno info_extents = 0; ! Only extent Xno info_flags = Xno write; ! Write only Xno info_bytes = 0; ! Assume last block full Xno info_blocks = 0; ! None yet dir info_written = 1 %result = success %end %integerfn open boot(%integer Uno, %integername Xno) ! Open the boot area as a special file %record(Uno info fm)%name Uno info %record(Xno info fm)%name Xno info %if common_diags & fsys diags # 0 %start pdate printstring("Open boot") newline %finish Uno info == common_Uno(Uno) %result = no authority %unless Uno info_q pass = common_system pass %c %or Uno info_l owner = GDMR %c %or Uno info_l owner = RWT %c %or Uno info_l owner = SYSTEM Xno = allocate Xno %result = Xno %if Xno < success Xno info == common_Xno(Xno) Xno info = 0 Xno info_Uno = Uno Xno info_context = ether context Xno info_flags = Xno read ! Xno write Xno info_file slot = -2 %result = success %end %externalintegerfn FS openmod(%integer Uno, %string(255) filename, %integername Xno, block count, pad count) ! Open file for both reading and writing. The file may be extended ! but will not be truncated. Non-owners may modify files provided ! they have full access to those files. %string(255) user name %record(Uno info fm)%name Uno info %record(Xno info fm)%name Xno info %record(dir info fm)%name dir info %record(directory fm)%name directory %record(header fm)%name header %record(entry fm)%name file %integer file index, x, z, part, entry, i, p extents, owner %integer p1, p2, d1, blank %short date, time %result = not writeable %if file system writeable = 0 %result = param error & p1 error %if filename = "" x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if filename -> user name . (":") . filename %start %result = param error & p1 error %if filename = "" ! Ownername provided, so check if it exists %result = param error & p1 error %if username = "" %if username = "$" %start %if filename = "BOOTAREA" %start block count = head size; pad count = 0 %result = open boot(Uno, Xno) %finish %result = not implemented %finish x = pack(user name, d1, blank) %result = param error & p1 error %if x # success %or blank # 0 x = find owner(d1, part, entry) %if x # success %start err n1 = 0; err n2 = 0; err pling = 0 err d = d1 %result = x & xs error %finish x = get directory(d1, part, entry) %result = x %if x < success dir info == common_dir info(x) %else ! No ownername provided, so use default %result = not logged on %if Uno = 0 dir info == Uno info_d directory remember directory(dir info) %finish directory == dir info_d ! Check for owner authority wrt directory %if dir info == Uno info_l directory %start owner = success %else owner = check password(Uno info_q pass, directory) %finish ! Now look for the file x = pack(filename, p1, p2) z = forget directory(dir info) %and %result = x & p1 error %if x # success file index = find file(directory, p1, p2, transient file) %if file index < 0 %start ! File doesn't exist err d = directory_header_owner z = forget directory(dir info) err n1 = p1; err n2 = p2 %result = file not found %finish file == directory_file(file index) ! Check authority -- must be full %unless (owner = success %and %c (file_perms >> owner permission shift) & 3 = F permission) %c %or (owner # success %and %c (file_perms >> public permission shift) & 3 = F permission) %c %start err d = directory_header_owner z = forget directory(dir info) err n1 = p1; err n2 = p2 %result = file not found %finish ! Authority OK, check for access conflicts x = check conflicts(directory_header_owner, p1, p2, 1, Xno read ! Xno write) z = forget directory(dir info) %and %result = x %if x # success log access(Uno info, directory_header_owner, file, "OpenMod") ! Get an Xno Xno = allocate Xno %if Xno < 0 %start z = forget directory(dir info) %result = no Xno %finish time stamp(date, time) ! File may be modified, so update its time stamp file_date = date file_time = file_time & (\ time mask) ! time dir info_written = 1 Xno info == common_Xno(Xno) Xno info_next file block = 1 Xno info_next extent block = infinity - 1; ! Force read of first extent Xno info_flags = Xno read ! Xno write; ! Allow reading and writing Xno info_dir info == dir info Xno info_file slot = file index Xno info_bytes = file_bytes & bytes mask; ! Bytes in last block Xno info_bytes = 512 %if Xno info_bytes = 0; ! 0 means a full block header == directory_header ! Now find out where the extents are in the directory %if file index = 1 %start Xno info_extents = extent limit + 1 - file_extents p extents = extent limit + 1 %else Xno info_extents = directory_file(file index - 1)_extents %c - file_extents p extents = directory_file(file index - 1)_extents %finish Xno info_extent no = Xno info_extents; ! First extent Xno info_extent = 0; ! Zap extent record Xno info_next disc block = 0; ! and disc address Xno info_Uno = Uno; ! Attach to Uno ! Now find out how big the file ie block count = 0 i = file_extents %while i < p extents %cycle block count = block count + (directory_extent(i)_size & h) i = i + 1 %repeat ! All done, set the block and pad counts pad count = 512 - Xno info_bytes Xno info_blocks = block count %result = success %end %externalintegerfn FS writesq(%integer Xno, %integer bytes, %record(buffer fm)%name buffer) ! Write the next block in the file. Bump the file size if the ! file was opened for writing only (not read/mod). %record(Xno info fm)%name Xno info %record(dir info fm)%name dir info %record(directory fm)%name d %record(header fm)%name header %record(extent fm)%name extent %record(entry fm)%name file %integer x, a start, a size, files, i %result = not writeable %if file system writeable = 0 %result = param error & p2 error %unless 0 <= bytes <= 512 x = validate Xno(Xno) %result = x %if x # success Xno info == common_Xno(Xno) %result = illegal operation %if Xno info_flags & Xno write = 0 %result = not implemented %if Xno info_file slot < 0; ! Special !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("WriteSQ:") !D write(Xno info_next file block, 1) !D write(Xno info_blocks, 1) !D %if 0 < bytes < 512 %start !D printstring(" (short ") !D write(bytes, 0) !D print symbol(')') !D %finish !D newline !D %finish %if Xno info_next file block = Xno info_blocks %start ! About to overwrite the last block, so set the last-block size. !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("About to overwrite last block, length was ") !D write(Xno info_bytes, 0) !D printstring(", becoming ") !D write(bytes, 0) !D newline !D %finish Xno info_bytes = bytes %else %if Xno info_next file block = Xno info_blocks + 1 ! Beyond already-existing data. Must be extending the file. Check ! that the last (short) block hasn't already been seen. %if 0 < Xno info_bytes < 512 %start ! Last block has alread gone !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Short block already gone:") !D write(Xno info_next file block, 1) !D write(Xno info_blocks, 1) !D write(Xno info_bytes, 1) !D newline !D %finish %result = protocol error %finish ! OK, so set last-block size Xno info_bytes = bytes %else ! In middle of file (as a result of having been reset). Ensure ! that the current block is a full 512 bytes, as short blocks are ! only allowed as the last in the file. %if 0 < bytes < 512 %start !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Short block in middle of file: ") !D write(Xno info_next file block, 1) !D write(Xno info_blocks, 1) !D write(Xno info_bytes, 1) !D newline !D %finish %result = protocol error %finish %finish dir info == Xno info_dir info ! Bump disc address and extent pointer. NOTE that until we're ! sure we've got the space we better be certain we decrement them ! again on errors. Xno info_next extent block = Xno info_next extent block + 1 Xno info_next disc block = Xno info_next disc block + 1 d == dir info_d header == d_header extent == Xno info_extent %if Xno info_next extent block >= (extent_size & h) %start ! Off the end of the current extent -- get another one !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("New extent required") !D newline !D %finish %if Xno info_extent no = 0 %start ! Already used last extent we had claimed, so we'll need to ! allocate another one files = header_files & files mask file == d_file(files) ! Make sure there's a slot in the directory for the new extent. ! 4 for each of the file headers, 4 for the directory header ! and 1 for the new extent itself. %if files * 4 + 4 + 1 > file_extents %start err d = header_owner err n1 = file_name1; err n2 = file_name2 err pling = file_status & transient file Xno info_next extent block = Xno info_next extent block - 1 Xno info_next disc block = Xno info_next disc block - 1 %result = no slot & xs error %finish allocate extent(default allocation, common_partition(dir info_partition)_bitmap_b(0), a size, a start) %if a start < 0 %start ! The disc was full, so we can't get more space Xno info_next extent block = Xno info_next extent block - 1 Xno info_next disc block = Xno info_next disc block - 1 %result = disc full %finish !D %if common_diags & fsys diags # 0 %start !D pdate !D write(a size, 0); printstring(" allocated at ") !D write(a start, 0) !D newline !D %finish ! Adjust quota if not '$' or '!' file file == d_file(Xno info_file slot) %if file_name1 >> 16 < temporary %c %and file_status & transient file = 0 %start %if header_quota left < a size %start err d = header_owner err n1 = file_name1; err n2 = file_name2 err pling = 0 Xno info_next extent block = Xno info_next extent block - 1 Xno info_next disc block = Xno info_next disc block - 1 %result = no quota & xs error %finish header_quota left = header_quota left - a size %finish %if (extent_start & h) + extent_size = a start %c %and extent_size + a size < 16384 %start ! The new extent is contiguous with the previous ! one, so we glue them together to save on ! directory slots !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Contiguous extents -- joining") !D newline !D %finish Xno info_next extent block = extent_size Xno info_next disc block = a start extent_size = extent_size + a size file == d_file(Xno info_file slot) extent == d_extent(file_extents) extent_size = extent_size + a size %else ! The new extent isn't contiguous with the previous one, ! so we can't join them together. We'll need a new slot ! in the directory, and may have to shuffle to get it. !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("New extent not contiguous") !D newline !D %finish file == d_file(Xno info_file slot) %if Xno info_file slot # files %start ! Not the last file in the directory, so we'll ! have to shuffle. !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Shuffle extents") !D newline !D %finish %for i = d_file(files)_extents, 1, file_extents - 1 %cycle d_extent(i - 1) = d_extent(i) %repeat ! Now adjust any file headers whose extents have moved %for i = Xno info_file slot + 1, 1, files %cycle d_file(i)_extents = d_file(i)_extents - 1 %repeat %finish ! Got our extent slot (the case of the last file in the ! directory comes for free). Now set it up and modify the ! Xno to use the new extent. file_extents = file_extents - 1 extent == d_extent(file_extents) extent_start <- a start extent_size = a size Xno info_next extent block = 0 Xno info_next disc block = a start Xno info_extent = extent Xno info_extent no = 0 Xno info_extents = Xno info_extents + 1 %finish dir info_written = 1 %else ! Already an extent allocated -- reuse it !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Reuse existing extent") !D newline !D %finish Xno info_extent no = Xno info_extent no - 1 Xno info_extent = %c d_extent(d_file(Xno info_file slot)_extents + Xno info_extent no) Xno info_next disc block = Xno info_extent_start & h Xno info_next extent block = 0 %finish %finish ! Extent pointers are now OK, so write the block out. x = write block(dir info_partition, Xno info_next disc block, buffer) %if x # success %start ! Block write failed. Mark the file as dubious file == d_file(Xno info_file slot) file_status = file_status ! dud file dir info_written = 1 ! Now complain and add it to a bad block list. pdate printstring("*** Bad block: partition ") write(dir info_partition, 0) printstring(", logical block ") write(Xno info_next disc block, 0) printstring(", file ") printstring(unpack(header_owner, 0)); print symbol(':') printstring(unpack(file_name1, file_name2)) print symbol('?') ;! %if file_status & dud file # 0 print symbol('!') %if file_status & transient file # 0 newline !REACH add to bad block list(p start(dir info_partition) %c !REACH + Xno info_next disc block) P reach add to bad block list(dir info_partition, Xno info_next disc block) %result = x %finish ! Bump the file size if the last write has extended the file x = Xno info_blocks + 1 Xno info_blocks = x %if x = Xno info_next file block Xno info_next file block = Xno info_next file block + 1 !O %if 0 < bytes < 512 %start !O { Last block } !O %if common_diags & fsys diags # 0 %start !O pdate !O printstring("Last block gone") !O newline !O %finish !O Xno info_bytes = bytes !O %finish %result = success %end %externalintegerfn FS reset(%integer Xno, block number) ! Reset next-block pointer. First block in file is 0. %record(Xno info fm)%name Xno info %record(dir info fm)%name dir info %record(entry fm)%name file %record(extent fm)%name extent %record(directory fm)%name d %record(special fm)%name special %integer x, e, e limit, slot, i, fb, eno %bytename qq !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Reset Xno "); write(Xno, 0) !D printstring(" to block "); write(block number, 0) !D newline !D %finish x = validate Xno(Xno) %result = x %if x # success Xno info == common_Xno(Xno) %if Xno info_file slot < 0 %start ! Special file %result = not implemented %c %unless 0 < Xno info_special buffer <= specials special == common_specials(Xno info_special buffer) i = special_bytes >> 9 %result = size error & p1 error %unless 0 <= block number <= i qq == special_b(1) Xno info_next to send == qq [512 * block number] Xno info_blocks to go = i - block number + 1 %result = success %finish %result = size error & p1 error %unless 0 <= block number <= Xno info_blocks ! RH condition is <= to allow one beyond end of file. The size is ! OK, so set the new next file block Xno info_next file block = block number + 1 dir info == Xno info_dir info d == dir info_d slot = Xno info_file slot file == d_file(slot) ! Find extents in directory e limit = file_extents %if slot = 1 %start e = extent limit %else e = d_file(slot - 1)_extents - 1 %finish ! Scan extents until we find which contains the desired block fb = 1; eno = e - d_file(slot)_extents %while e >= e limit %cycle extent == d_extent(e) %if block number < extent_size %start ! Block lies in current extent Xno info_next extent block = block number - 1 Xno info_next disc block = block number + (extent_start & h) - 1 Xno info_extent no = eno Xno info_extent = extent %if Xno info_flags & Xno read = 0 %start ! Write only, so reset file length Xno info_blocks = Xno info_next file block - 1 Xno info_bytes = 0; ! reset short block %finish !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("File reset:") !D write(Xno info_next file block, 1) !D write(Xno info_blocks, 1) !D write(Xno info_next extent block, 1) !D write(Xno info_next disc block, 1) !D write(eno, 1) !D newline !D %finish %result = success %finish ! Not in this extent, so take account of its size and then ! go round again for the next one. i = block number - extent_size fb = fb + extent_size eno = eno - 1 e = e - 1 block number = i %repeat ! Requested block was outwith extents. Must be one block beyond ! the end of the last one, so set up to force either an empty read ! or a new extent when writing. Xno info_extent no = 0; ! Current extent is last Xno info_extent = extent; ! Set extent data!!! Xno info_next extent block = infinity - 1; ! Way beyond end of file %result = success; ! (we hope) %end %integerfn close file(%integer Xno, mode) ! Close/Uclose a file ! mode = 0: UClose ! mode # 0: Close %record(Xno info fm)%name Xno info %record(Uno info fm)%name Uno info %record(dir info fm)%name dir info %record(extent fm)%name extent %record(entry fm)%name file, file x %record(header fm)%name header %integer x, old file slot, free, first to free, files, extents %integer f size, t size, i, new q, used, q restore %integer n1, n2 !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Close Xno "); write(Xno, 0) !D printstring(", mode "); write(mode, 0) !D printstring(", context "); write(ether context, 0) !D newline !D %finish x = validate Xno(Xno) %result = x %if x # success Xno info == common_Xno(Xno) %if Xno info_file slot < 0 %start ! Special file -- free the buffer !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Closing special file") !D newline !D %finish free special(Xno info_special buffer) %if Xno info_special buffer > 0 x = forget directory(Xno info_forget) %if Xno info_forget ## nil Xno info_Uno = -1 %result = success %finish ! Normal file, so we may have to delete it or un-'!' it and ! truncate it. Uno info == common_Uno(Xno info_Uno) dir info == Xno info_dir info header == dir info_d_header files = header_files & files mask file == dir info_d_file(Xno info_file slot) %if Xno info_flags & Xno write # 0 %start ! File must have been opened for writing %if {mode = 0 %and} Xno info_blocks = 0 %start ! No blocks written, so delete it !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("No blocks were written, deleting....") !D newline !D %finish Xno info_Uno = -1; ! Must forget Xno before deleting file x = delete file(Xno info_file slot, dir info) %result = forget directory(dir info) %else %if Xno info_next file block > Xno info_blocks ! File will require truncation q restore = 0 file_bytes = file_bytes & permissions mask ! Xno info_bytes ! Spare in this extent? extent == dir info_d_extent(file_extents + Xno info_extent no) used = Xno info_next extent block + 1 free = (extent_size & h) - used %if free > 0 %start ! Unused blocks in the current extent -- free them !D %if common_diags & fsys diags # 0 %start !D pdate !D write(free, 0) !D printstring(" spare in current extent") !D newline !D %finish free extent((extent_start & h) + used, free, common_partition(dir info_partition)_bitmap_b(0)) q restore = free %finish extent_size = used ! Now see if there are any more unused extents -- the file may ! have been reset to shorten it. extents = Xno info_extent no first to free = extents - 1 %if first to free >= 0 %start ! There are unused extents. Cycle round them, freeing ! the space they use %for i = 0, 1, first to free %cycle !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Free unused extent ") !D write(i, 0) !D newline !D %finish extent == dir info_d_extent(file_extents + i) free extent(extent_start, extent_size, common_partition(dir info_partition)_bitmap_b(0)) q restore = q restore + free %repeat ! Now remove the unused extents from the directory %if Xno info_file slot = files %start ! Last file in directory -- easy special case file_extents = file_extents + extents %else ! Not last file in directory -- we'll have to shuffle !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Shuffle extents") !D newline !D %finish dir info_d_extent(i + extents) = dir info_d_extent(i) %c %for i = file_extents - 1, %c -1, %c dir info_d_file(files)_extents ! Now reset the extent pointers for later files %for i = Xno info_file slot, 1, files %cycle file x == dir info_d_file(i) file x_extents = file x_extents + extents %repeat %finish %finish ! Restore quota if necessary %if file_name 1 >> 16 < temporary %c %and file_status & transient file = 0 %start dir info_d_header_quota left = dir info_d_header_quota left + %c q restore %finish dir info_written = 1 %finish %finish ! File has been truncated if required. Now see if we have to ! de-'!' it, deleting a previous incarnation if one exists. %if mode # 0 %start ! Successful close, so de-'!'ing may be required. First find ! out how big the new file is, so that we can adjust quotas as ! required. n1 = file_name1; n2 = file_name2 x = file size(Xno info_file slot, dir info_d, t size, i) %result = x %if x # success %if Xno info_flags & Xno read = 0 %c %and file_status & transient file # 0 %start ! May be an earlier '!'-version to replace (Write only!) old file slot = find file(dir info_d, n1, n2, transient file) %if old file slot > success %c %and old file slot # Xno info_file slot %start ! Found a previous version (not the same file) ! Check to see if we have to deduct quota %if n1 >> 16 < temporary %start ! Will be a permanent file, deduct quota new q = header_quota left - t size %if new q < 0 %start err d = dir info_d_header_owner err n1 = n1; err n2 = n2 %result = no quota & xs error %finish header_quota left = new q %finish ! Now delete the previous version x = delete file(old file slot, dir info); ! Restores quota %result = x %if x < success %else ! No previous version of the file exists %if n1 >> 16 < temporary %start ! Enclosing condition ensures file is currently transient ! Becoming permanent file, deduct quota new q = header_quota left - t size %if new q < 0 %start err d = dir info_d_header_owner err n1 = n1; err n2 = n2 %result = no quota & xs error %finish header_quota left = new q %finish %finish ! Now de-'!' it file == dir info_d_file(Xno info_file slot) file_status = file_status & (\transient file) dir info_written = 1 %finish %finish Xno info_Uno = -1 %result = forget directory(dir info) %end %externalintegerfn FS close(%integer Xno) ! Successful close %result = close file(Xno, 1) %end %externalintegerfn FS Uclose(%integer Xno) ! Unsuccessful close %result = close file(Xno, 0) %end %externalintegerfn FS readsq(%integer Xno, %integername bytes, %record(buffer fm)%name buffer) ! Read the next block in a file. %record(Xno info fm)%name Xno info %record(dir info fm)%name dir info %record(directory fm)%name d %record(entry fm)%name file %integer x !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("ReadSQ "); write(Xno, 0) !D newline !D %finish x = validate Xno(Xno) %result = x %if x # success Xno info == common_Xno(Xno) ! File must have been opened for reading, of course %result = illegal operation %if Xno info_flags & Xno read = 0 %if Xno info_file slot < 0 %start %result = not implemented %if Xno info_file slot # -1 ! Reading special buffer %if Xno info_blocks to go <= 0 %start ! At the end buffer_bytes = 0 bytes = 0 %result = success %finish ! One fewer left now Xno info_blocks to go = Xno info_blocks to go - 1 %if Xno info_blocks to go = 0 %start ! This one is the last, so use short values buffer_bytes = Xno info_bytes bytes = Xno info_bytes %else ! Not the last, so use full block buffer_bytes = 512 bytes = 512 %finish ! Shift the data, bump the pointer bulk move(bytes, Xno info_next to send, buffer_b(0)) %if bytes > 0 Xno info_next to send == Xno info_next to send[512] %result = success %finish ! Normal disc file. %if Xno info_next file block > Xno info_blocks %start ! Last block already gone bytes = 0 buffer_bytes = 0 %result = success %finish dir info == Xno info_dir info d == dir info_d ! Bump disc address and extent pointer Xno info_next disc block = Xno info_next disc block + 1 Xno info_next extent block = Xno info_next extent block + 1 %if Xno info_next extent block >= (Xno info_extent_size & h) %start ! Off the end of the extent -- we'll need to get the next one !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring("Get next extent") !D newline !D %finish file == d_file(Xno info_file slot) ! Get next extent index Xno info_extent no = Xno info_extent no - 1 ! Get the extent itself Xno info_extent = d_extent(file_extents + Xno info_extent no) ! Disc start address for extent Xno info_next disc block = (Xno info_extent_start & h) ! First block in extent Xno info_next extent block = 0 %finish %if Xno info_next file block = Xno info_blocks %start ! Last block of last extent -- use short size bytes = Xno info_bytes buffer_bytes = bytes %else ! Not the last block, so use full 512 bytes = 512 buffer_bytes = 512 %finish ! Get the block off the disc x = read block(dir info_partition, Xno info_next disc block, buffer) ! Bump block-in-file pointer Xno info_next file block = Xno info_next file block + 1 %result = x %end %externalintegerfn FS readda(%integer Xno, block number, %integername bytes, %record(buffer fm)%name buffer) ! Direct access (ie non-sequential) read, done as a Reset followed ! by a ReadSQ. Vast extra checking, but..... %record(Xno info fm)%name Xno info %integer x x = validate Xno(Xno) %result = x %if x < success Xno info == common_Xno(Xno) %if Xno info_file slot < 0 %start ! A special file -- is it the boot area? %if Xno info_file slot = -2 %start ! Yes, allow it. bytes = 512; buffer_bytes = 512 %result = read boot area(block number, buffer_b(0)) %finish %finish x = FS reset(Xno, block number) %result = x %if x # success %result = FS readsq(Xno, bytes, buffer) %end %externalintegerfn FS writeda(%integer Xno, block number, bytes, %record(buffer fm)%name buffer) ! Direct access (ie non-sequential) write, done as a Reset followed ! by a WriteSQ. Vast extra checking, but..... %record(Xno info fm)%name Xno info %integer x %result = not writeable %if file system writeable = 0 x = validate Xno(Xno) %result = x %if x < success Xno info == common_Xno(Xno) %if Xno info_file slot < 0 %start ! A special file -- is it the boot area? %if Xno info_file slot = -2 %start ! Yes, allow it if a full block. %result = param error %if 0 # bytes # 512 %result = write boot area(block number, buffer_b(0)) %finish %finish x = FS reset(Xno, block number) %result = x %if x # success %result = FS writesq(Xno, bytes, buffer) %end %externalintegerfn FS readback(%integer Xno, %integername bytes, %record(buffer fm)%name buffer) ! Read back the previous block in the file, decrementing current ! position pointers. %record(Xno info fm)%name Xno info %record(dir info fm)%name dir info %integer x x = validate Xno(Xno) %result = x %if x # success Xno info == common_Xno(Xno) %result = not implemented %if Xno info_file slot < 0; ! Special dir info == Xno info_dir info %if Xno info_next file block = 1 %start ! Already at the start of the file bytes = 0 buffer_bytes = 0 %result = success %finish ! Decrement pointers. Shorten the file if we are reading ! back the last block in the file. Xno info_next file block = Xno info_next file block - 1 Xno info_blocks = Xno info_blocks - 1 %c %if Xno info_next file block = Xno info_blocks %if Xno info_next extent block < 0 %start ! Off the start of the current extent -- bring in previous one Xno info_extent no = Xno info_extent no + 1 Xno info_extent = %c dir info_d_extent(dir info_d_file(Xno info_file slot)_extents %c + Xno info_extent no) Xno info_next extent block = Xno info_extent_size - 1 Xno info_next disc block = (Xno info_extent_start & h) %c + Xno info_next extent block %finish x = read block(dir info_partition, Xno info_next disc block, buffer) Xno info_next disc block = Xno info_next disc block - 1 Xno info_next extent block = Xno info_next extent block - 1 %result = x %if x # success bytes = Xno info_bytes bytes = 512 %if bytes = 0 buffer_bytes = bytes Xno info_bytes = 0 !?? What happens if we readback from the middle of the file?! %result = success %end %externalintegerfn FS general(%integer Uno, case, %string(255) sp2, %integername bytes, %record(buffer fm)%name buffer) ! Miscellaneous stuff, e.g. time of day %record(Uno info fm)%name Uno info %short date, time %string(255) d, t %integer x, common start x = validate Uno(Uno, success) %result = x %if x # success Uno info == common_Uno(Uno) %if case = 0 %start ! Date and time time stamp(date, time) d = unpack date(date) t = unpack time(time) buffer_bytes = 0 add text(d, buffer) add text(" ", buffer) add text(t, buffer) bytes = buffer_bytes %result = success %else %result = not implemented %finish %end %constinteger control kill Uno = 1 %constinteger control kill Xno = 2 %constinteger control kill port = 3 %constinteger control diags = 4 %constinteger control available = 5 %constinteger control sys pass = 6 %constinteger control reboot = 7 %constinteger control bad block = 8 %constinteger control setdate = 9 %constinteger control lpzap = 10 %constinteger last control = 10 %externalinteger lpzap = -1 %externalintegerfnspec set date and time(%string(15) date, time) %externalintegerfn FS control(%integer Uno, option, %string(255) param) ! Filestore control -- kill ports, Unos, Xnos, set trace, openness %record(Uno info fm)%name Uno info %record(Uno info fm)%name Uno info x %record(Xno info fm)%name Xno info %integer x, current context, i, j, u, q %string(127) sp1, sp2 %switch case(0 : last control) x = validate Uno(Uno, not logged on) %result = x %if x # success Uno info == common_Uno(Uno) -> case(option) %if 0 < option <= last control case(*): %result = not implemented case(control kill Uno): current context = ether context u = hdx to i(param) %result = param error & p2 error %unless 0 < u <= Unos Uno info x == common_Uno(u) %result = no authority %unless Uno info_q pass = common_system pass %c %or Uno info x_l owner = Uno info_l owner { Allow suicide } %result = not logged on %if Uno info x_l owner = 0 !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring(unpack(Uno info_l owner, 0)) !D printstring(" killing Uno ") !D write(u, 0) !D newline !D %finish ether context = Uno info x_context x = FS logoff(u) %if x # success %start printstring("*** Kill Uno "); write(u, 0) printstring(": failed to log off user: ") write(x, 0) newline %finish ether context = current context %result = success case(control kill Xno): %result = no authority %unless Uno info_q pass = common_system pass current context = ether context x = hdx to i(param) %result = param error & p2 error %unless 0 < x <= Xnos Xno info == common_Xno(x) !D %if common_diags & fsys diags # 0 %start !D pdate !D printstring(unpack(Uno info_l owner, 0)) !D printstring(" killing Xno ") !D write(x, 0) !D newline !D %finish ether context = Xno info_context q = FS Uclose(x) %if q # success %start printstring("*** Kill Xno "); write(x, 0) printstring(": failed to log off user: ") write(q, 0) newline %finish ether context = current context %result = success case(control diags): { Set diagnostic mode } %result = no authority %unless Uno info_q pass = common_system pass common_diags = hdx to i(param) %result = success case(control available): { Open/close system } %result = no authority %unless Uno info_q pass = common_system pass common_system open = hdx to i(param) pdate printstring("System availability set to "); write(common_system open, 0) printstring(" by "); printstring(unpack(Uno info_l owner, 0)) printstring(" at "); print client address(ether context) newline %result = success case(control sys pass): { Set a new system password } %result = no authority %unless Uno info_q pass = common_system pass pdate printstring(unpack(Uno info_l owner, 0)) printstring(" at "); print client address(ether context) printstring(" setting new system password") newline !! param = endecrypt(unpack(Uno info_l owner, 0), param, 0) q = set system pass(encrypt(param)) %result = q %if q # success Uno info_q pass = common_system pass; ! Preserve privilege %result = success case(control reboot): { Reboot the filestore system } %result = no authority %unless Uno info_q pass = common_system pass pdate printstring(unpack(Uno info_l owner, 0)) printstring(" rebooting.....") newlines(3) %for i = 1, 1, 500 000 %cycle; %repeat *move.w #16_2700, D0 *trap #0 *move.l 0, SP *move.l 4, -(SP) *rts %result = illegal operation; ! Placate compilers, etc.... case(control bad block): { Add a new bad block to the bad list } %result = no authority %unless Uno info_q pass = common_system pass i = hdx to i(param) %result = param error & p2 error %c %unless sy1 start + head size <= i < sy2 start pdate printstring(unpack(Uno info_l owner, 0)) printstring(" at "); print client address(ether context) printstring(" adding "); write(i, 0) printstring(" to bad block list") newline add to bad block list(i) %result = success case(control set date): { Set date and time } %result = no authority %unless Uno info_q pass = common_system pass %result = param error & p2 error %unless param -> sp1 . (" ") . sp2 %result = param error & p2 error %unless length(sp1) = 8 %result = param error & p2 error %unless length(sp2) = 5 i = set date and time(sp1, sp2) %result = param error & p2 error %if i # success pdate printstring("Date and time reset by ") printstring(unpack(Uno info_l owner, 0)) printstring(" at "); print client address(ether context) newline %result = success case(control lpzap): { Ask the printer to stop printing something } i = hdx to i(param) %result = param error & p2 error %unless 0 < i <= despoolers lpzap = i; ! Printer will notice this "shortly" pdate printstring("Printer "); write(lpzap, 0) printstring(" zapped by ") printstring(unpack(Uno info_l owner, 0)) printstring(" at "); print client address(ether context) newline %result = success %end %externalroutine FSx clear context(%integer context) ! Clear down an ether context (= port). Uclose all transactions ! and log off all users using the port. %record(Uno info fm)%name Uno info %record(Xno info fm)%name Xno info %integer i, x %for i = 1, 1, Xnos %cycle ! First scan the Xno table looking for transactions owned ! by the context being killed.... ether context = context Xno info == common_Xno(i) x = FS Uclose(i) %if Xno info_Uno >= 0 %c %and Xno info_context = context %repeat %for i = 1, 1, Unos %cycle ! Now scan the Uno table, doing similarly.... ether context = context Uno info == common_Uno(i) x = FS logoff(i) %if Uno info_l owner # 0 %c %and Uno info_context = context %repeat %end %end %of %file