Small change to header comment to make it match the other 3 release shootouts

[I just realized that I didn't ever review this as intended before the
code freeze... will have to do so afterwards]

git-svn-id: 88467cb1fb04b8a755be7e1ee1026be4190196ef@22027 3a8e244f-b0f2-452b-bcba-4c88e055c3ca

/* The Computer Language Benchmarks Game
   http://benchmarksgame.alioth.debian.org/

   contributed by Hannah Hemmaplardh
   modified by Lydia Duncan
*/

config const numMeetings : int = 6000000;  // number of meetings to take place
config const numChameneos1 : int(32) = 3;  // size of population 1
config const numChameneos2 : int(32) = 10; // size of population 2
enum Color {blue=0, red=1, yellow=2};
enum Digit {zero, one, two, three, four,
            five, six, seven, eight, nine};
config const verbose = false;
// if verbose is true, prints out non-det output, otherwise prints det output
config param CHAMENEOS_IDX_MASK = 0xFF: uint(32);
config param MEET_COUNT_SHIFT = 8;

class MeetingPlace {
  var state : atomic uint(32);

  /* constructor for MeetingPlace, sets the
     number of meetings to take place */
  proc MeetingPlace() {
    state.write((numMeetings << MEET_COUNT_SHIFT) : uint(32));
  }

  /* reset must be called after meet,
     to reset numMeetings for a subsequent call of meet */
  proc reset() {
    state.write((numMeetings << MEET_COUNT_SHIFT) : uint(32));
  }
}

/* getComplement returns the complement of this and another color:
   if this and the other color are of the same value, return own value
   otherwise return the color that is neither this or the other color */
inline proc getComplement(myColor : Color, otherColor : Color) {
  if (myColor == otherColor) {
    return myColor;
  }
  return (3 - myColor - otherColor) : Color;
}

class Chameneos {
  var id: int(32);
  var color : Color;
  var meetings : int;
  var meetingsWithSelf : int;
  var meetingCompleted : atomic bool;

  /* start tells a Chameneos to go to a given MeetingPlace, where it may meet
     with another Chameneos.  If it does, it will get the other's color and
     use this color and its own to compute the color both will have after the
     meeting has ended, setting both of their colors to this value. */
  proc start(population : [] Chameneos, meetingPlace: MeetingPlace) {
    var stateTemp : uint(32);
    var peer_idx : uint(32);
    var xchg : uint(32);

    stateTemp = meetingPlace.state.read(memory_order_acquire);

    while (true) {
      peer_idx = stateTemp & CHAMENEOS_IDX_MASK;
      if (peer_idx) {
        xchg = stateTemp - peer_idx - (1 << MEET_COUNT_SHIFT):uint(32);
      } else if (stateTemp) {
        xchg = stateTemp | id;
      } else {
        break;
      }
      if (meetingPlace.state.compareExchangeStrong(stateTemp, xchg, memory_order_acq_rel)) {
        if (peer_idx) {
          runMeeting(population, peer_idx);
        } else {
          // Attend meeting

          // This version uses waitFor instead of chpl_task_yield
          meetingCompleted.waitFor(true);

          meetingCompleted.write(false, memory_order_release);
          stateTemp = meetingPlace.state.read(memory_order_acquire);
        }
      } else {
        stateTemp = meetingPlace.state.read(memory_order_acquire);
      }
    }
  }

  /* Given the id of its peer, finds and updates the data of its peer and
     itself */
  proc runMeeting (population : [] Chameneos, peer_idx : uint(32)) {
    var peer : Chameneos;
    var is_same : int;
    var newColor : Color;
    if (id == peer_idx) {
      is_same = 1;
      halt("halt: chameneos met with self");
    }
    peer = population[peer_idx:int(32)];
    newColor = getComplement(color, peer.color);
    peer.color = newColor;
    peer.meetings += 1;
    peer.meetingsWithSelf += is_same;
    peer.meetingCompleted.write(true, memory_order_release);

    color = newColor;
    meetings += 1;
    meetingsWithSelf += is_same;
  }
}

/* printColorChanges prints the result of getComplement for all possible
   pairs of colors */
proc printColorChanges() {
  const colors : [1..3] Color = (Color.blue, Color.red, Color.yellow);
  for color1 in colors {
    for color2 in colors {
      writeln(color1, " + ", color2, " -> ", getComplement(color1, color2));
    }
  }
  writeln();
}

/* populate takes an parameter of type int, size, and returns a population of
   chameneos of that size. if population size is set to 10, will use preset
   array of colors  */
proc populate (size : int(32)) {
  const colorsDefault10  = (Color.blue, Color.red, Color.yellow, Color.red,
                            Color.yellow, Color.blue, Color.red, Color.yellow,
                            Color.red, Color.blue);
  const D : domain(1, int(32)) = {1..size};
  var population : [D] Chameneos;

  if (size == 10) {
    for i in D {
      population(i) = new Chameneos(i, colorsDefault10(i));
    }
  } else {
    for i in D {
      population(i) = new Chameneos(i, ((i-1) % 3):Color);
    }
  }
  return population;
}

/* run takes a population of Chameneos and a MeetingPlace, then allows the
   Chameneos to meet. It then prints out the number of times each Chameneos
   met another Chameneos, spells out the number of times it met with itself,
   then spells out the total number of times all the Chameneos met
   another Chameneos. */
proc run(population : [] Chameneos, meetingPlace : MeetingPlace) {
  for i in population {
    write(" ", i.color);
  }
  writeln();

  coforall i in population {
    i.start(population, meetingPlace);
  }

  meetingPlace.reset();
}

proc runQuiet(population : [] Chameneos, meetingPlace : MeetingPlace) {
  coforall i in population {
    i.start(population, meetingPlace);
  }
  meetingPlace.reset();

  const totalMeetings = + reduce population.meetings;
  const totalMeetingsWithSelf = + reduce population.meetingsWithSelf;
  if (totalMeetings == numMeetings*2) {
    writeln("total meetings PASS");
  } else {
    writeln("total meetings actual = ", totalMeetings, ", total meetings expected = ", numMeetings*2);
  }

  if (totalMeetingsWithSelf == 0) {
    writeln("total meetings with self PASS");
  } else {
    writeln("total meetings with self actual = ", totalMeetingsWithSelf, ", total meetings with self expected = 0");
  }

  writeln();
}

proc printInfo(population : [] Chameneos) {
  for i in population {
    write(i.meetings);
    spellInt(i.meetingsWithSelf);
  }
  const totalMeetings = + reduce population.meetings;
  spellInt(totalMeetings);
  writeln();
}

/* spellInt takes an integer, and spells each of its digits out in English */
proc spellInt(n : int) {
  var s : string = n:string;
  for i in 1..s.length {
    write(" ", (s.substring(i):int + 1):Digit);
  }
  writeln();
}

proc main() {
  if (numChameneos1 < 2 || numChameneos2 < 2 || numMeetings < 0) {
    writeln("Please specify numChameneos1 and numChameneos2 of at least 2, and numMeetings of at least 0.");
  } else  {
    printColorChanges();

    const forest : MeetingPlace = new MeetingPlace();

    const population1 = populate(numChameneos1);
    const population2 = populate(numChameneos2);

    if (verbose) {
      run(population1, forest);
      printInfo(population1);

      run(population2, forest);
      printInfo(population2);
    } else {
      runQuiet(population1, forest);
      runQuiet(population2, forest);
    }
  }
}

/* The Computer Language Benchmarks Game
   http://benchmarksgame.alioth.debian.org/

   created by Hannah Hemmaplardh
   modified for contribution by Lydia Duncan

   - The Chameneos game is as follows:
      A population of n chameneos gathers at a common meeting place, where
      m meetings will take place (n and m may be distinct).  At any time, only
      one meeting may take place between exactly two chameneos, and each
      chameneos may be either blue, red, or yellow.  During a meeting, the
      chameneos exchange information about the other chameneos' color, so that
      after the meeting it can change its own color to the complement of its
      original color and the other chameneos' color.  (The complement is
      respective to two colors, its own and its partner's, such that if both
      colors are the same, no change, otherwise each chameneos changes to the
      color you and your partner both are not.)

    - (description of benchmark: http://shootout.alioth.debian.org/u32q/benchmark.php?test=chameneosredux&lang=all */

config const numMeetings : int = 6000000;  // number of meetings to take place
config const numChameneos1 : int(32) = 3;  // size of population 1
config const numChameneos2 : int(32) = 10; // size of population 2
enum Color {blue=0, red=1, yellow=2};
enum Digit {zero, one, two, three, four,
            five, six, seven, eight, nine};
config const verbose = false;
// if verbose is true, prints out non-det output, otherwise prints det output
config param CHAMENEOS_IDX_MASK = 0xFF: uint(32);
config param MEET_COUNT_SHIFT = 8;

class MeetingPlace {
  var state : atomic uint(32);

  /* constructor for MeetingPlace, sets the
     number of meetings to take place */
  proc MeetingPlace() {
    state.write((numMeetings << MEET_COUNT_SHIFT) : uint(32));
  }

  /* reset must be called after meet,
     to reset numMeetings for a subsequent call of meet */
  proc reset() {
    state.write((numMeetings << MEET_COUNT_SHIFT) : uint(32));
  }
}

/* getComplement returns the complement of this and another color:
   if this and the other color are of the same value, return own value
   otherwise return the color that is neither this or the other color */
inline proc getComplement(myColor : Color, otherColor : Color) {
  if (myColor == otherColor) {
    return myColor;
  }
  return (3 - myColor - otherColor) : Color;
}

class Chameneos {
  var id: int(32);
  var color : Color;
  var meetings : int;
  var meetingsWithSelf : int;
  var meetingCompleted : atomic bool;

  /* start tells a Chameneos to go to a given MeetingPlace, where it may meet
     with another Chameneos.  If it does, it will get the other's color and
     use this color and its own to compute the color both will have after the
     meeting has ended, setting both of their colors to this value. */
  proc start(population : [] Chameneos, meetingPlace: MeetingPlace) {
    var stateTemp : uint(32);
    var peer_idx : uint(32);
    var xchg : uint(32);

    stateTemp = meetingPlace.state.read(memory_order_acquire);

    while (true) {
      peer_idx = stateTemp & CHAMENEOS_IDX_MASK;
      if (peer_idx) {
        xchg = stateTemp - peer_idx - (1 << MEET_COUNT_SHIFT):uint(32);
      } else if (stateTemp) {
        xchg = stateTemp | id;
      } else {
        break;
      }
      if (meetingPlace.state.compareExchangeStrong(stateTemp, xchg, memory_order_acq_rel)) {
        if (peer_idx) {
          runMeeting(population, peer_idx);
        } else {
          // Attend meeting

          // This version uses waitFor instead of chpl_task_yield
          meetingCompleted.waitFor(true);

          meetingCompleted.write(false, memory_order_release);
          stateTemp = meetingPlace.state.read(memory_order_acquire);
        }
      } else {
        stateTemp = meetingPlace.state.read(memory_order_acquire);
      }
    }
  }

  /* Given the id of its peer, finds and updates the data of its peer and
     itself */
  proc runMeeting (population : [] Chameneos, peer_idx : uint(32)) {
    var peer : Chameneos;
    var is_same : int;
    var newColor : Color;
    if (id == peer_idx) {
      is_same = 1;
      halt("halt: chameneos met with self");
    }
    peer = population[peer_idx:int(32)];
    newColor = getComplement(color, peer.color);
    peer.color = newColor;
    peer.meetings += 1;
    peer.meetingsWithSelf += is_same;
    peer.meetingCompleted.write(true, memory_order_release);

    color = newColor;
    meetings += 1;
    meetingsWithSelf += is_same;
  }
}

/* printColorChanges prints the result of getComplement for all possible
   pairs of colors */
proc printColorChanges() {
  const colors : [1..3] Color = (Color.blue, Color.red, Color.yellow);
  for color1 in colors {
    for color2 in colors {
      writeln(color1, " + ", color2, " -> ", getComplement(color1, color2));
    }
  }
  writeln();
}

/* populate takes an parameter of type int, size, and returns a population of
   chameneos of that size. if population size is set to 10, will use preset
   array of colors  */
proc populate (size : int(32)) {
  const colorsDefault10  = (Color.blue, Color.red, Color.yellow, Color.red,
                            Color.yellow, Color.blue, Color.red, Color.yellow,
                            Color.red, Color.blue);
  const D : domain(1, int(32)) = {1..size};
  var population : [D] Chameneos;

  if (size == 10) {
    for i in D {
      population(i) = new Chameneos(i, colorsDefault10(i));
    }
  } else {
    for i in D {
      population(i) = new Chameneos(i, ((i-1) % 3):Color);
    }
  }
  return population;
}

/* run takes a population of Chameneos and a MeetingPlace, then allows the
   Chameneos to meet. It then prints out the number of times each Chameneos
   met another Chameneos, spells out the number of times it met with itself,
   then spells out the total number of times all the Chameneos met
   another Chameneos. */
proc run(population : [] Chameneos, meetingPlace : MeetingPlace) {
  for i in population {
    write(" ", i.color);
  }
  writeln();

  coforall i in population {
    i.start(population, meetingPlace);
  }

  meetingPlace.reset();
}

proc runQuiet(population : [] Chameneos, meetingPlace : MeetingPlace) {
  coforall i in population {
    i.start(population, meetingPlace);
  }
  meetingPlace.reset();

  const totalMeetings = + reduce population.meetings;
  const totalMeetingsWithSelf = + reduce population.meetingsWithSelf;
  if (totalMeetings == numMeetings*2) {
    writeln("total meetings PASS");
  } else {
    writeln("total meetings actual = ", totalMeetings, ", total meetings expected = ", numMeetings*2);
  }

  if (totalMeetingsWithSelf == 0) {
    writeln("total meetings with self PASS");
  } else {
    writeln("total meetings with self actual = ", totalMeetingsWithSelf, ", total meetings with self expected = 0");
  }

  writeln();
}

proc printInfo(population : [] Chameneos) {
  for i in population {
    write(i.meetings);
    spellInt(i.meetingsWithSelf);
  }
  const totalMeetings = + reduce population.meetings;
  spellInt(totalMeetings);
  writeln();
}

/* spellInt takes an integer, and spells each of its digits out in English */
proc spellInt(n : int) {
  var s : string = n:string;
  for i in 1..s.length {
    write(" ", (s.substring(i):int + 1):Digit);
  }
  writeln();
}

proc main() {
  if (numChameneos1 < 2 || numChameneos2 < 2 || numMeetings < 0) {
    writeln("Please specify numChameneos1 and numChameneos2 of at least 2, and numMeetings of at least 0.");
  } else  {
    printColorChanges();

    const forest : MeetingPlace = new MeetingPlace();

    const population1 = populate(numChameneos1);
    const population2 = populate(numChameneos2);

    if (verbose) {
      run(population1, forest);
      printInfo(population1);

      run(population2, forest);
      printInfo(population2);
    } else {
      runQuiet(population1, forest);
      runQuiet(population2, forest);
    }
  }
}


Give back memory in chameneosredux.chpl

Give back memory in chameneosredux.chpl

/* The Computer Language Benchmarks Game
   http://benchmarksgame.alioth.debian.org/

   contributed by Hannah Hemmaplardh
   modified by Lydia Duncan and Brad Chamberlain
*/

//
// the following params indicate the number of bits to use when storing
// IDs in a meeting place's 'state' and the corresponding bitmask
// to use when extracting chameneos IDs, respectively.
//
config param NUM_CHAMENEOS_ID_BITS = 8;
param CHAMENEOS_ID_MASK = (0x1 << NUM_CHAMENEOS_ID_BITS) - 1;

config const n = 100,                // number of meetings to take place
             numChameneos1 = 3,      // size of population 1
             numChameneos2 = 10;     // size of population 2

if (numChameneos1 < 2 || numChameneos2 < 2) then
  halt("There must be at least 2 chameneos");

if (n < 0) then
  halt("the number of meetings must be non-negative");

//
// the colors the chameneos can take on
//
param numColors = 3;
enum Color {blue=0, red, yellow};

proc main() {
  //
  // print out the equations that govern chameneos color changes
  //
  printColorEquations();

  //
  // run two simulations for the two different population sizes
  //
  simulate(numChameneos1);
  simulate(numChameneos2);
}

//
// simulate() takes a number of chameneos as input and creates as
// population of that size, then allows them to meet in parallel.
//
proc simulate(numChameneos) {
  //
  // create the population of chameneos
  //
  const chameneos = createChameneos(numChameneos);

  //
  // print the chameneos's initial colors
  //
  for c in chameneos do
    write(" ", c.color);
  writeln();

  //
  // create a meeting place and simulate the interactions
  //
  const meetingPlace = new MeetingPlace(n);
  meetingPlace.hostMeetings(chameneos);

  //
  // print information about the population once we're done
  //
  printInfo(chameneos);

  //
  // hasta las chameneos, baby.
  //
  destroyChameneos(chameneos);
  delete meetingPlace;
}

//
// This class represents a place for chameneos to meet
//
class MeetingPlace {
  //
  // the 'state' variable packs a chameneos ID in the bottom
  // NUM_CHAMENEOS_ID_BITS bits and the number of meetings in the
  // upper ones, permitting atomic operations to be performed on both
  // values simultaneously.
  //
  var state: atomic int;

  //
  // constructor initializes the number of meetings to take place
  //
  proc MeetingPlace(numMeetings) {
    state.write(numMeetings << NUM_CHAMENEOS_ID_BITS);
  }

  //
  // This method kicks off the execution of all the chameneos in
  // parallel and has them meet with each other in the meeting place.
  //
  proc hostMeetings(chameneos) {
    //
    // fire off a task per chameneos, and have them try to meet
    //
    coforall c in chameneos {
      //
      // each chameneos will try to have meetings with others as
      // long as meetings remain
      //
      do {
        //
        // read the current state, atomically
        //
        const currentState = state.read(memory_order_acquire);

        //
        // extract the number of meetings remaining and the index of a
        // waiting peer (if any) from the state
        //
        const (meetingsLeft, peerID) = stateToVals(currentState);

        //
        // if meetings remain, let's try to get one
        //
        if (meetingsLeft) {
          //
          // We're the first to arrive if there is no peer index in the
          // state
          //
          const firstToArrive = (peerID == 0);

          //
          // We'll try to meet by optimistically building the state
          // that would indicate we were successful in meeting.
          // We decrement the meetingsLeft if we are the second to arrive.
          // If we're the first to arrive, we store our ID; otherwise,
          // we reset the ID to zero.
          //
          const newState = valsToState(meetingsLeft - !firstToArrive,
                                       if firstToArrive then c.id else 0);

          //
          // use a compare-exchange to see if the state remains the
          // same, and to swap in the new state we've computed if it is.
          //
          if (state.compareExchangeStrong(currentState, newState,
                                          memory_order_acq_rel)) {
            //
            // We get to meet!
            //
            if (firstToArrive) {
              //
              // We were the first chameneos in, so we wait for the
              // other to complete the meeting
              //
              c.waitForMeetingToEnd();
            } else {
              //
              // We were the second chameneos in, so we run the meeting
              //
              c.meetWith(chameneos[peerID]);
            }
          }
        }
        //
        // continue looping as long as meetings remain
        //
      } while (meetingsLeft);
    }
  }

  //
  // This method computes a state value from a number of meetings and ID
  //
  inline proc valsToState(numMeetings, chameneosID) {
    return (numMeetings << NUM_CHAMENEOS_ID_BITS) | chameneosID;
  }

  //
  // This method extracts the number of meeting IDs and chameneos ID from
  // a state value
  //
  inline proc stateToVals(state) {
    return (state >> NUM_CHAMENEOS_ID_BITS, state & CHAMENEOS_ID_MASK);
  }
}

//
// a class representing a chameneos
//
class Chameneos {
  const id: int;                       // its unique ID
  var color: Color;                    // its color
  var meetings,                        // the number of meetings it's had
      meetingsWithSelf: int;           // the number of meetings with itself
  var meetingCompleted: atomic bool;   // indicates that a meeting is over

  //
  // This method says how to wait for a meeting to end and is used
  // by the first chameneos to a meeting
  //
  proc waitForMeetingToEnd() {
    //
    // wait for meeting to end
    //
    meetingCompleted.waitFor(true);
    //
    // and then reset our meetingCompleted flag for next time
    //
    meetingCompleted.write(false, memory_order_release);
  }

  //
  // This method computes the meeting between 'this' chameneos and
  // its peer and is used by the second chameneos to a meeting
  //
  proc meetWith(peer) {
    //
    // If we meet with ourself, something's wrong, so check for that
    //
    const metSelf = (peer == this);

    //
    // compute the new color that we'll share as a function of our
    // current colors
    //
    const newColor = getNewColor(color, peer.color);

    //
    // Update our peer's state: store the new color, update its
    // meetings, and then tell it that the meeting's over
    //
    //
    peer.color = newColor;
    peer.meetings += 1;
    peer.meetingsWithSelf += metSelf;
    peer.meetingCompleted.write(true, memory_order_release);

    //
    // Then update ourself similarly
    //
    color = newColor;
    meetings += 1;
    meetingsWithSelf += metSelf;
  }
}

//
// createChameneos() creates an array of chameneos of the given size.
// If the size is 10, it uses the predefined array of colors;
// otherwise, it deals out colors round-robin.
//
//
proc createChameneos(size): [1..size] Chameneos {
  const colorsFor10 = [Color.blue, Color.red, Color.yellow, Color.red,
                       Color.yellow, Color.blue, Color.red, Color.yellow,
                       Color.red, Color.blue];

  return [i in 1..size] new Chameneos(i, if (size == 10) then colorsFor10[i]
                                                         else ((i-1)%3):Color);
}

proc destroyChameneos(ca: [] Chameneos) {
  for c in ca do delete c;
}

//
// getNewColor() returns the complement of two colors; if the two colors
// are the same value, the color stays the same; otherwise, it becomes the
// third color.
//
inline proc getNewColor(myColor, otherColor) {
  select myColor {
    when otherColor do return myColor;
    when Color.blue {
      if (otherColor == Color.red) then return Color.yellow;
      else
        // otherColor == Color.yellow, because first when statement
        // eliminates Color.blue
        return Color.red;
    }
    when Color.red {
      if (otherColor == Color.blue) then return Color.yellow;
      else
        // otherColor == Color.yellow, because first when statement
        // eliminates Color.red
        return Color.blue;
    }
    otherwise {
      if (otherColor == Color.blue) then return Color.red;
      else
        // otherColor == Color.red, because first when statement
        // eliminates Color.yellow
        return Color.blue;
    }
  }
}

//
// printColorEquations() prints the result of getNewColor() for all
// pairs of colors
//
proc printColorEquations() {
  const colors = [Color.blue, Color.red, Color.yellow];
  for color1 in colors {
    for color2 in colors {
      writeln(color1, " + ", color2, " -> ", getNewColor(color1, color2));
    }
  }
  writeln();
}

//
// printInfo() prints out information about the population using
// a combination of numbers and English text
//
proc printInfo(chameneos) {
  //
  // for each chameneos, print the number of meetings and spell out
  // the number of self-meetings for each chameneos
  //
  for c in chameneos {
    write(c.meetings);
    spellInt(c.meetingsWithSelf);
  }

  //
  // compute the total number of meetings and spell it out
  //
  const totalMeetings = + reduce chameneos.meetings;
  spellInt(totalMeetings);
  writeln();
}

//
// the base-10 digits
//
enum Digit {zero=0, one, two, three, four, five, six, seven, eight, nine};

//
// spellInt() takes out an integer value and prints its digits out
// in English
//
proc spellInt(n) {
  //
  // Cast n to a string in order to process its characters one by one
  //
  var s = n:string;
  //writeln(n, ' ', s);
  for i in 1..s.length do
    write(" ", (s[i]:int):Digit);
  writeln();
}

/* The Computer Language Benchmarks Game
   http://benchmarksgame.alioth.debian.org/

   contributed by Hannah Hemmaplardh
   modified by Lydia Duncan and Brad Chamberlain
*/

//
// the following params indicate the number of bits to use when storing
// IDs in a meeting place's 'state' and the corresponding bitmask
// to use when extracting chameneos IDs, respectively.
//
config param NUM_CHAMENEOS_ID_BITS = 8;
param CHAMENEOS_ID_MASK = (0x1 << NUM_CHAMENEOS_ID_BITS) - 1;

config const n = 100,                // number of meetings to take place
             numChameneos1 = 3,      // size of population 1
             numChameneos2 = 10;     // size of population 2

if (numChameneos1 < 2 || numChameneos2 < 2) then
  halt("There must be at least 2 chameneos");

if (n < 0) then
  halt("the number of meetings must be non-negative");

//
// the colors the chameneos can take on
//
param numColors = 3;
enum Color {blue=0, red, yellow};

proc main() {
  //
  // print out the equations that govern chameneos color changes
  //
  printColorEquations();

  //
  // run two simulations for the two different population sizes
  //
  simulate(numChameneos1);
  simulate(numChameneos2);
}

//
// simulate() takes a number of chameneos as input and creates as
// population of that size, then allows them to meet in parallel.
//
proc simulate(numChameneos) {
  //
  // create the population of chameneos
  //
  const chameneos = createChameneos(numChameneos);

  //
  // print the chameneos's initial colors
  //
  for c in chameneos do
    write(" ", c.color);
  writeln();

  //
  // create a meeting place and simulate the interactions
  //
  const meetingPlace = new MeetingPlace(n);
  meetingPlace.hostMeetings(chameneos);

  //
  // print information about the population once we're done
  //
  printInfo(chameneos);
}

//
// This class represents a place for chameneos to meet
//
class MeetingPlace {
  //
  // the 'state' variable packs a chameneos ID in the bottom
  // NUM_CHAMENEOS_ID_BITS bits and the number of meetings in the
  // upper ones, permitting atomic operations to be performed on both
  // values simultaneously.
  //
  var state: atomic int;

  //
  // constructor initializes the number of meetings to take place
  //
  proc MeetingPlace(numMeetings) {
    state.write(numMeetings << NUM_CHAMENEOS_ID_BITS);
  }

  //
  // This method kicks off the execution of all the chameneos in
  // parallel and has them meet with each other in the meeting place.
  //
  proc hostMeetings(chameneos) {
    //
    // fire off a task per chameneos, and have them try to meet
    //
    coforall c in chameneos {
      //
      // each chameneos will try to have meetings with others as
      // long as meetings remain
      //
      do {
        //
        // read the current state, atomically
        //
        const currentState = state.read(memory_order_acquire);

        //
        // extract the number of meetings remaining and the index of a
        // waiting peer (if any) from the state
        //
        const (meetingsLeft, peerID) = stateToVals(currentState);

        //
        // if meetings remain, let's try to get one
        //
        if (meetingsLeft) {
          //
          // We're the first to arrive if there is no peer index in the
          // state
          //
          const firstToArrive = (peerID == 0);

          //
          // We'll try to meet by optimistically building the state
          // that would indicate we were successful in meeting.
          // We decrement the meetingsLeft if we are the second to arrive.
          // If we're the first to arrive, we store our ID; otherwise,
          // we reset the ID to zero.
          //
          const newState = valsToState(meetingsLeft - !firstToArrive,
                                       if firstToArrive then c.id else 0);

          //
          // use a compare-exchange to see if the state remains the
          // same, and to swap in the new state we've computed if it is.
          //
          if (state.compareExchangeStrong(currentState, newState,
                                          memory_order_acq_rel)) {
            //
            // We get to meet!
            //
            if (firstToArrive) {
              //
              // We were the first chameneos in, so we wait for the
              // other to complete the meeting
              //
              c.waitForMeetingToEnd();
            } else {
              //
              // We were the second chameneos in, so we run the meeting
              //
              c.meetWith(chameneos[peerID]);
            }
          }
        }
        //
        // continue looping as long as meetings remain
        //
      } while (meetingsLeft);
    }
  }

  //
  // This method computes a state value from a number of meetings and ID
  //
  inline proc valsToState(numMeetings, chameneosID) {
    return (numMeetings << NUM_CHAMENEOS_ID_BITS) | chameneosID;
  }

  //
  // This method extracts the number of meeting IDs and chameneos ID from
  // a state value
  //
  inline proc stateToVals(state) {
    return (state >> NUM_CHAMENEOS_ID_BITS, state & CHAMENEOS_ID_MASK);
  }
}

//
// a class representing a chameneos
//
class Chameneos {
  const id: int;                       // its unique ID
  var color: Color;                    // its color
  var meetings,                        // the number of meetings it's had
      meetingsWithSelf: int;           // the number of meetings with itself
  var meetingCompleted: atomic bool;   // indicates that a meeting is over

  //
  // This method says how to wait for a meeting to end and is used
  // by the first chameneos to a meeting
  //
  proc waitForMeetingToEnd() {
    //
    // wait for meeting to end
    //
    meetingCompleted.waitFor(true);
    //
    // and then reset our meetingCompleted flag for next time
    //
    meetingCompleted.write(false, memory_order_release);
  }

  //
  // This method computes the meeting between 'this' chameneos and
  // its peer and is used by the second chameneos to a meeting
  //
  proc meetWith(peer) {
    //
    // If we meet with ourself, something's wrong, so check for that
    //
    const metSelf = (peer == this);

    //
    // compute the new color that we'll share as a function of our
    // current colors
    //
    const newColor = getNewColor(color, peer.color);

    //
    // Update our peer's state: store the new color, update its
    // meetings, and then tell it that the meeting's over
    //
    //
    peer.color = newColor;
    peer.meetings += 1;
    peer.meetingsWithSelf += metSelf;
    peer.meetingCompleted.write(true, memory_order_release);

    //
    // Then update ourself similarly
    //
    color = newColor;
    meetings += 1;
    meetingsWithSelf += metSelf;
  }
}

//
// createChameneos() creates an array of chameneos of the given size.
// If the size is 10, it uses the predefined array of colors;
// otherwise, it deals out colors round-robin.
//
//
proc createChameneos(size): [1..size] Chameneos {
  const colorsFor10 = [Color.blue, Color.red, Color.yellow, Color.red,
                       Color.yellow, Color.blue, Color.red, Color.yellow,
                       Color.red, Color.blue];

  return [i in 1..size] new Chameneos(i, if (size == 10) then colorsFor10[i]
                                                         else ((i-1)%3):Color);
}

//
// getNewColor() returns the complement of two colors; if the two colors
// are the same value, the color stays the same; otherwise, it becomes the
// third color.
//
inline proc getNewColor(myColor, otherColor) {
  select myColor {
    when otherColor do return myColor;
    when Color.blue {
      if (otherColor == Color.red) then return Color.yellow;
      else
        // otherColor == Color.yellow, because first when statement
        // eliminates Color.blue
        return Color.red;
    }
    when Color.red {
      if (otherColor == Color.blue) then return Color.yellow;
      else
        // otherColor == Color.yellow, because first when statement
        // eliminates Color.red
        return Color.blue;
    }
    otherwise {
      if (otherColor == Color.blue) then return Color.red;
      else
        // otherColor == Color.red, because first when statement
        // eliminates Color.yellow
        return Color.blue;
    }
  }
}

//
// printColorEquations() prints the result of getNewColor() for all
// pairs of colors
//
proc printColorEquations() {
  const colors = [Color.blue, Color.red, Color.yellow];
  for color1 in colors {
    for color2 in colors {
      writeln(color1, " + ", color2, " -> ", getNewColor(color1, color2));
    }
  }
  writeln();
}

//
// printInfo() prints out information about the population using
// a combination of numbers and English text
//
proc printInfo(chameneos) {
  //
  // for each chameneos, print the number of meetings and spell out
  // the number of self-meetings for each chameneos
  //
  for c in chameneos {
    write(c.meetings);
    spellInt(c.meetingsWithSelf);
  }

  //
  // compute the total number of meetings and spell it out
  //
  const totalMeetings = + reduce chameneos.meetings;
  spellInt(totalMeetings);
  writeln();
}

//
// the base-10 digits
//
enum Digit {zero=0, one, two, three, four, five, six, seven, eight, nine};

//
// spellInt() takes out an integer value and prints its digits out
// in English
//
proc spellInt(n) {
  //
  // Cast n to a string in order to process its characters one by one
  //
  var s = n:string;
  //writeln(n, ' ', s);
  for i in 1..s.length do
    write(" ", (s[i]:int):Digit);
  writeln();
}


Implement copyTree

Implement copyTree

Takes in a source and a destination (and whether to follow or copy contents of
symbolic links) and copies the contents of source into dest.  src/a will be
copied to dest/a, etc.

Pair programmed with @bradcray.

Will add tests.  Still working out the kinks.

/*
 * Copyright 2004-2015 Cray Inc.
 * Other additional copyright holders may be indicated within.
 *
 * The entirety of this work is licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use Error;

/* Change the current working directory of the current locale to the specified
   name. Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(out error: syserr, name: string) {
  extern proc chpl_fs_chdir(name: c_string):syserr;

  on this {
    error = chpl_fs_chdir(name.c_str());
  }
}

/* Change the current working directory of the current locale to the specified
   name. Generates an error message if one occurred.
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(name: string) {
  var err: syserr = ENOERR;
  chdir(err, name);
  if err != ENOERR then ioerror(err, "in chdir", name);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings.  Returns any errors that occurred via an out
   parameter.

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file in
              question.  See description of the provided constants for
              potential values.
*/
proc chmod(out error: syserr, name: string, mode: int) {
  extern proc chpl_fs_chmod(name: c_string, mode: int): syserr;

  error = chpl_fs_chmod(name.c_str(), mode);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings, and may generate an error message

   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file
              in question.  See description of the provided constants for potential
              values.
*/
proc chmod(name: string, mode: int){
  var err: syserr = ENOERR;
  chmod(err, name, mode);
  if err != ENOERR then ioerror(err, "in chmod", name);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged.
   err: a syserr used to indicate if an error occurred
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(out error: syserr, name: string, uid: int, gid: int) {
  extern proc chpl_fs_chown(name: c_string, uid: c_int, gid: c_int):syserr;

  error = chpl_fs_chown(name.c_str(), uid:c_int, gid:c_int);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged. Generates an error message if one occurred.
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(name: string, uid: int, gid: int) {
  var err: syserr = ENOERR;
  chown(err, name, uid, gid);
  if err != ENOERR then ioerror(err, "in chown", name);
}

// FUTURE WORK:
// When basename and joinPath are supported, enable dest to be a directory.

/* Copies the contents and permissions of the file indicated by src into
   the file or directory dest.  If dest is a directory, will return an error via
   the out parameter.  If metadata is set to true, will also copy the metadata
   of the file to be copied.  Other errors may also be returned via the out
   parameter.
   err: a syserr used to indicate if an error occurred
   src: the source file whose contents and permissions are to be copied
   dest: the destination file for the contents and permissions.
   metadata: a boolean indicating whether to copy metadata (uid, gid, time of
             last access and time of modification) associated with the source
             file.
*/
proc copy(out error: syserr, src: string, dest: string, metadata: bool = false) {
  var destFile = dest;
  if (isDir(error, destFile)) {
    // destFile = joinPath(destFile, basename(src));
    error = EISDIR;
    // Supporting a destination directory requires getting the basename from
    // the src (because we're using the same name) and joining it with the
    // provided destination directory.  Both of those operations are part of
    // the string portion, so we aren't supporting it just yet.
    return;
  } else {
    if (error == ENOENT) {
      // Destination didn't exist before.  We'd be overwriting it anyways, so
      // we don't care.
      error = ENOERR;
    }
    if error != ENOERR then return;
  }
  copyFile(error, src, destFile);
  if error != ENOERR then return;
  copyMode(error, src, destFile);
  if error != ENOERR then return;

  if (metadata) {
    extern proc chpl_fs_copy_metadata(source: c_string, dest: c_string): syserr;

    // Copies the access time, and time of last modification.
    // Does not copy uid, gid, or mode
    error = chpl_fs_copy_metadata(src.c_str(), dest.c_str());

    // Get uid and gid from src
    var uid = getUID(error, src);
    if error != ENOERR then return;
    var gid = getGID(error, src);
    if error != ENOERR then return;
    // Change uid and gid to that of the src
    chown(error, destFile, uid, gid);
  }
}

/* Copies the contents and permissions of the file indicated by src into
   the file or directory dest.  If dest is a directory, will halt with an error
   message.  If metadata is set to true, will also copy the metadata of the
   file to be copied.  May halt with other error messages.
   src: the source file whose contents and permissions are to be copied
   dest: the destination file for the contents and permissions.
   metadata: a boolean indicating whether to copy metadata (uid, gid, time of
             last access and time of modification) associated with the source
             file.
*/
proc copy(src: string, dest: string, metadata: bool = false) {
  var err: syserr = ENOERR;
  copy(err, src, dest, metadata);
  if err != ENOERR then ioerror(err, "in copy(" + src + ", " + dest + ")");
}

/* Copies the contents of the file indicated by src into the file indicated
   by dest, replacing dest if it already exists (and is different than src).
   If the dest is not writable or src and dest are the same file, generates
   an error.  Does not copy metadata.
   err: a syserr used to indicate if an error occurred
   src: the source file whose contents are to be copied.
   dest: the destination of the contents.
*/
proc copyFile(out error: syserr, src: string, dest: string) {
  // This implementation is based off of the python implementation for copyfile,
  // with some slight differences.  That implementation was found at:
  // https://bitbucket.org/mirror/cpython/src/c8ce5bca0fcda4307f7ac5d69103ce128a562705/Lib/shutil.py?at=default
  // I did not look at the other functions in that file, except for copyfileobj
  // (which copyfile called).
  if (!exists(src)) {
    error = ENOENT;
    // Source didn't exist, we can't copy it.
    return;
  }
  if (isDir(error, src) || isDir(error, dest)) {
    // If the source is a directory, the user has made a mistake, so return an
    // error.  The same is true if the destination is a directory.
    error = EISDIR;
    return;
  }

  if (error == ENOENT) {
    error = ENOERR;
    // We don't care if dest did not exist before, we'll create or overwrite it
    // anyways.  We already know src exists.
  } else if (sameFile(error, src, dest)) {
    // Check if the files are the same, error if yes

    // Don't need to check if they're the same file when we know dest didn't
    // exist.
    error = EINVAL;
    return;
    // The second argument is invalid if the two arguments are the same.
  }

  // Lydia note (03/04/2014): These enclosing curly braces are to avoid the bug
  // found in test/io/lydia/outArgEarlyExit*.future.  When those futures are
  // resolved, the braces should be removed.
  {
    // Open src for reading, open dest for writing
    var srcFile = open(src, iomode.r);
    var destFile = open(dest, iomode.cw);
    var srcChnl = srcFile.reader(kind=ionative, locking=false);
    var destChnl = destFile.writer(kind=ionative, locking=false);
    // read in, write out.
    var line: [0..1023] uint(8);
    var numRead: int = 0;
    while (srcChnl.readline(line, numRead=numRead, error=error)) {
      // From mppf:
      // If you want it to be faster, we can make it only buffer once (sharing
      // the bytes read into memory between the two channels). To do that you'd
      // do something like this (in a loop):

      // srcReader.mark
      // srcReader.advance( buffer size )
      // srcReader.beginPeekBuffer to get the current buffer (qio_channel_begin_peek_buffer)
      // dstWriter.putBuffer with the buffer we got (qio_channel_put_buffer)
      // srcReader.endPeekBuffer

      // Some of these routines don't exist with Chapel wrappers now

      destChnl.write(line[0..#numRead]);
    }
    if error == EEOF then error = ENOERR;
    destChnl.flush();

    srcFile.close();
    destFile.close();
  }
}

/* Copies the contents of the file indicated by src into the file indicated
   by dest, replacing dest if it already exists (and is different than src).
   If the dest is not writable or src and dest are the same file, halts with
   an error.  Does not copy metadata.  May halt with an error message.
   src: the source file whose contents are to be copied.
   dest: the destination of the contents.
*/
proc copyFile(src: string, dest: string) {
  var err: syserr = ENOERR;
  copyFile(err, src, dest);
  if err != ENOERR then ioerror(err, "in copyFile(" + src + ", " + dest + ")");
}

/* Copies the permissions of the file indicated by src to the file indicated
   by dest, leaving contents, owner and group unaffected.
   err: a syserr used to indicate if an error occurred
   src: the source file whose permissions are to be copied.
   dest: the destination of the permissions.
*/
proc copyMode(out error: syserr, src: string, dest: string) {
  // Gets the mode from the source file.
  var srcMode = getMode(error, src);
  // If any error occurred, we want to be the one reporting it, so as not
  // to bleed implementation details.  If we found one when viewing the
  // source's mode, we should return immediately.
  if error != ENOERR then return;
  // Sets the mode of the destination to the source's mode.
  chmod(error, dest, srcMode);
}

/* Copies the permissions of the file indicated by src to the file indicated
   by dest, leaving contents, owner and group unaffected.  May halt with an
   error message.
   src: the source file whose permissions are to be copied.
   dest: the destination of the permissions.
*/
proc copyMode(src: string, dest: string) {
  var err: syserr = ENOERR;
  copyMode(err, src, dest);
  if err != ENOERR then ioerror(err, "in copyMode " + src, dest);
}

use Filerator;
use Path;

proc copyTreeHelper(out error: syserr, src: string, dest: string, copySymbolically: bool=false) {
  var oldMode = getMode(src);
  mkdir(error, dest, mode=oldMode, parents=true);
  if error != ENOERR then return;
  // Create dest

  for filename in listdir(path=src, dirs=false, files=true, listlinks=copySymbolically) {
    // Take care of files in src
    var fileDestName = dest + "/" + filename;
    copy(error, filename, fileDestName, metadata=true);
    if (error != ENOERR) then return;
  }

  for dirname in listdir(path=src, dirs=true, files=false, listlinks=copySymbolically) {
    copyTreeHelper(error, src+"/"+dirname, dest+"/"+dirname, copySymbolically);
  }
}

/* Will recursively copy the tree which starts at src into dst, including all
   contents, permissions, and metadata.  dst must not previously exist, this
   function assumes it can create it and any missing parent directories.
   If copySymbolically is true, symlinks will be copied as symlinks, otherwise
   their contents and metadata will be copied. May return an error via an out
   argument.
   error: a syserr used to indicate if an error occurred
   src: the root of the source tree to be copied.
   dest: the root of the destination directory where the tree where the tree is
         to be copied (must not exist prior to this function call).
   copySymbolically: a boolean indicating how to handle symlinks in the
                     source directory.
*/
proc copyTree(out error: syserr, src: string, dest: string, copySymbolically: bool=false) {
  var expectedErrorCases = exists(error, dest);
  if (error != ENOERR) then return; // Some error occurred in checking the existence of dest.
  else if (expectedErrorCases) {
    // dest exists.  That's not ideal.
    error = EEXIST;
    return;
  }
  expectedErrorCases = !isDir(error, src);
  if (error != ENOERR) then return; // Some error occurred in checking src was a directory.
  else if (expectedErrorCases) {
    error = ENOTDIR;
    return;
  }

  var srcPath = realPath(src);

  copyTreeHelper(error, srcPath, dest, copySymbolically);
}

/* Will recursively copy the tree which starts at src into dst, including all
   contents, permissions, and metadata.  dst must not previously exist, this
   function assumes it can create it and any missing parent directories.
   If copySymbolically is true, symlinks will be copied as symlinks, otherwise
   their contents and metadata will be copied. May halt with an error message.
   src: the root of the source tree to be copied.
   dest: the root of the destination directory where the tree where the tree is
         to be copied (must not exist prior to this function call).
   copySymbolically: a boolean indicating how to handle symlinks in the
                     source directory.
*/
proc copyTree(src: string, dest: string, copySymbolically: bool=false) {
  var err: syserr = ENOERR;
  copyTree(err, src, dest, copySymbolically);
  if err != ENOERR then ioerror(err, "in copyTree " + src, dest);
}

/* Returns the current working directory for the current locale.
   err: a syserr used to indicate if an error occurred

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(out error: syserr): string {
  extern proc chpl_fs_cwd(ref working_dir:c_string_copy):syserr;

  var ret:string;
  on this {
    var tmp:c_string_copy;
    // c_strings and c_string_copy's can't cross on statements.
    error = chpl_fs_cwd(tmp);
    if (error != ENOERR) {
      ret = "";
    } else {
      // This version of toString steals its operand.  No need to free.
      ret = toString(tmp);
    }
  }
  return ret;
}

/* Returns the current working directory for the current locale. Generates an
   error message if one occurred.

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(): string {
  var err: syserr = ENOERR;
  var ret = cwd(err);
  if err != ENOERR then ioerror(err, "in cwd");
  return ret;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Returns any errors that
   occurred via an out parameter
   name: a string used to attempt to find the file specified.
*/
proc exists(out error: syserr, name: string): bool {
  extern proc chpl_fs_exists(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_exists(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Generates an error
   message if one occurred.
   name: a string used to attempt to find the file specified.
*/
proc exists(name: string): bool {
  var err: syserr = ENOERR;
  var ret = exists(err, name);
  if err != ENOERR then ioerror(err, "in exists");
  return ret;
}

/* Returns the group id associated with the file or directory specified by
   name.  Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string used to indicate the file in question
*/
proc getGID(out error: syserr, name: string): int {
  extern proc chpl_fs_get_gid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  error = chpl_fs_get_gid(result, name.c_str());
  return result;
}

/* Returns the group id associated with the file or directory specified by
   name.  May generate an error message.
   name: a string used to indicate the file in question
*/
proc getGID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getGID(err, name);
  if err != ENOERR then ioerror(err, "in getGID");
  return ret;
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   err: a syserr used to indicate if an error occurred during this function
   name: the name of the file that you want to know the permissions of.
*/
proc getMode(out error: syserr, name: string): int {
  extern proc chpl_fs_viewmode(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_viewmode(ret, name.c_str());
  return ret;
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   name: the name of the file that you want to know the permissions of.
*/
proc getMode(name: string): int {
  var err:syserr = ENOERR;
  var result = getMode(err, name);
  if err != ENOERR then ioerror(err, "in getMode", name);
  return result;
}

/* Returns the user id associated with the file or directory specified by
   name.  Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string used to indicate the file in question
*/
proc getUID(out error: syserr, name: string): int {
  extern proc chpl_fs_get_uid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  error = chpl_fs_get_uid(result, name.c_str());
  return result;
}

/* Returns the user id associated with the file or directory specified by
   name.  May generate an error message.
   name: a string used to indicate the file in question
*/
proc getUID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getUID(err, name);
  if err != ENOERR then ioerror(err, "in getUID");
  return ret;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a directory.
*/
proc isDir(out error:syserr, name:string):bool {
  extern proc chpl_fs_is_dir(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  error = chpl_fs_is_dir(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a directory.
*/
proc isDir(name:string):bool {
  var err:syserr;
  var ret = isDir(err, name);
  if err != ENOERR then ioerror(err, "in isDir", name);
  return ret;
}

/* Returns true if the name corresponds to a file, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a file.
*/
proc isFile(out error:syserr, name:string):bool {
  extern proc chpl_fs_is_file(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  error = chpl_fs_is_file(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a file, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a file.
*/
proc isFile(name:string):bool {
  var err:syserr;
  var ret = isFile(err, name);
  if err != ENOERR then ioerror(err, "in isFile", name);
  return ret;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.
   name: a string that could be the name of a symbolic link.
*/
proc isLink(out error:syserr, name: string): bool {
  extern proc chpl_fs_is_link(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_is_link(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.  Generates an error message if one
   occurs
   name: a string that could be the name of a symbolic link.
*/
proc isLink(name: string): bool {
  var err:syserr;
  var ret = isLink(err, name);
  if err != ENOERR then ioerror(err, "in isLink", name);
  return ret;
}

/* Returns true if the name corresponds to a mount point, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a mount point.
*/
proc isMount(out error:syserr, name: string): bool {
  extern proc chpl_fs_is_mount(ref result:c_int, name: c_string): syserr;

  if (isFile(name)) {
    // Files aren't mount points.  That would be silly.
    error = ENOERR;
    return false;
  }
  var ret:c_int;
  error = chpl_fs_is_mount(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a mount point, false otherwise.
   name: a string that could be the name of a mount point.
*/
proc isMount(name: string): bool {
  var err:syserr;
  var ret = isMount(err, name);
  if err != ENOERR then ioerror(err, "in isMount", name);
  return ret;
}

/* These are constant values of the form S_I[R | W | X][USR | GRP | OTH],
   S_IRWX[U | G | O], S_ISUID, S_ISGID, or S_ISVTX, where R corresponds to
   readable, W corresponds to writable, X corresponds to executable, USR and
   U correspond to user, GRP and G correspond to group, OTH and O correspond
   to other, directly tied to the C idea of these constants.  They are intended
   for use with functions that alter the permissions of files or directories.
*/
extern const S_IRUSR: int;
extern const S_IWUSR: int;
extern const S_IXUSR: int;
extern const S_IRWXU: int;

extern const S_IRGRP: int;
extern const S_IWGRP: int;
extern const S_IXGRP: int;
extern const S_IRWXG: int;

extern const S_IROTH: int;
extern const S_IWOTH: int;
extern const S_IXOTH: int;
extern const S_IRWXO: int;

extern const S_ISUID: int;
extern const S_ISGID: int;
extern const S_ISVTX: int;

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Returns any errors that occurred via an out parameter

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(out error: syserr, name: string, mode: int = 0o777,
           parents: bool=false) {
  extern proc chpl_fs_mkdir(name: c_string, mode: int, parents: bool):syserr;

  error = chpl_fs_mkdir(name.c_str(), mode, parents);
}

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Generates an error message if one occurred.

   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(name: string, mode: int = 0o777, parents: bool=false) {
  var err: syserr = ENOERR;
  mkdir(err, name, mode, parents);
  if err != ENOERR then ioerror(err, "in mkdir", name);
}

/* Renames the file specified by oldname to newname, returning an error
   if one occurred.  The file is not opened during this operation.
   error: a syserr used to indicate if an error occurred during renaming.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(out error: syserr, oldname, newname: string) {
  extern proc chpl_fs_rename(oldname: c_string, newname: c_string):syserr;

  error = chpl_fs_rename(oldname.c_str(), newname.c_str());
}

/* Renames the file specified by oldname to newname, generating an error message
   if one occurred.  The file is not opened during this operation.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(oldname, newname: string) {
  var err:syserr = ENOERR;
  rename(err, oldname, newname);
  if err != ENOERR then ioerror(err, "in rename", oldname);
}

/* Removes the file or directory specified by name, returning an error
   if one occurred via an out parameter.
   err: a syserr used to indicate if an error occurred during removal
   name: the name of the file/directory to remove */
proc remove(out error: syserr, name: string) {
  extern proc chpl_fs_remove(name: c_string):syserr;

  error = chpl_fs_remove(name.c_str());
}

/* Removes the file or directory specified by name, generating an error message
   if one occurred.
   name: the name of the file/directory to remove */
proc remove(name: string) {
  var err:syserr = ENOERR;
  remove(err, name);
  if err != ENOERR then ioerror(err, "in remove", name);
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.
   err: a syserr used to indicate if an error occurred during comparison
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(out error: syserr, file1: string, file2: string): bool {
  extern proc chpl_fs_samefile_string(ref ret: c_int, file1: c_string, file2: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_samefile_string(ret, file1.c_str(), file2.c_str());
  return ret != 0;
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.  May generate an error message.
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(file1: string, file2: string): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1, file2);
  return result;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.
*/
proc sameFile(out error: syserr, file1: file, file2: file): bool {
  extern proc chpl_fs_samefile(ref ret: c_int, file1: qio_file_ptr_t,
                               file2: qio_file_ptr_t): syserr;

  var ret:c_int;
  if (is_c_nil(file1._file_internal) || is_c_nil(file2._file_internal)) {
    // Implementation note on program design tradeoffs:
    // I could use file.check() here.  That would not rely on the understanding
    // of file's internals.  However, it would cause this method to either
    // return error messages or halt, depending on the error encountered.
    // This check could be moved to the version w/o the err argument, but if
    // someone called this function w/o going through that, we'd lose the
    // check.  Also, we already must make use of the record internals to do the
    // inner comparison (since the record is a chapel construct), so there's no
    // additional harm.

    // The file is referencing a null file.  We'll get a segfault if we
    // continue.
    error = EBADF;
    return false; // This part isn't as important as the error.
  }

  error = chpl_fs_samefile(ret, file1._file_internal, file2._file_internal);
  return ret != 0;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.  May generate an error message.
*/
proc sameFile(file1: file, file2: file): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1.path, file2.path);
  return result;
}

/* Create a symbolic link pointing to oldName named newName.  May generate an
   error message.
   err: a syserr used to indicate if an error occurred during creation
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(out error: syserr, oldName: string, newName: string) {
  extern proc chpl_fs_symlink(orig: c_string, linkName: c_string): syserr;

  error = chpl_fs_symlink(oldName.c_str(), newName.c_str());
}

/* Create a symbolic link pointing to oldName named sym.  May generate an error
   message.
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(oldName: string, newName: string) {
  var err:syserr = ENOERR;
  symlink(err, oldName, newName);
  if err != ENOERR then ioerror(err, "in symlink " + oldName, newName);
}

/* Sets the file creation mask of the current process to mask, and returns
   the previous value of the file creation mask.
   mask: the file creation mask to use now.
*/
proc umask(mask: int): int {
  extern proc chpl_fs_umask(mask: mode_t): mode_t;

  return chpl_fs_umask(mask.safeCast(mode_t));
}

/*
 * Copyright 2004-2015 Cray Inc.
 * Other additional copyright holders may be indicated within.
 *
 * The entirety of this work is licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
use Error;

/* Change the current working directory of the current locale to the specified
   name. Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(out error: syserr, name: string) {
  extern proc chpl_fs_chdir(name: c_string):syserr;

  on this {
    error = chpl_fs_chdir(name.c_str());
  }
}

/* Change the current working directory of the current locale to the specified
   name. Generates an error message if one occurred.
   name: a string indicating a new directory

   Note: this is not safe within a parallel context.  A chdir call in one task
   will affect the current working directory of all tasks for that locale.
*/
proc locale.chdir(name: string) {
  var err: syserr = ENOERR;
  chdir(err, name);
  if err != ENOERR then ioerror(err, "in chdir", name);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings.  Returns any errors that occurred via an out
   parameter.

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file in
              question.  See description of the provided constants for
              potential values.
*/
proc chmod(out error: syserr, name: string, mode: int) {
  extern proc chpl_fs_chmod(name: c_string, mode: int): syserr;

  error = chpl_fs_chmod(name.c_str(), mode);
}

/* Set the permissions of the file or directory specified by name to that
   indicated by settings, and may generate an error message

   :arg name: the name of the file/directory which should have its permissions
              alterred.
   :arg mode: an integer representing the permissions desired for the file
              in question.  See description of the provided constants for potential
              values.
*/
proc chmod(name: string, mode: int){
  var err: syserr = ENOERR;
  chmod(err, name, mode);
  if err != ENOERR then ioerror(err, "in chmod", name);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged.
   err: a syserr used to indicate if an error occurred
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(out error: syserr, name: string, uid: int, gid: int) {
  extern proc chpl_fs_chown(name: c_string, uid: c_int, gid: c_int):syserr;

  error = chpl_fs_chown(name.c_str(), uid:c_int, gid:c_int);
}

/* Changes one or both of the owner and group id of the named file to the
   specified values.  If uid or gid are -1, the value in question will remain
   unchanged. Generates an error message if one occurred.
   name: the name of the file to be changed.
   uid: user id to use as new owner, or -1 if it should remain the same.
   gid: group id to use as the new group owner, or -1 if it should remain the same.
*/
proc chown(name: string, uid: int, gid: int) {
  var err: syserr = ENOERR;
  chown(err, name, uid, gid);
  if err != ENOERR then ioerror(err, "in chown", name);
}

// FUTURE WORK:
// When basename and joinPath are supported, enable dest to be a directory.

/* Copies the contents and permissions of the file indicated by src into
   the file or directory dest.  If dest is a directory, will return an error via
   the out parameter.  If metadata is set to true, will also copy the metadata
   of the file to be copied.  Other errors may also be returned via the out
   parameter.
   err: a syserr used to indicate if an error occurred
   src: the source file whose contents and permissions are to be copied
   dest: the destination file for the contents and permissions.
   metadata: a boolean indicating whether to copy metadata (uid, gid, time of
             last access and time of modification) associated with the source
             file.
*/
proc copy(out error: syserr, src: string, dest: string, metadata: bool = false) {
  var destFile = dest;
  if (isDir(error, destFile)) {
    // destFile = joinPath(destFile, basename(src));
    error = EISDIR;
    // Supporting a destination directory requires getting the basename from
    // the src (because we're using the same name) and joining it with the
    // provided destination directory.  Both of those operations are part of
    // the string portion, so we aren't supporting it just yet.
    return;
  } else {
    if (error == ENOENT) {
      // Destination didn't exist before.  We'd be overwriting it anyways, so
      // we don't care.
      error = ENOERR;
    }
    if error != ENOERR then return;
  }
  copyFile(error, src, destFile);
  if error != ENOERR then return;
  copyMode(error, src, destFile);
  if error != ENOERR then return;

  if (metadata) {
    extern proc chpl_fs_copy_metadata(source: c_string, dest: c_string): syserr;

    // Copies the access time, and time of last modification.
    // Does not copy uid, gid, or mode
    error = chpl_fs_copy_metadata(src.c_str(), dest.c_str());

    // Get uid and gid from src
    var uid = getUID(error, src);
    if error != ENOERR then return;
    var gid = getGID(error, src);
    if error != ENOERR then return;
    // Change uid and gid to that of the src
    chown(error, destFile, uid, gid);
  }
}

/* Copies the contents and permissions of the file indicated by src into
   the file or directory dest.  If dest is a directory, will halt with an error
   message.  If metadata is set to true, will also copy the metadata of the
   file to be copied.  May halt with other error messages.
   src: the source file whose contents and permissions are to be copied
   dest: the destination file for the contents and permissions.
   metadata: a boolean indicating whether to copy metadata (uid, gid, time of
             last access and time of modification) associated with the source
             file.
*/
proc copy(src: string, dest: string, metadata: bool = false) {
  var err: syserr = ENOERR;
  copy(err, src, dest, metadata);
  if err != ENOERR then ioerror(err, "in copy(" + src + ", " + dest + ")");
}

/* Copies the contents of the file indicated by src into the file indicated
   by dest, replacing dest if it already exists (and is different than src).
   If the dest is not writable or src and dest are the same file, generates
   an error.  Does not copy metadata.
   err: a syserr used to indicate if an error occurred
   src: the source file whose contents are to be copied.
   dest: the destination of the contents.
*/
proc copyFile(out error: syserr, src: string, dest: string) {
  // This implementation is based off of the python implementation for copyfile,
  // with some slight differences.  That implementation was found at:
  // https://bitbucket.org/mirror/cpython/src/c8ce5bca0fcda4307f7ac5d69103ce128a562705/Lib/shutil.py?at=default
  // I did not look at the other functions in that file, except for copyfileobj
  // (which copyfile called).
  if (!exists(src)) {
    error = ENOENT;
    // Source didn't exist, we can't copy it.
    return;
  }
  if (isDir(error, src) || isDir(error, dest)) {
    // If the source is a directory, the user has made a mistake, so return an
    // error.  The same is true if the destination is a directory.
    error = EISDIR;
    return;
  }

  if (error == ENOENT) {
    error = ENOERR;
    // We don't care if dest did not exist before, we'll create or overwrite it
    // anyways.  We already know src exists.
  } else if (sameFile(error, src, dest)) {
    // Check if the files are the same, error if yes

    // Don't need to check if they're the same file when we know dest didn't
    // exist.
    error = EINVAL;
    return;
    // The second argument is invalid if the two arguments are the same.
  }

  // Lydia note (03/04/2014): These enclosing curly braces are to avoid the bug
  // found in test/io/lydia/outArgEarlyExit*.future.  When those futures are
  // resolved, the braces should be removed.
  {
    // Open src for reading, open dest for writing
    var srcFile = open(src, iomode.r);
    var destFile = open(dest, iomode.cw);
    var srcChnl = srcFile.reader(kind=ionative, locking=false);
    var destChnl = destFile.writer(kind=ionative, locking=false);
    // read in, write out.
    var line: [0..1023] uint(8);
    var numRead: int = 0;
    while (srcChnl.readline(line, numRead=numRead, error=error)) {
      // From mppf:
      // If you want it to be faster, we can make it only buffer once (sharing
      // the bytes read into memory between the two channels). To do that you'd
      // do something like this (in a loop):

      // srcReader.mark
      // srcReader.advance( buffer size )
      // srcReader.beginPeekBuffer to get the current buffer (qio_channel_begin_peek_buffer)
      // dstWriter.putBuffer with the buffer we got (qio_channel_put_buffer)
      // srcReader.endPeekBuffer

      // Some of these routines don't exist with Chapel wrappers now

      destChnl.write(line[0..#numRead]);
    }
    if error == EEOF then error = ENOERR;
    destChnl.flush();

    srcFile.close();
    destFile.close();
  }
}

/* Copies the contents of the file indicated by src into the file indicated
   by dest, replacing dest if it already exists (and is different than src).
   If the dest is not writable or src and dest are the same file, halts with
   an error.  Does not copy metadata.  May halt with an error message.
   src: the source file whose contents are to be copied.
   dest: the destination of the contents.
*/
proc copyFile(src: string, dest: string) {
  var err: syserr = ENOERR;
  copyFile(err, src, dest);
  if err != ENOERR then ioerror(err, "in copyFile(" + src + ", " + dest + ")");
}

/* Copies the permissions of the file indicated by src to the file indicated
   by dest, leaving contents, owner and group unaffected.
   err: a syserr used to indicate if an error occurred
   src: the source file whose permissions are to be copied.
   dest: the destination of the permissions.
*/
proc copyMode(out error: syserr, src: string, dest: string) {
  // Gets the mode from the source file.
  var srcMode = getMode(error, src);
  // If any error occurred, we want to be the one reporting it, so as not
  // to bleed implementation details.  If we found one when viewing the
  // source's mode, we should return immediately.
  if error != ENOERR then return;
  // Sets the mode of the destination to the source's mode.
  chmod(error, dest, srcMode);
}

/* Copies the permissions of the file indicated by src to the file indicated
   by dest, leaving contents, owner and group unaffected.  May halt with an
   error message.
   src: the source file whose permissions are to be copied.
   dest: the destination of the permissions.
*/
proc copyMode(src: string, dest: string) {
  var err: syserr = ENOERR;
  copyMode(err, src, dest);
  if err != ENOERR then ioerror(err, "in copyMode " + src, dest);
}

use Filerator;
use Path;
/* Will recursively copy the tree which starts at src into dst, including all
   contents, permissions, and metadata.  dst must not previously exist, this
   function assumes it can create it and any missing parent directories.
   If copySymbolically is true, symlinks will be copied as symlinks, otherwise
   their contents and metadata will be copied. May return an error via an out
   argument.
   error: a syserr used to indicate if an error occurred
   src: the root of the source tree to be copied.
   dest: the root of the destination directory where the tree where the tree is
         to be copied (must not exist prior to this function call).
   copySymbolically: a boolean indicating how to handle symlinks in the
                     source directory.
*/
proc copyTree(out error: syserr, src: string, dest: string, copySymbolically: bool=false) {
  var expectedErrorCases = exists(error, dest);
  if (error != ENOERR) then return; // Some error occurred in checking the existence of dest.
  else if (expectedErrorCases) {
    // dest exists.  That's not ideal.
    error = EEXIST;
    return;
  }
  expectedErrorCases = !isDir(error, src);
  if (error != ENOERR) then return; // Some error occurred in checking src was a directory.
  else if (expectedErrorCases) {
    error = ENOTDIR;
    return;
  }

  // FENCEPOST to avoid needing basename
  var oldMode = getMode(src);
  mkdir(error, dest, mode=oldMode, parents=true);
  if error != ENOERR then return;
  // Create dest

  for filename in listdir(path=src, dirs=false, files=true, listlinks=copySymbolically) {
    // Take care of files in src
    var fileDestName = dest + "/" + filename;
    copy(error, filename, fileDestName, metadata=true);
    if (error != ENOERR) then return;
  }
  for dir in listdir(path=src, dirs=true, files=false, listlinks=copySymbolically) {
    // Now go into src's children
    // walkdirs returns path first if topdown=true.  But that's fine
    for dirname in walkdirs(path=dir, topdown=true, followlinks=copySymbolically) {
      var srcName = realPath(src + "/" + dirname);
      oldMode = getMode(srcName);
      // Create the directory name by replacing src with dest in dirname
      var destName = realPath(dest) + "/" + dirname; // TODO: assumes no trailing "/" atm
      // Make the new directory with the name just created and the same
      // permissions
      writeln(destName);

      mkdir(error, destName, mode=oldMode);
      if error != ENOERR then return;
      // If an error occurred making this directory, exit immediately.
      for filename in listdir(path=srcName, dirs=false, files=true, listlinks=copySymbolically) {
        // Copy the src directory's contents.
        var fileDestName = destName + "/" + filename;
        copy(error, filename, fileDestName, metadata=true);
        if (error != ENOERR) then return;
        // If an error occurred copying this individual file, exit immmediately.
      }
    }
  }
}

/* Will recursively copy the tree which starts at src into dst, including all
   contents, permissions, and metadata.  dst must not previously exist, this
   function assumes it can create it and any missing parent directories.
   If copySymbolically is true, symlinks will be copied as symlinks, otherwise
   their contents and metadata will be copied. May halt with an error message.
   src: the root of the source tree to be copied.
   dest: the root of the destination directory where the tree where the tree is
         to be copied (must not exist prior to this function call).
   copySymbolically: a boolean indicating how to handle symlinks in the
                     source directory.
*/
proc copyTree(src: string, dest: string, copySymbolically: bool=false) {
  var err: syserr = ENOERR;
  copyTree(err, src, dest, copySymbolically);
  if err != ENOERR then ioerror(err, "in copyTree " + src, dest);
}

/* Returns the current working directory for the current locale.
   err: a syserr used to indicate if an error occurred

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(out error: syserr): string {
  extern proc chpl_fs_cwd(ref working_dir:c_string_copy):syserr;

  var ret:string;
  on this {
    var tmp:c_string_copy;
    // c_strings and c_string_copy's can't cross on statements.
    error = chpl_fs_cwd(tmp);
    if (error != ENOERR) {
      ret = "";
    } else {
      // This version of toString steals its operand.  No need to free.
      ret = toString(tmp);
    }
  }
  return ret;
}

/* Returns the current working directory for the current locale. Generates an
   error message if one occurred.

   Note: another task on this locale can change the current working
   directory from underneath this task, so use caution when making use
   of this function in a parallel environment.
*/
proc locale.cwd(): string {
  var err: syserr = ENOERR;
  var ret = cwd(err);
  if err != ENOERR then ioerror(err, "in cwd");
  return ret;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Returns any errors that
   occurred via an out parameter
   name: a string used to attempt to find the file specified.
*/
proc exists(out error: syserr, name: string): bool {
  extern proc chpl_fs_exists(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_exists(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the provided filename corresponds to an existing file, false
   otherwise.  Returns false for broken symbolic links.  Generates an error
   message if one occurred.
   name: a string used to attempt to find the file specified.
*/
proc exists(name: string): bool {
  var err: syserr = ENOERR;
  var ret = exists(err, name);
  if err != ENOERR then ioerror(err, "in exists");
  return ret;
}

/* Returns the group id associated with the file or directory specified by
   name.  Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string used to indicate the file in question
*/
proc getGID(out error: syserr, name: string): int {
  extern proc chpl_fs_get_gid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  error = chpl_fs_get_gid(result, name.c_str());
  return result;
}

/* Returns the group id associated with the file or directory specified by
   name.  May generate an error message.
   name: a string used to indicate the file in question
*/
proc getGID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getGID(err, name);
  if err != ENOERR then ioerror(err, "in getGID");
  return ret;
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   err: a syserr used to indicate if an error occurred during this function
   name: the name of the file that you want to know the permissions of.
*/
proc getMode(out error: syserr, name: string): int {
  extern proc chpl_fs_viewmode(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_viewmode(ret, name.c_str());
  return ret;
}

/* Returns an integer representing the current permissions of the file specified
   by name.  May generate an error message.
   name: the name of the file that you want to know the permissions of.
*/
proc getMode(name: string): int {
  var err:syserr = ENOERR;
  var result = getMode(err, name);
  if err != ENOERR then ioerror(err, "in getMode", name);
  return result;
}

/* Returns the user id associated with the file or directory specified by
   name.  Returns any errors that occurred via an out parameter.
   err: a syserr used to indicate if an error occurred
   name: a string used to indicate the file in question
*/
proc getUID(out error: syserr, name: string): int {
  extern proc chpl_fs_get_uid(ref result: c_int, filename: c_string): syserr;

  var result: c_int;
  error = chpl_fs_get_uid(result, name.c_str());
  return result;
}

/* Returns the user id associated with the file or directory specified by
   name.  May generate an error message.
   name: a string used to indicate the file in question
*/
proc getUID(name: string): int {
  var err: syserr = ENOERR;
  var ret = getUID(err, name);
  if err != ENOERR then ioerror(err, "in getUID");
  return ret;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a directory.
*/
proc isDir(out error:syserr, name:string):bool {
  extern proc chpl_fs_is_dir(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  error = chpl_fs_is_dir(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a directory, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a directory.
*/
proc isDir(name:string):bool {
  var err:syserr;
  var ret = isDir(err, name);
  if err != ENOERR then ioerror(err, "in isDir", name);
  return ret;
}

/* Returns true if the name corresponds to a file, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a file.
*/
proc isFile(out error:syserr, name:string):bool {
  extern proc chpl_fs_is_file(ref result:c_int, name: c_string):syserr;

  var ret:c_int;
  error = chpl_fs_is_file(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a file, false otherwise.
   Generates an error message if one occurs.
   name: a string that could be the name of a file.
*/
proc isFile(name:string):bool {
  var err:syserr;
  var ret = isFile(err, name);
  if err != ENOERR then ioerror(err, "in isFile", name);
  return ret;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.
   name: a string that could be the name of a symbolic link.
*/
proc isLink(out error:syserr, name: string): bool {
  extern proc chpl_fs_is_link(ref result:c_int, name: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_is_link(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a symbolic link, false if not
   or if symbolic links are not supported.  Generates an error message if one
   occurs
   name: a string that could be the name of a symbolic link.
*/
proc isLink(name: string): bool {
  var err:syserr;
  var ret = isLink(err, name);
  if err != ENOERR then ioerror(err, "in isLink", name);
  return ret;
}

/* Returns true if the name corresponds to a mount point, false otherwise.
   err: a syserr used to indicate if an error occurred
   name: a string that could be the name of a mount point.
*/
proc isMount(out error:syserr, name: string): bool {
  extern proc chpl_fs_is_mount(ref result:c_int, name: c_string): syserr;

  if (isFile(name)) {
    // Files aren't mount points.  That would be silly.
    error = ENOERR;
    return false;
  }
  var ret:c_int;
  error = chpl_fs_is_mount(ret, name.c_str());
  return ret != 0;
}

/* Returns true if the name corresponds to a mount point, false otherwise.
   name: a string that could be the name of a mount point.
*/
proc isMount(name: string): bool {
  var err:syserr;
  var ret = isMount(err, name);
  if err != ENOERR then ioerror(err, "in isMount", name);
  return ret;
}

/* These are constant values of the form S_I[R | W | X][USR | GRP | OTH],
   S_IRWX[U | G | O], S_ISUID, S_ISGID, or S_ISVTX, where R corresponds to
   readable, W corresponds to writable, X corresponds to executable, USR and
   U correspond to user, GRP and G correspond to group, OTH and O correspond
   to other, directly tied to the C idea of these constants.  They are intended
   for use with functions that alter the permissions of files or directories.
*/
extern const S_IRUSR: int;
extern const S_IWUSR: int;
extern const S_IXUSR: int;
extern const S_IRWXU: int;

extern const S_IRGRP: int;
extern const S_IWGRP: int;
extern const S_IXGRP: int;
extern const S_IRWXG: int;

extern const S_IROTH: int;
extern const S_IWOTH: int;
extern const S_IXOTH: int;
extern const S_IRWXO: int;

extern const S_ISUID: int;
extern const S_ISGID: int;
extern const S_ISVTX: int;

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Returns any errors that occurred via an out parameter

   :arg err: a syserr used to indicate if an error occurred
   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(out error: syserr, name: string, mode: int = 0o777,
           parents: bool=false) {
  extern proc chpl_fs_mkdir(name: c_string, mode: int, parents: bool):syserr;

  error = chpl_fs_mkdir(name.c_str(), mode, parents);
}

/* Attempt to create a directory with the given path.  If parents is true,
   will attempt to create any directory in the path that did not previously
   exist.  Generates an error message if one occurred.

   :arg name: the name of the directory to be created, fully specified.
   :arg mode: an integer representing the permissions desired for the file in
              question. See description of the provided constants for
              potential values.
   :arg parents: a boolean indicating if parent directories should be created.
                 If set to false, any nonexistent parent will cause an error to
                 occur.

   Important note: In the case where parents is true, there is a potential
   security vulnerability.  Checking whether parent directories exist and
   creating them if not are separate events. So even if parents==true and a
   parent directory didn't exist before this function is called but does exist
   afterward, it's still not necessarily true that this function created that
   parent. Some other concurrent operation could have done so.
*/
proc mkdir(name: string, mode: int = 0o777, parents: bool=false) {
  var err: syserr = ENOERR;
  mkdir(err, name, mode, parents);
  if err != ENOERR then ioerror(err, "in mkdir", name);
}

/* Renames the file specified by oldname to newname, returning an error
   if one occurred.  The file is not opened during this operation.
   error: a syserr used to indicate if an error occurred during renaming.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(out error: syserr, oldname, newname: string) {
  extern proc chpl_fs_rename(oldname: c_string, newname: c_string):syserr;

  error = chpl_fs_rename(oldname.c_str(), newname.c_str());
}

/* Renames the file specified by oldname to newname, generating an error message
   if one occurred.  The file is not opened during this operation.
   oldname: current name of the file
   newname: name which should refer to the file in the future.*/
proc rename(oldname, newname: string) {
  var err:syserr = ENOERR;
  rename(err, oldname, newname);
  if err != ENOERR then ioerror(err, "in rename", oldname);
}

/* Removes the file or directory specified by name, returning an error
   if one occurred via an out parameter.
   err: a syserr used to indicate if an error occurred during removal
   name: the name of the file/directory to remove */
proc remove(out error: syserr, name: string) {
  extern proc chpl_fs_remove(name: c_string):syserr;

  error = chpl_fs_remove(name.c_str());
}

/* Removes the file or directory specified by name, generating an error message
   if one occurred.
   name: the name of the file/directory to remove */
proc remove(name: string) {
  var err:syserr = ENOERR;
  remove(err, name);
  if err != ENOERR then ioerror(err, "in remove", name);
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.
   err: a syserr used to indicate if an error occurred during comparison
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(out error: syserr, file1: string, file2: string): bool {
  extern proc chpl_fs_samefile_string(ref ret: c_int, file1: c_string, file2: c_string): syserr;

  var ret:c_int;
  error = chpl_fs_samefile_string(ret, file1.c_str(), file2.c_str());
  return ret != 0;
}

/* Returns true if both pathnames refer to the same file or directory
   (utilizing operating system operations rather than string ones), returns
   false otherwise.  May generate an error message.
   file1, file2: string representations of paths to be compared.
*/
proc sameFile(file1: string, file2: string): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1, file2);
  return result;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.
*/
proc sameFile(out error: syserr, file1: file, file2: file): bool {
  extern proc chpl_fs_samefile(ref ret: c_int, file1: qio_file_ptr_t,
                               file2: qio_file_ptr_t): syserr;

  var ret:c_int;
  if (is_c_nil(file1._file_internal) || is_c_nil(file2._file_internal)) {
    // Implementation note on program design tradeoffs:
    // I could use file.check() here.  That would not rely on the understanding
    // of file's internals.  However, it would cause this method to either
    // return error messages or halt, depending on the error encountered.
    // This check could be moved to the version w/o the err argument, but if
    // someone called this function w/o going through that, we'd lose the
    // check.  Also, we already must make use of the record internals to do the
    // inner comparison (since the record is a chapel construct), so there's no
    // additional harm.

    // The file is referencing a null file.  We'll get a segfault if we
    // continue.
    error = EBADF;
    return false; // This part isn't as important as the error.
  }

  error = chpl_fs_samefile(ret, file1._file_internal, file2._file_internal);
  return ret != 0;
}

/* Same as the above function, but taking file records instead of string
   pathnames as arguments.  May generate an error message.
*/
proc sameFile(file1: file, file2: file): bool {
  var err:syserr = ENOERR;
  var result = sameFile(err, file1, file2);
  if err != ENOERR then ioerror(err, "in sameFile " + file1.path, file2.path);
  return result;
}

/* Create a symbolic link pointing to oldName named newName.  May generate an
   error message.
   err: a syserr used to indicate if an error occurred during creation
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(out error: syserr, oldName: string, newName: string) {
  extern proc chpl_fs_symlink(orig: c_string, linkName: c_string): syserr;

  error = chpl_fs_symlink(oldName.c_str(), newName.c_str());
}

/* Create a symbolic link pointing to oldName named sym.  May generate an error
   message.
   oldName: the source file to be linked
   newName: the location the symbolic link should live
*/
proc symlink(oldName: string, newName: string) {
  var err:syserr = ENOERR;
  symlink(err, oldName, newName);
  if err != ENOERR then ioerror(err, "in symlink " + oldName, newName);
}

/* Sets the file creation mask of the current process to mask, and returns
   the previous value of the file creation mask.
   mask: the file creation mask to use now.
*/
proc umask(mask: int): int {
  extern proc chpl_fs_umask(mask: mode_t): mode_t;

  return chpl_fs_umask(mask.safeCast(mode_t));
}


Forgot newline at end of file.

Forgot newline at end of file.

use FileSystem;

var masks: [1..3] int = [0o777, 0o744, 0o755];

var startingMask = 0o700;
var oldMask = here.umask(startingMask);

forall i in masks.domain {
  var prevMask = here.umask(masks[i]);
  if (prevMask != startingMask) {
    writeln("Not task safe!");
  }
}


Removing comments that will end up in associative primer

var names = ["alice", "bob", "carl", "dana", "eric"];

var dom : domain(string);
var data : [dom] int;

for name in names do data[name] = name.length;

for name in names do
  if !dom.member(name) {
    writeln("FAILURE: name added via array did not exist in backing domain");
    exit(1);
  }

writeln("SUCCESS");

/*
var illegal : [dom] string;

data["foo"] = 42;
illegal["badKey"] = "you can't do this with multiple domains";
*/