// rwsem prototype implementation: many readers, only one writer at a time
typedef struct rwsem { // what rwsem needs to track
  spinlock L; // a lock of some sort (TBD): protects "owners" field
  // use (quick) spinlock b/c we're only changing one int in memory
  // "owners" defines the 3 "states" that the lock L is in
  int owners; // >0 (N readers), -1 (one writer), 0 (no one owns the lock)
  int max_owners; // need to cap the max len of waiters, so as not to run
		  // out of kernel mem (or consume too many CPU resources)
  wait_q waiting_readers; // list of all waiting readers (assume FCFS)
} rwsem_t;

lock_read(rwsem_t *rws)
{
  lock(L); // must lock before reading (next line) or modifying (line after) owners
  if (rws->owners >= 0) { // no readers or at least one reader
    rws->owners++; // this is the CS of the spinlock L
    unlock(L);
    return; // successful return means we allowed the caller to enter the CS
  }
  // what if there's a writer? Can't allow reader to proceed?
  if (rws->owners < 0) {
    // exit(1); // exit is bad, see code samples below
    // better: block this reader, put them to sleep, in WAIT state
    add_to(waiting_readers); // add this thread to waiting readers queue
    // change thread state to WAITING (moved to WAIT state in scheduler)
    unlock(L);
    return;
  }
  unlock(L);
}
unlock_read(rwsem_t *rws)
{
  lock(L);
  if (rws->owners > 0) { // must have at least least one reader
    rws->owners++;
    unlock(L);
    return; // successful return means we allowed the caller to enter the CS
  } else if (rws->owners == 0) { // no readers
    unlock(L);
    BUG(); // some bug, someone not using the rwsem correctly
  }
  unlock(L);
}

lock_write(rwsem_t *rws)
{
  // no lock owners
  lock(L);
  if (rws->owners == 0) {
    rws->owners == -1;
    unlock(L);
    return;
  }
  unlock(L);
  // one or more readers

  // another writer?
}
unlock_write(rwsem_t *rws)
{
  lock(L);
  if (rws->owners < 0) { // probably should check that it's "== -1"
			// (if < -1) BUG();
    rws->owners == 0;
    // check if there are any waiting readers and wake them up
    wakeup_all(waiting_readers); // move them from WAIT to READY state
    // ideally, wake up those who slept the longest first (fairness principle)
    unlock(L);
    return;
  } else {
    BUG();
  }
  unlock(L);
}

// example of how one might use rwsem
// assume what we want to protect is the inode owner (UID) field
// we read the inode/file's owner/UID many times, but often don't change it
// unless root called chown(2).
check_inode_permission()
{
  // code here
  lock_read(inode->i_rwsem); // programmer defined that inode->i_rwsem
			     // protects inode->i_uid
  read value of inode->i_uid; // this is the CS of the rwsem
  unlock_read(inode->i_rwsem);
  // code here
}
chown_inode_owner(int new_owner)
{
  // code here
  lock_write(inode->i_rwsem);
  inode->i_uid = new_owner; // this is the CS of the rwsem
  unlock_write(inode->i_rwsem);
  // code here
}

check_inode_permission2() // what if lock_read returned a success/failure code
{
  // alternative 1
  if (lock_read(inode->i_rwsem) == 0) { // success
    read value of inode->i_uid; // this is the CS of the rwsem
    unlock_read(inode->i_rwsem);
  } else { // lock_read failed
    // option 1:
    return FAILED; // return failed to caller, propagate to upper layers,
		   // then to syscall entry point, then to user application;
		   // user app may abort.
  }

  // alternative 2: users will write spinning loops code, bad CPU use
  while (lock_read(inode->i_rwsem) != 0)
    ;
  read value of inode->i_uid; // this is the CS of the rwsem
  unlock_read(inode->i_rwsem);
}
