Issue 3-23, June 10, 1998

Be Engineering Insights: Getting Mounted

By Robert Polic

Mounting a volume under the BeOS requires identifying and passing three parameters to the mount() function. The first parameter is the file system that the volume was initialized with (for the mount to succeed, a file system add-on of the same name must exist in the /boot/beos/system/add-ons/kernel/file_systems directory).

The second parameter is the mount point—the location that the newly mounted volume will be referenced from (typically this is at root level but in theory can be anywhere).

The last parameter is the device driver used to access the volume.

NOTE: I lied, there are actually *six* parameters defined for the mount() prototype in /boot/develop/headers/posix/unistd.be.h but the last three are currently not used and should be set to 0.

Device drivers for accessing volumes come in two flavors: physical and logical. A physical driver allows complete and total access to all blocks on the device. A physical driver can only be used to mount a device that does not contain a partition map or has multiple sessions. A logical driver allows only indirect access to a range of blocks on the device and is used for mounting individual partitions and sessions.

Physical (raw) drivers are installed by the system at boot time for all recognizable devices. To look for and install raw drivers for devices after boot, there are two "rescan" drivers, /dev/disk/scsi/rescan and /dev/disk/ide/rescan...

if ((dev = open("/dev/disk/ide/rescan",0)) >=0) {
  ioctl(dev, B_SCSI_SCAN_FOR_DEVICES);
  close(dev);
}

Logical drivers are constructed by creating a new device and using an ioctl to set the range of accessible blocks...

char    path[256];
char    tmp[256];
int32   dev;
partition_info  partition;

strncpy(tmp, DEVICE_PATH,
  strlen(DEVICE_PATH) - strlen("/raw"));
sprintf(path, "%s/%d_%d", tmp, session, partition);

if ((dev = create(path, 0666)) >= 0) {
  partition.offset = offset;
    /* offset from start of physical device in bytes */
  partition.size = size;
    /* length of partition in bytes */
  partition.logical_block_size = block_size;
    /* physical block size*/
  partition.session = session;      /* session ID */
  partition.partition = partition;  /* partition ID */
  strcpy(partition.device, DEVICE_PATH);
  ioctl(dev, B_SET_PARTITION, &partition);
  close(dev);
}
else {
  // error creating logical device
}

The BeOS comes with a number of add-ons to help determine session offsets, what type of partition map, if any, and what types of file systems exist on a device. These add-ons are located in the /boot/beos/system/add-ons/drive_setup/ directory. To use these add-ons, you first need to determine if the device is a CD. To do this, use the B_GET_GEOMETRY ioctl call and test against B_CD...

bool    is_cd;
int32   dev;
device_geometry  geometry;

if ((dev = open(DEVICE_PATH, 0)) >= 0) {
  if (ioctl(dev, B_GET_GEOMETRY, &geometry) == B_NO_ERROR) {
    is_cd = (geometry.device_type == B_CD);
  }
  else {
    // media not loaded
  }
  close(dev);
}
else {
  // error accessing device
}

If the device is indeed a CD, use the session add-on to locate the offset, length, and type of each session...

#define DS_SESSION_ADDONS   "drive_setup/session/"
#define DS_GET_NTH_SESSION  "ds_get_nth_session"

typedef struct {
  uint64  offset;;    /* in device blocks */
  uint64  blocks;    /* number of blocks in session */
  bool    data;      /* true: data session, session */
} session_data;

bool        have_session = false;
int32       block_size;
int32       dev;
int32       index = 0;
BDirectory  dir;
BEntry      entry;
BPath       path;
device_geometry  geometry;
image_id         image;
session_data     session;
status_t    (*ds_get_nth_session)(int32, int32, int32,
  session_data*);

if (ioctl(dev, B_GET_GEOMETRY, &geometry) == B_NO_ERROR) {
  block_size = geometry.bytes_per_sector;
  find_directory(B_BEOS_ADDONS_DIRECTORY, &path);
  dir.SetTo(path.Path());
  dir.FindEntry(DS_SESSION_ADDONS, &entry);
  dir.SetTo(&entry);
  if (dir.InitCheck() == B_NO_ERROR) {
    dir.Rewind();
    while (!have_session) {
      /* try loading the next add-on */
      if (dir.GetNextEntry(&entry) >= 0) {
        entry.GetPath(&path);
        if ((image = load_add_on(path.Path())) >= 0) {
          if (get_image_symbol(image, DS_GET_NTH_SESSION,
            B_SYMBOL_TYPE_TEXT, &ds_get_nth_session) >= 0) {
            have_session = true;
            while ((*ds_get_nth_session)(dev, index,
              block_size, session) == B_NO_ERROR) {
              ...
              index++;
            }
          }
          unload_add_on(image);
        }
      }
      else
        break;
    }
  }
}

If the device is not a CD or for each session of a multi-session CD, use the partition add-ons to determine if there is a partition map and, if so, the offset; and length for each individual partition...

#define DS_PARTITION_ID    "ds_partition_id"
#define DS_PARTITION_MAP   "ds_get_nth_map"

typedef struct {
  char    partition_name[B_FILE_NAME_LENGTH];
  char    partition_type[B_FILE_NAME_LENGTH];
  char    file_system_short_name[B_FILE_NAME_LENGTH];
  char    file_system_long_name[B_FILE_NAME_LENGTH];
  char    volume_name[B_FILE_NAME_LENGTH];
  char    mounted_at[B_FILE_NAME_LENGTH];
  uint32  logical_block_size;
  uint64  offset;;    /* in logical blocks from start of session */
  uint64  blocks;    /* in logical blocks */
  bool    hidden;    /* non-file system partition */
  bool    reserved1;
  uint32  reserved2;
} partition_data;

  bool      have_map = false;
  bool      (*ds_partition_id)(uchar *, int32);
  uchar     *block;
  int32     index = 0;
  BDirectory  dir;
  BEntry      add_on;
  BEntry      entry;
  BPath       path;
  image_id    image;
  partition_data  partition;
  status_t    (*ds_get_nth_map)(int32, uchar*, uint64,
    int32, int32, partition_data*);

  block = (uchar *)malloc(block_size);
  lseek(dev, session->offset; * block_size, 0);
  if (read(dev, block, block_size) == block_size) {
    find_directory(B_BEOS_ADDONS_DIRECTORY, &path);
    dir.SetTo(path.Path());
    dir.FindEntry(DS_PART_ADDONS, &entry);
    dir.SetTo(&entry);
    if (dir.InitCheck() == B_NO_ERROR) {
      dir.Rewind();
      while (!have_map) {
        /* try loading the next add-on */
        if (dir.GetNextEntry(&add_on) >= 0) {
          add_on.GetPath(&path);
          if ((image = load_add_on(path.Path())) >= 0) {
            if (get_image_symbol(image, DS_PARTITION_ID,
              B_SYMBOL_TYPE_TEXT, &ds_partition_id) >= 0) {
              /* add-on recognize this map? */
              if ((*ds_partition_id)(block, block_size)) {
                have_map = true;
                if (get_image_symbol(image, DS_PARTITION_MAP,
                  B_SYMBOL_TYPE_TEXT, &ds_get_nth_map) >= 0) {
                  /* gather info about each partition */
                  while ((*ds_get_nth_map)(dev, block,
                    session->offset;, fBlockSize, index,
                    &partition) == B_NO_ERROR) {
                    ...
                    index++;
                  }
                }
              }
            }
          }
          unload_add_on(image);
        }
        else
          break;
      }
    }
    free(block);
  }

The file system add-ons are used to determine the type of file system a partition, session or raw device was initialized with...

#define DS_FS_ADDONS    "drive_setup/fs/"
#define DS_FS_ID        "ds_fs_id"

bool    (*ds_fs_id)(partition_data*, int32, uint64, int32);
bool    have_fs = FALSE;
BDirectory  dir;
BEntry      entry;
BPath       path;
image_id    image;

find_directory(B_BEOS_ADDONS_DIRECTORY, &path);
dir.SetTo(path.Path());
dir.FindEntry(DS_FS_ADDONS, &entry);
dir.SetTo(&entry);
if (dir.InitCheck() == B_NO_ERROR) {
  dir.Rewind();
  while (!have_fs) {
    /* try loading next add-on */
    if (dir.GetNextEntry(&entry) >= 0) {
      entry.GetPath(&path);
      if ((image = load_add_on(path.Path())) >= 0) {
        if (get_image_symbol(image, DS_FS_ID,
          B_SYMBOL_TYPE_TEXT, &ds_fs_id) = 0) {
          /* add-on recognize this file system? */
          if ((*ds_fs_id)(partition, dev, session->offset;,
            block_size)) {
            ...
            have_fs = TRUE;
          }
        }
        unload_add_on(image);
      }
    }
    else
      break;
  }
}

If the file system was successfully identified by the add-on, the file_system_short_name, file_system_long_name and volume_name fields of the partition_data structure will be filled in with the correct values. The volume name can be used to create the mount point and the file_system_short_name is used for the mount function...

char       mount_point[B_FILE_NAME_LENGTH];
char       tmp[B_FILE_NAME_LENGTH];
int32      index = 1;
int32      loop;;
BDirectory directory;

/* if there is no volume name, use a default */
if (!strlen(partition.volume_name))
  sprintf(tmp, "/disk");
else {
  /* create mount point at root */
  sprintf(tmp, "/%s", partition.volume_name);
  /* convert any forward slashes to ':' */
  for (loop; = 1;
       loop; < strlen(partition.volume_name);
       loop;++)
    if (tmp[loop;] == '/')
      tmp[loop;] = ':';
  }
}
/* make sure name is unique or if not unique, */
/* make sure existing directory is empty */
strcpy(mount_point, tmp);
while (1) {
  if (mkdir(mount_point, 0777) >= 0)
    break;
  directory.SetTo(mount_point);
  if ((!directory.InitCheck()) &&
    (!directory.CountEntries()))
    break;
  sprintf(mount_point, "%s%d", tmp, index++);
}
/* try mounting */
if (mount(partition.file_system_short_name, mount_point,
  device_path, 0, NULL, 0) < 0) {
  /* error mounting device */
}

Developers' Workshop: MIDI Masterpieces

By Doug Fulton
Q:

Having just read a book on baroque counterpoint I'm now ready to create masterpieces (in MIDI format) that are indistinguishable from those of Bach, Handel, and Cannabich (my favorite!). However, I can't play a musical instrument and I'm not that great at reading music, either. Can I create MIDI files by typing them in?—Dick "Really Scary Spice" Wagner, Bayreuth, Germany

A:

Well, Dick, you've dropped in at the right moment. I just popped a little MIDI-generating language out of the oven. Mmmmm, smell the aroma of copy-and-paste composition churned out by tone-deaf hobbyists whose musical education ended right after they learned that tritones sound really creepy.

Before we go any further, let's do some weeding. If you don't need to generate MIDI, or if you'd rather use a GUI to enter data, leave the room. Or just skip down to the final paragraph where we pay our respects to one of the creators of the Headspace General MIDI synthesizer.

For the rest of you, you'll soon be generating chromatic scales as easily as...

Xchannel(1);
Xvoice(B_BANJO);
for (int i = 0; i < 12; i++ ) {
  Xnote(c4+1, 1.0, 0.5, 0.0);
  Xinc(1.0);
}

Talk about fun. How about this:

Xchannel(1);
Xvoice(B_OBOE);
Xnote(c5, 4.0, 0.5, 0.0); // play a note
XskewA(0.0);              // start a pitch bend trajectory
XampA(1.0);               // start an amplitude trajectory
Xinc(4.0);                // wait for beats
XskewZ(-1.0);             // stop the pitch bend trajectory
XampZ(0.0);               // stop the amplitude trajectory

This generates an oboe tone on c5 (the c above middle c), and then, over the next four beats (seconds, by default) bends the note down to bf4 and fades the note out. One more; this produces a cloud of swirly metal for about 20 seconds:

Xvoice(B_REVERSE_CYMBAL);
for (int i = 0; i < 100; i++) {
  Xabs(1.5);
  Xinc((rand()%100)/5.0);
  Xnote((rand()%30)+40, (rand()%100)/40.0+.2,
    ((rand()%100)/200.0)+.3, 0.);
}

Everything you need to scare your dog is contained in this tar file:

ftp://ftp.be.com/pub/samples/midi_kit/obsolete/EdMidi.zip

When you unpack the tar file, it will create a directory called "EdMidi" that contains the following seven files in three parts:

The process goes something like this:

  1. Copy and modify lbjtest.cpp, mixing and matching the Xmumble() macros that are listed in langMacros.h (and described in edmidi.lang).

  2. Compile lbjtest.cpp as, say, LbjTest.

  3. Run LbjTest and capture the output (for convenience, I use the extension ".lbj", but it isn't required): $ LbjTest > test1.lbj

  4. If you want, you can edit the test1.lbj file. You can even create such files by hand by using the language format described in Part 1 of edmidi.lang.

  5. Pass test1.lbj to edmidi: $ edmidi test1.lbj

  6. edmidi will think for a little bit, and then will produce the file "test.midi" (the filename is declared in the lbjtest.cpp file). It will also play the file, spitting out statistics as it does, and prompting you to replay:

    $ edmidi test1.lbj
    Last timestamp: 23.225000
    Max notes: 74
    Writing midi test.midi
    Setting sampling rate 44100
    Replay [y]?
    

If you pass it a trailing "r" argument, edmidi will record the playback to a sound file:

$ edmidi lbjtest.lbj r
Recording to
Last timestamp: 23.225000
Max notes: 74
Writing midi test.midi
Writing sound lbjtest.snd
Setting sampling rate 44100
Replay [y]?

edmidi will also let you mute and solo individual channels. It also has a number of other options that you can dump by typing 'edmidi' with no argument.

Now to the serious and unfortunate part of the column. Last Friday, Jim Nitchalls, one of the creators of the Headspace synthesizer, died. I met Jim eons ago when we both were working in or around Electronic Arts. Every couple of years since then, our paths would cross with us on them at the same time; one particularly cogent collision is when he called me up two years ago to see if Be could use a General MIDI synthesizer.

You may have seen Jim, possibly even chatted with him, at one of the Be Developer Conferences. If you've never actually used the Headspace synthesizer, you should try it sometime. Utter Jim's name and generate a burst of voice #126 (0-based).


Our New Developer Programs

By Jean-Louis Gassée

If you haven't already visited the Developer section of our Web site, I'd like to direct your attention to

http://www.be.com/developers/developer_program/devprogram_overview.html.

As the end of the URL implies, it contains an overview of our new tiered developer programs, as well as pointers to more detailed information.

This should provide you with the information you need in order to decide which of the three programs is right for you. For my part, I'd like to review the philosophy behind these changes, as well as what we are trying to accomplish for the Be community.

As obvious as we think it is, it bears repeating that an operating system is useless in itself. Developers make it useful with the applications they build for it. Only after we have applications that proclaim the strengths of the BeOS (and we believe we have some already) can we think of the other constituencies in the Be community—our customers, shareholders, business partners, and employees.

So, why do we charge for developer programs if we believe developers are so important? Don't we risk turning away the next Mitch Kapor, or even the next Paul Allen?

As you'll see in looking through the new programs, we are trying to juggle a contradiction. We want to make sure enthusiasts can try their hand at writing code for the BeOS in the largest possible numbers at the least possible expense. That requires access to a computer and to a copy of the BeOS. There is no annual fee at this level. Our Web site and newsgroups provide technical documentation, and access to libraries and newsletters. Your creations will be featured in the BeWare section of our Web site.

For people or companies who make a business of writing BeOS commercial applications we want to offer more support. These developers get more technical and marketing assistance, their CodeWarrior tools are included in the $299 annual fee, and we'll enthusiastically distribute your commercial software through BeDepot.com.

Developers with a large organization can use the $599 Corporate program with even more technical and marketing support, along with one copy of the BeOS and all upgrades released during the subscription year, a copy of the current IDE, CodeWarrior, and PackageBuilder (which lets developers build packages that can be installed with SoftwareValet). Corporate developers can also buy additional seats (BeOS, CodeWarrior, and PackageBuilder tools) "wholesale" at $99 each.

Further provisions ease the transition for existing developers, especially CodeWarrior customers. It's that simple.

We've already received some feedback on the letter we sent to existing developers. One suggestion was to use Silver, Gold, and Platinum labels for the three tiers. Personally, I prefer the suggestion from a Be executive (who requested anonymity): Geek, Midnight Programmer, and Commercial, if I remember correctly.

Our hope is that the no-fee program will continue to attract individuals who are intrigued with the BeOS and want to try writing an application. If our product and our company do a good job for them, they'll go up one level, and make a business out of their experiment. If we all do what we're supposed to do, they'll go to the top—and us with them. Or else they'll happily make shareware because that's what they like to do.

We learned a long time ago it's not our place to tell a developer what to do. It works much better the other way around when programmers who actually struggle with the platform and the marketplace tell us what to do with our APIs and our marketing programs. Their suggestions are always colorful and useful.

Speaking of hue and abuse, we're off to NYC next week for PC Expo. If you're in town, look for us at the friendly Jacob Javits Convention Center. We'll see if New York City unions and waiters are a match for French ones. More exciting still, we'll be showing off the newest BeOS applications running on Release 3.1.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.