Compare commits

..

10 Commits

Author SHA1 Message Date
b7894f10ca fix: double free when ref < 0 2022-05-06 14:53:13 -04:00
9392eecab1 fix: gitlab order 2022-05-06 14:33:02 -04:00
cc370cedbf fix: fflush 2022-05-06 14:23:46 -04:00
931f763e12 feat: pbx 2022-05-06 13:54:16 -04:00
d231b64544 feat: tu 2022-05-05 22:20:59 -04:00
f5e321c443 fix: cmd start with \n will terminate the server 2022-05-05 14:08:52 -04:00
bcabef7925 feat: server 2022-05-04 20:51:21 -04:00
53bfd71f9f feat: main 2022-05-04 17:04:25 -04:00
8d089808b6 feat: add hw5 doc 2022-05-04 15:13:08 -04:00
d167d81dfc fix: gitignore 2022-05-04 15:10:28 -04:00
10 changed files with 2586 additions and 58 deletions

4
.gitignore vendored
View File

@ -3,5 +3,5 @@
*.d *.d
*.o *.o
*.out *.out
bin/* bin/
build/* build/

View File

@ -9,8 +9,8 @@ before_script:
- make clean all -C ${HW_DIR} - make clean all -C ${HW_DIR}
stages: stages:
- build - build
- run
- test - test
- run
build: build:
stage: build stage: build
script: script:

593
hw5-doc/README.md Normal file
View File

@ -0,0 +1,593 @@
# Homework 5 - CSE 320 - Spring 2022
#### Professor Eugene Stark
### **Due Date: Friday 5/6/2022 @ 11:59pm**
## Introduction
The goal of this assignment is to become familiar with low-level POSIX
threads, multi-threading safety, concurrency guarantees, and networking.
The overall objective is to implement a server that simulates the behavior
of a Private Branch Exchange (PBX) telephone system.
As you will probably find this somewhat difficult, to grease the way
I have provided you with a design for the server, as well as binary object
files for almost all the modules. This means that you can build a
functioning server without initially facing too much complexity.
In each step of the assignment, you will replace one of my
binary modules with one built from your own source code. If you succeed
in replacing all of my modules, you will have completed your own
version of the server.
For this assignment, there are four modules to work on:
* Server initialization (`main.c`)
* Server module (`server.c`)
* PBX module (`pbx.c`)
* TU (telephone unit) module (`tu.c`)
It is probably best if you work on the modules in the order listed.
You should turn in whatever modules you have worked on.
Though the exact details are not set at this time, I expect that your
code will be compiled and tested in the following configurations:
1. Blackbox tests using your `main.c` and your `server.c` (if implemented,
otherwise my `server.o`), and my `pbx.o` and `tu.o`.
2. Blackbox tests using the modules you implemented, with mine for the rest.
3. Unit tests on your `pbx.c` in isolation.
4. Unit tests on your `tu.c` in isolation.
For each configuration, you will receive points for whatever test cases in
that configuration are passed. If you are able to achieve some reasonable
functionality in a module, it will probably be beneficial to submit it.
It is probably not a good strategy to submit modules that are completely
broken.
It is definitely not a good strategy to submit code that does not compile,
as this might prevent you from getting any points at all!
### Takeaways
After completing this homework, you should:
* Have a basic understanding of socket programming
* Understand thread execution, mutexes, and semaphores
* Have an understanding of POSIX threads
* Have some insight into the design of concurrent data structures
* Have enhanced your C programming abilities
## Hints and Tips
* We strongly recommend you check the return codes of all system
calls. This will help you catch errors.
* **BEAT UP YOUR OWN CODE!** Exercise your modules with rapid and
concurrent calls to ensure that they do not crash or deadlock.
Ideally, besides basic sequential tests, you would write multi-threaded
test drivers to achieve this. We will use tests of this nature in grading.
* Your code should **NEVER** crash. We will be deducting points for
each time your program crashes during grading. Make sure your code
handles invalid usage gracefully.
* You should make use of the macros in `debug.h`. In non-debugging,
"production" use, your code should basically be silent, except perhaps
to emit a one-line error message just before terminating in case a fatal
error situation requires an abort.
**FOLLOW THIS GUIDELINE!** `make debug` is your friend.
> :scream: **DO NOT** modify any of the header files provided to you in the base code.
> These have to remain unmodified so that the modules can interoperate correctly.
> We will replace these header files with the original versions during grading.
> You are of course welcome to create your own header files that contain anything
> you wish.
## Helpful Resources
### Textbook Readings
You should make sure that you understand the material covered in
chapters **11.4** and **12** of **Computer Systems: A Programmer's
Perspective 3rd Edition** before starting this assignment. These
chapters cover networking and concurrency in great detail and will be
an invaluable resource for this assignment.
### pthread Man Pages
The pthread man pages can be easily accessed through your terminal.
However, [this opengroup.org site](http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html)
provides a list of all the available functions. The same list is also
available for [semaphores](http://pubs.opengroup.org/onlinepubs/7908799/xsh/semaphore.h.html).
## Getting Started
Fetch and merge the base code for `hw5` as described in `hw0`. You can
find it at this link: https://gitlab02.cs.stonybrook.edu/cse320/hw5.
Remember to use the `--stategy-option=theirs` flag for the `git merge`
command to avoid merge conflicts in the Gitlab CI file.
### The Base Code
Here is the structure of the base code:
```
.
├── demo
│ └── pbx
├── hw5.sublime-project
├── include
│ ├── debug.h
│ ├── pbx.h
│ ├── server.h
│ └── tu.h
├── lib
│ └── pbx.a
├── Makefile
├── src
│ ├── globals.c
│ ├── main.c
│ ├── pbx.c
│ ├── server.c
│ └── tu.c
└── tests
├── basecode_tests.c
├── script_tester.c
└── __test_includes.h
```
The base code consists of header files that define module interfaces,
a library `pbx.a` containing binary object code for my
implementations of the modules, a source code file `globals.c` that
contains definitions of some global variables, and a source code file `main.c`
that contains a stub for function `main()`. The `Makefile` is
designed to compile any existing source code files and then link them
against the provided library. The result is that any modules for
which you provide source code will be included in the final
executable, but modules for which no source code is provided will be
pulled in from the library. The `pbx.a` library was compiled
without `-DDEBUG`, so it does not produce any debugging printout.
Also provided is a demonstration binary `demo/pbx`. This executable
is a complete implementation of the PBX server, which is intended to help
you understand the specifications from a behavioral point of view.
It also does not produce any debugging printout, however.
Most of the detailed specifications for the various modules and functions
that you are to implement are provided in comments that precede stubs for
these functions in the various source files. In the interests of brevity and
avoiding redundancy, those specifications are not reproduced in this document.
Nevertheless, the information they contain is very important, and constitutes
the authoritative specification of what you are to implement.
> :scream: The various functions and variables defined in the header files
> constitute the **entirety** of the interfaces between the modules in this program.
> Use these functions and variables as described and **do not** introduce any
> additional functions or global variables as "back door" communication paths
> between the modules. If you do, the modules you implement will not interoperate
> properly with my implementations, and it will also likely negatively impact
> our ability to test your code.
The function stubs that appear in the various source files have been
commented out in the basecode. This is important, because when the linker
does not see any definitions for these functions, it will link in binaries
from the `pbx.a` library. If you choose to implement a function in one of the
modules, you must uncomment the stubs for all the other functions in that module,
otherwise the linker will still link the library version of that module,
which will result in "multiply defined" errors at link time.
## The PBX Server: Overview
"PBX" is a simple implementation of a server that simulates a PBX
telephone system. A PBX is a private telephone exchange that is used
within a business or other organization to allow calls to be placed
between telephone units (TUs) attached to the system, without having to
route those calls over the public telephone network. We will use the
familiar term "extension" to refer to one of the TUs attached to a PBX.
The PBX system provides the following basic capabilities:
* *Register* a TU as an extension in the system.
* *Unregister* a previously registered extension.
Once a TU has been registered, the following operations are available
to perform on it:
* *Pick up* the handset of a registered TU. If the TU was ringing,
then a connection is established with a calling TU. If the TU was not
ringing, then the user hears a dial tone over the receiver.
* *Hang up* the handset of a registered TU. Any call in progress is
disconnected.
* *Dial* an extension on a registered TU. If the dialed extension is
currently "on hook" (*i.e.* the telephone handset is on the switchhook),
then the dialed extension starts to ring, indicating the presence of an
incoming call, and a "ring back" notification is played over the receiver
of the calling extension. Otherwise, if the dialed extension is "off hook",
then a "busy signal" notification is played over the receiver of the
calling extension.
* *Chat* over the connection made when one TU has dialed an extension
and the called extension has picked up.
The basic idea of these operations should be simple and familiar,
since I am sure that everyone has used a telephone system :wink:.
However, we will need a rather more detailed and complete specification
than just this simple overview.
## The PBX Server: Details
Our PBX system will be implemented as a multi-threaded network server.
When the server is started, a **master server** thread sets up a socket on
which to listen for connections from clients (*i.e.* the TUs). When a network
connection is accepted, a **client service thread** is started to handle requests
sent by the client over that connection. The client service thread registers
the client with the PBX system and is assigned an extension number.
The client service thread then executes a service loop in which it repeatedly
receives a **message** sent by the client, performs some operation determined
by the message, and sends one or more messages in response.
The server will also send messages to a client asynchronously as a result of actions
performed on behalf of other clients.
For example, if one client sends a "dial" message to dial another extension,
then if that extension is currently on-hook it will receive an asynchronous
"ring" message, indicating that the ringer is to be activated.
Messages from a client to a server represent commands to be performed.
Except for messages containing "chat" sent from a connected TU, every message
from the server to a client will consist of a notification of the current state
of that client, as it is currently understood by the server.
Usually these messages will inform the client of a state change that has occurred
as a result of a command the client sent, or of an asynchronous state change
that has occurred as a result of a command sent by some other client.
If a command sent by a client does not result in any state change, then the
response sent by the server will simply report the unchanged state.
> :nerd: One of the basic tenets of network programming is that a
> network connection can be broken at any time and the parties using
> such a connection must be able to handle this situation. In the
> present context, the client's connection to the PBX server may
> be broken at any time, either as a result of explicit action by the
> client or for other reasons. When disconnection of the client is
> noticed by the client service thread, the server acts as though the client
> had sent an explicit **hangup** command, the client is then unregistered from
> the PBX, and the client service thread terminates.
The PBX system maintains the set of registered clients in the form of a mapping
from assigned extension numbers to clients.
It also maintains, for each registered client, information about the current
state of the TU for that client. The following are the possible states of a TU:
* **On hook**: The TU handset is on the switchhook and the TU is idle.
* **Ringing**: The TU handset is on the switchhook and the TU ringer is
active, indicating the presence of an incoming call.
* **Dial tone**: The TU handset is off the switchhook and a dial tone is
being played over the TU receiver.
* **Ring back**: The TU handset is off the switchhook and a "ring back"
signal is being played over the TU receiver.
* **Busy signal**: The TU handset is off the switchhook and a "busy"
signal is being played over the TU receiver.
* **Connected**: The TU handset is off the switchhook and a connection has been
established between this TU and the TU at another extension. In this state
it is possible for users at the two TUs to "chat" with each other over the
connection.
* **Error**: The TU handset is off the switchhook and an "error" signal
is being played over the TU receiver.
Transitions between TU states occur in response to messages received from the
associated client, and sometimes also asynchronously in conjunction with
state transitions of other TUs.
The list below specifies all the possible transitions between states that a TU
can perform. The arrival of any message other than those explicitly listed for
each particular state does not cause any transition between states to take place.
* When in the **on hook** state:
* A **pickup** message from the client will cause a transition to the **dial tone** state.
* An asynchronous transition to the **ringing** state is also possible from this state.
* When in the **ringing** state:
* A **pickup** message from the client will cause a transition to the **connected** state.
Simultaneously, the calling TU will also make an asynchronous transition from the
**ring back** state to the **connected** state. The PBX will establish a connection
between these two TUs, which we will refer to as *peers* as long as the connection
remains established.
* An asynchronous transition to the **on hook** state is also possible from this state.
This would occur if the calling TU hangs up before the call is answered.
* When in the **dial tone** state:
* A **hangup** message from the client will cause a transition to the **on hook** state.
* A **dial** message from the client will cause a transition to either the **error**,
**busy signal**, or **ring back** states, depending firstly on whether or not a TU
is currently registered at the dialed extension, and secondly, whether the TU at
the dialed extension is currently in the **on hook** state or in some other state.
If there is no TU registered at the dialed extension, the transition will be to the
**error** state.
If there is a TU registered at the dialed extension, then if that extension is
currently not in the **on hook** state, then the transition will be to the
**busy signal** state, otherwise the transition will be to the **ring back** state.
In the latter case, the TU at the dialed extension makes an asynchronous transition
to the **ringing** state, simultaneously with the transition of the calling TU to the
**ring back** state.
* When in the **ring back** state:
* A **hangup** message from the client will cause a transition to the **on hook** state.
Simultaneously, the called TU will make an asynchronous transition from the **ringing**
state to the **on hook** state.
* An asynchronous transition to the **connected** state (if the called TU picks up)
or to the **dial tone** state (if the called TU unregisters) is also possible from this state.
* When in the **busy signal** state:
* A **hangup** message from the client will cause a transition to the **on hook** state.
* When in the **connected** state:
* A **hangup** message from the client will cause a transition to the **on hook** state.
Simultaneously, the peer TU will make a transition from the **connected** state to
the **dial tone** state.
* An asynchronous transition to the **dial tone** state is also possible from this state.
This would occur if the peer TU were to hang up.
* When in the **error** state:
* A **hangup** message from the client will cause a transition to the **on hook** state.
Messages are sent between the client and server in a text-based format,
in which each message consists of a single line of text, the end of which is
indicated by the two-byte line termination sequence `"\r\n"`.
There is no *a priori* limitation on the length of the line of text that is sent
in a message.
The possible messages that can be sent are listed below.
The initial keywords in each message are case-sensitive.
* Commands from Client to Server
* **pickup**
* **hangup**
* **dial** #, where # is the number of the extension to be dialed.
* **chat** ...arbitrary text...
* Responses from Server to Client
* **ON HOOK** #, where # reports the extension number of the client.
* **RINGING**
* **DIAL TONE**
* **RING BACK**
* **CONNECTED** #, where # is the number of the extension to which the
connection exists.
* **ERROR**
* **CHAT** ...arbitrary text...
### Demonstration Server
To help you understand what the PBX server is supposed to do, I have provided
a complete implementation for demonstration purposes.
Run it by typing the following command:
```
$ demo/pbx -p 3333
```
You may replace `3333` by any port number 1024 or above (port numbers below 1024
are generally reserved for use as "well-known" ports for particular services,
and require "root" privilege to be used).
The server should report that it has been initialized and is listening
on the specified port.
From another terminal window, use `telnet` to connect to the server as follows:
```
$ telnet localhost 3333
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
ON HOOK 4
```
You can now issue commands to the server:
```
pickup
DIAL TONE
dial 5
ERROR
hangup
ON HOOK 4
```
If you make a second connection to the server from yet another terminal window,
you can make calls.
Note that the server will silently ignore syntactically incorrect commands,
such as "dial" without an extension number. If commands are issued when the
TU at the server side is in an inappropriate state (for example, if "dial 5"
is sent when the TU is "on hook"), then a response will be sent by the server
that just repeats the state of the TU, which does not change.
## Task I: Server Initialization
When the base code is compiled and run, it will print out a message
saying that the server will not function until `main()` is
implemented. This is your first task. The `main()` function will
need to do the following things:
- Obtain the port number to be used by the server from the command-line
arguments. The port number is to be supplied by the required option
`-p <port>`.
- Install a `SIGHUP` handler so that clean termination of the server can
be achieved by sending it a `SIGHUP`. Note that you need to use
`sigaction()` rather than `signal()`, as the behavior of the latter is
not well-defined in a multithreaded context.
- Set up the server socket and enter a loop to accept connections
on this socket. For each connection, a thread should be started to
run function `pbx_client_service()`.
These things should be relatively straightforward to accomplish, given the
information presented in class and in the textbook. If you do them properly,
the server should function and accept connections on the specified port,
and you should be able to connect to the server using the test client.
## Task II: Server Module
In this part of the assignment, you are to implement the server module,
which provides the function `pbx_client_service()` that is invoked
when a client connects to the server. You should implement this function
in the `src/server.c` file.
The `pbx_client_service` function is invoked as the **thread function**
for a thread that is created (using ``pthread_create()``) to service a
client connection.
The argument is a pointer to the integer file descriptor to be used
to communicate with the client. Once this file descriptor has been
retrieved, the storage it occupied needs to be freed.
The thread must then become detached, so that it does not have to be
explicitly reaped, it must initialize a new TU with that file descriptor,
and it must register the TU with the PBX module under a particular
extension number. The demo program uses the file descriptor as the
extension number, but you may choose a different scheme if you wish.
Finally, the thread should enter a service loop in which it repeatedly
receives a message sent by the client, parses the message, and carries
out the specified command.
The actual work involved in carrying out the command is performed by calling
the functions provided by the PBX module.
These functions will also send the required response back to the client
(each syntactically correct command will elicit a single response that
contains the resulting state of the TU)
so the server module need not be directly concerned with that.
## Task III: PBX Module
The PBX module is the central module in the implementation of the server.
It provides the functions listed below, for which more detailed specifications
are given in the source file `pbx.c`.
* `PBX *pbx_init()`: Initialize a new PBX.
* `void pbx_shutdown(PBX *pbx)`: Shut down a PBX.
* `int pbx_register(PBX *pbx, TU *tu, int ext)`: Register a TU with a PBX.
* `int pbx_unregister(PBX *pbx, TU *tu)`: Unregister a TU from a PBX.
* `int pbx_dial(PBX *pbx, TU *tu, int ext)`: Dial an extension.
The PBX module will need to maintain a registry of connected clients and
manage the TU objects associated with these clients.
The PBX will need to be able to map each extension number to the associated TU object.
As the PBX object will be accessed concurrently by multiple threads,
it will need to provide appropriate synchronization (for example, using mutexes and/or
semaphores) to ensure correct and reliable operation.
Finally, the `pbx_shutdown()` function is required to shut down the network connections
to all registered clients (the `shutdown(2)` function can be used to shut down a socket
for reading, writing, or both, without closing the associated file descriptor)
and it is then required to wait for all the client service threads to unregister
the associated TUs before returning. Consider using a semaphore, possibly in conjunction
with additional bookkeeping variables, for this purpose.
## Task IV: TU Module
The TU module implements objects that simulate a telephone unit.
The functions provided by this module are shown below. More detailed specifications
can be found in the source file `tu.c`.
* `TU *tu_init(int fd)`: Initialize a new TU with the file descriptor for a client.
* `void tu_ref(TU *tu, char *reason)`: Increase the reference count (see below) of a TU by one.
* `void tu_unref(TU *tu, char *reason)`: Decrease the reference count of a TU by one,
freeing the TU and associated resources if the reference count reaches zero.
* `int tu_fileno(TU *tu)`: Get the file descriptor for the network connection underlying a TU.
* `int tu_extension(TU *tu)`: Get the extension number for a TU.
* `int tu_set_extension(TU *tu, int ext)`: Set the extension number for a TU.
* `int tu_pickup(TU *tu)`: Take a TU receiver off-hook (i.e. pick up the handset).
* `int tu_hangup(TU *tu)`: Hang up a TU (i.e. replace the handset on the switchhook).
* `int tu_dial(TU *tu, int ext)`: Use a TU to originate a call to a specified extension.
* `int tu_chat(TU *tu, char *msg)`: "Chat" during a call to another TU.
Each TU object will contain the file descriptor of an underlying network connection
to a client, as well as a representation of the current state of the TU.
It will need to use the file descriptor to issue responses to the client,
as well as any required asynchronous notifications, whenever any of the `tu_xxx`
functions is called.
Since the TU objects will be accessed concurrently by multiple threads,
the TU module will need to provide appropriate synchronization
Changes to the state of a TU will require exclusive access to the TU.
Some operations require simultaneous changes of state of two TUs; these will require
exclusive access to both TUs at the same time in order to ensure the simultaneity of
the state changes. Care must be taken to avoid deadlock when obtaining exclusive
access to two TUs at the same time.
Sending responses and notifications to the network client managed by a TU will also
require exclusive access to the TU, in order to ensure that messages sent by separate
threads are serialized over the network connection, rather than possibly intermingled.
In order for TU objects to exist independently of a PBX object, yet still avoid the possibility
of having "dangling pointers" to TU objects that have unexpectedly been freed, the TU module
uses a *reference counting* scheme. The basic idea of reference counts is that they are
an integer field stored in an object that keeps track of the number of references
(*i.e.* pointers) that exist to that object. When a pointer to a TU object is copied,
the reference count on that object must be increased in order to account for the additional
pointer that now exists. When a pointer to a TU object is discarded, the reference count on
that object must be decreased in order to account for the pointer that no longer exists.
When the reference count on a TU object has been decremented to zero, that means there are
no longer any pointers by which that object can be accessed and therefore the object can
safely be freed. Incrementing and decrementing the reference count of a TU is performed
by calling the `tu_ref()` and `tu_unref()` functions. These functions take as an argument
the TU to be manipulated, but in addition they take a second argument `msg`.
This is for debugging purposes: when you increment or decrement a TU you should supply
as the `msg` argument a string giving the reason why the reference count is being changed.
If, when you implement the TU module, you have the `tu_ref()` and `tu_unref()` functions
announce when they are called, along with the reason why, it will make it easier to
debug reference count issues.
## Test Exerciser
The `tests` directory in the basecode contains a test driver with a few selected tests
to get you started. These tests are coded using Criterion as usual, but note that it
is important that when you run the tests you supply the `-j1` argument to `pbx_tests`.
This is because each test starts its own server instance and if you run them all
concurrently only one server instance will be able to bind the port number that is
being used and the other server instances will fail.
The file `basecode_tests.c` contains the Criterion portion of the tests and the file
`script_tester.c` contains the test driver that they use. The file `__test_includes.h`
contains some macros and declarations that are shared between the two files.
The overall idea of the tests are that they are table-driven. Each test has a "script",
which defines an array of `TEST_STEP` structures. The `TEST_STEP` structure is
defined in `__test_includes.h`. Each step contains an `id`, which identifies a particular
TU that should execute the step, a `command` which is the command to be sent to the TU,
an `id_to_dial` field which specifies the extension to be dialed in case the `command`
is TU_DIAL, a `response` field, which specifies the TU state that it is expected will
be sent in the response from the server, and a `timeout` field, which can be used to
place a limit on how long the tester will wait for a response from the server.
The `timeout` values themselves have type `struct timeval` (see `man 2 setitimer` for
a definition). There are several predefined timeout values of various lengths defined
in `__test_includes.h`. The idea is that if the expected response from the server does
not arrive before the timeout expires, then the test script fails. A timeout value of
`ZERO_SEC` means the test driver will wait indefinitely for the response from the server.
To use the full capabilities of the test driver is probably somewhat complicated,
since if you get multiple TUs sending commands in a concurrent fashion you have to
start worrying about asynchronous notifications from the server "crossing in front of"
the expected response to a command. But you can probably follow the basic pattern in
the scripts that I have provided to create other similar scripts that test the ability of
your server process other series of commands issued in rapid succession.
## Debugging Multi-threaded Code with `gdb`
The `gdb` debugger has features for debugging multi-threaded code.
In particular, it is aware of the presence of multiple threads and it provides
commands for you to switch the focus of debugging from one thread to another.
Use the `info threads` command to get a list of the existing threads.
Use the command `thread nnn` (replace `nnn` by the thread number) to switch
the focus of debugging to that thread. Once you have done, this, the `gdb`
commands such as `bt` that examine the stack will be executed with respect
to the selected thread. Threads can be stopped and started independently
using `gdb`, as well.
## Submission Instructions
Make sure your hw5 directory looks similarly to the way it did
initially and that your homework compiles (be sure to try compiling
both with and without "debug").
Note that you should omit any source files for modules that you did not
complete, and that you might have some source and header files in addition
to those shown. You are also, of course, encouraged to create Criterion
tests for your code.
It would definitely be a good idea to use `valgrind` to check your program
for memory and file descriptor leaks. Keeping track of allocated objects
and making sure to free them is potentially one of the more challenging aspects
of this assignment.
To submit, run `git submit hw5`.

199
hw5/include/csapp.h Normal file
View File

@ -0,0 +1,199 @@
/*
* csapp.h - prototypes and definitions for the CS:APP3e book
*/
/* $begin csapp.h */
#ifndef __CSAPP_H__
#define __CSAPP_H__
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* Default file permissions are DEF_MODE & ~DEF_UMASK */
/* $begin createmasks */
#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
#define DEF_UMASK S_IWGRP|S_IWOTH
/* $end createmasks */
/* Simplifies calls to bind(), connect(), and accept() */
/* $begin sockaddrdef */
typedef struct sockaddr SA;
/* $end sockaddrdef */
/* Persistent state for the robust I/O (Rio) package */
/* $begin rio_t */
#define RIO_BUFSIZE 8192
typedef struct {
int rio_fd; /* Descriptor for this internal buf */
int rio_cnt; /* Unread bytes in internal buf */
char *rio_bufptr; /* Next unread byte in internal buf */
char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;
/* $end rio_t */
/* External variables */
extern int h_errno; /* Defined by BIND for DNS errors */
extern char **environ; /* Defined by libc */
/* Misc constants */
#define MAXLINE 8192 /* Max text line length */
#define MAXBUF 8192 /* Max I/O buffer size */
#define LISTENQ 1024 /* Second argument to listen() */
/* Our own error-handling functions */
void unix_error(char *msg);
void posix_error(int code, char *msg);
void dns_error(char *msg);
// void gai_error(int code, char *msg);
void app_error(char *msg);
/* Process control wrappers */
pid_t Fork(void);
void Execve(const char *filename, char *const argv[], char *const envp[]);
pid_t Wait(int *status);
pid_t Waitpid(pid_t pid, int *iptr, int options);
void Kill(pid_t pid, int signum);
unsigned int Sleep(unsigned int secs);
void Pause(void);
unsigned int Alarm(unsigned int seconds);
void Setpgid(pid_t pid, pid_t pgid);
pid_t Getpgrp();
/* Signal wrappers */
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
void Sigemptyset(sigset_t *set);
void Sigfillset(sigset_t *set);
void Sigaddset(sigset_t *set, int signum);
void Sigdelset(sigset_t *set, int signum);
int Sigismember(const sigset_t *set, int signum);
int Sigsuspend(const sigset_t *set);
/* Sio (Signal-safe I/O) routines */
ssize_t sio_puts(char s[]);
ssize_t sio_putl(long v);
void sio_error(char s[]);
/* Sio wrappers */
ssize_t Sio_puts(char s[]);
ssize_t Sio_putl(long v);
void Sio_error(char s[]);
/* Unix I/O wrappers */
int Open(const char *pathname, int flags, mode_t mode);
ssize_t Read(int fd, void *buf, size_t count);
ssize_t Write(int fd, const void *buf, size_t count);
off_t Lseek(int fildes, off_t offset, int whence);
void Close(int fd);
int Select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
int Dup2(int fd1, int fd2);
void Stat(const char *filename, struct stat *buf);
void Fstat(int fd, struct stat *buf) ;
/* Directory wrappers */
DIR *Opendir(const char *name);
struct dirent *Readdir(DIR *dirp);
int Closedir(DIR *dirp);
/* Memory mapping wrappers */
void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
void Munmap(void *start, size_t length);
/* Standard I/O wrappers */
void Fclose(FILE *fp);
FILE *Fdopen(int fd, const char *type);
char *Fgets(char *ptr, int n, FILE *stream);
FILE *Fopen(const char *filename, const char *mode);
void Fputs(const char *ptr, FILE *stream);
size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
/* Dynamic storage allocation wrappers */
void *Malloc(size_t size);
void *Realloc(void *ptr, size_t size);
void *Calloc(size_t nmemb, size_t size);
void Free(void *ptr);
/* Sockets interface wrappers */
int Socket(int domain, int type, int protocol);
void Setsockopt(int s, int level, int optname, const void *optval, int optlen);
void Bind(int sockfd, struct sockaddr *my_addr, int addrlen);
void Listen(int s, int backlog);
int Accept(int s, struct sockaddr *addr, socklen_t *addrlen);
void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
/* Protocol independent wrappers */
void Getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host,
size_t hostlen, char *serv, size_t servlen, int flags);
void Freeaddrinfo(struct addrinfo *res);
void Inet_ntop(int af, const void *src, char *dst, socklen_t size);
void Inet_pton(int af, const char *src, void *dst);
/* DNS wrappers */
struct hostent *Gethostbyname(const char *name);
struct hostent *Gethostbyaddr(const char *addr, int len, int type);
/* Pthreads thread control wrappers */
void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp,
void * (*routine)(void *), void *argp);
void Pthread_join(pthread_t tid, void **thread_return);
void Pthread_cancel(pthread_t tid);
void Pthread_detach(pthread_t tid);
void Pthread_exit(void *retval);
pthread_t Pthread_self(void);
void Pthread_once(pthread_once_t *once_control, void (*init_function)());
/* POSIX semaphore wrappers */
void Sem_init(sem_t *sem, int pshared, unsigned int value);
void P(sem_t *sem);
void V(sem_t *sem);
/* Rio (Robust I/O) package */
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
void rio_readinitb(rio_t *rp, int fd);
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
/* Wrappers for Rio package */
ssize_t Rio_readn(int fd, void *usrbuf, size_t n);
void Rio_writen(int fd, void *usrbuf, size_t n);
void Rio_readinitb(rio_t *rp, int fd);
ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
/* Reentrant protocol-independent client/server helpers */
int open_clientfd(char *hostname, char *port);
int open_listenfd(char *port);
/* Wrappers for reentrant protocol-independent client/server helpers */
int Open_clientfd(char *hostname, char *port);
int Open_listenfd(char *port);
#endif /* __CSAPP_H__ */
/* $end csapp.h */

0
hw5/include/excludes.h Normal file
View File

1068
hw5/src/csapp.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
#include <stdlib.h> #define _GNU_SOURCE
#include <unistd.h>
#include "pbx.h" #include "pbx.h"
#include "server.h" #include "server.h"
#include "debug.h" #include "debug.h"
#include "csapp.h"
static void terminate(int status); static void terminate(int status);
void sighandler(int sig);
/* /*
* "PBX" telephone exchange simulation. * "PBX" telephone exchange simulation.
@ -27,10 +27,50 @@ int main(int argc, char* argv[]){
// a SIGHUP handler, so that receipt of SIGHUP will perform a clean // a SIGHUP handler, so that receipt of SIGHUP will perform a clean
// shutdown of the server. // shutdown of the server.
fprintf(stderr, "You have to finish implementing main() " // check arg counts and second arg valid
"before the PBX server will function.\n"); if (argc != 3 || argv[1][0] != '-' || argv[1][1] != 'p' || argv[1][2] != '\0')
{
fprintf(stderr, "Usage: ./pbx -p <port>\n");
terminate(EXIT_FAILURE); terminate(EXIT_FAILURE);
}
// check port num
int port = 0;
char* ptr = argv[2];
while (*ptr)
{
if (*ptr > '9' || *ptr < '0' || port > 65535)
{
fprintf(stderr, "Please provide a valid port number\n");
terminate(EXIT_FAILURE);
}
port *= 10;
port += *ptr - '0';
ptr ++;
}
// install SIGHUP handler
struct sigaction act;
act.sa_handler = sighandler;
if(sigaction(SIGHUP, &act, NULL)<0)
{
fprintf(stderr, "Failed to install a SIGHUP handler");
terminate(EXIT_FAILURE);
}
// multi threading & socket connection
int listenfd, *connfdp;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
pthread_t tid;
listenfd = Open_listenfd(argv[2]);
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfdp = Malloc(sizeof(int));
*connfdp = Accept(listenfd, (SA *) &clientaddr, &clientlen);
Pthread_create(&tid, NULL, (void*) pbx_client_service, connfdp);
}
terminate(EXIT_SUCCESS);
} }
/* /*
@ -42,3 +82,9 @@ static void terminate(int status) {
debug("PBX server terminating"); debug("PBX server terminating");
exit(status); exit(status);
} }
void sighandler(int sig)
{
debug("Server turned down successfully...");
terminate(EXIT_SUCCESS);
}

View File

@ -2,6 +2,9 @@
* PBX: simulates a Private Branch Exchange. * PBX: simulates a Private Branch Exchange.
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/socket.h>
#include "pbx.h" #include "pbx.h"
#include "debug.h" #include "debug.h"
@ -11,10 +14,28 @@
* *
* @return the newly initialized PBX, or NULL if initialization fails. * @return the newly initialized PBX, or NULL if initialization fails.
*/ */
#if 0 #if 1
typedef struct pbx
{
pthread_mutex_t mutex;
TU *tus[PBX_MAX_EXTENSIONS];
} PBX;
void pbx_unregister_helper(TU *tu);
PBX *pbx_init() { PBX *pbx_init() {
// TO BE IMPLEMENTED PBX *pbx = malloc(sizeof(PBX));
abort(); if (!pbx)
{
debug("ERROR: Fail to alloc memeory for pbx");
return NULL;
}
// init pbx
for (size_t i = 0; i < PBX_MAX_EXTENSIONS; i++)
pbx->tus[i] = NULL;
pthread_mutex_init(&pbx->mutex, NULL);
return pbx;
} }
#endif #endif
@ -28,10 +49,25 @@ PBX *pbx_init() {
* *
* @param pbx The PBX to be shut down. * @param pbx The PBX to be shut down.
*/ */
#if 0 #if 1
void pbx_shutdown(PBX *pbx) { void pbx_shutdown(PBX *pbx) {
// TO BE IMPLEMENTED if (!pbx)
abort(); {
debug("ERROR: pbx does not extst");
return;
}
pthread_mutex_lock(&pbx->mutex);
for (size_t i = 0; i < PBX_MAX_EXTENSIONS; i++)
{
if (pbx->tus[i])
{
pbx_unregister_helper(pbx->tus[i]);
pbx->tus[i] = NULL;
}
}
pthread_mutex_unlock(&pbx->mutex);
pthread_mutex_destroy(&pbx->mutex);
free(pbx);
} }
#endif #endif
@ -49,10 +85,32 @@ void pbx_shutdown(PBX *pbx) {
* @param ext The extension number on which the TU is to be registered. * @param ext The extension number on which the TU is to be registered.
* @return 0 if registration succeeds, otherwise -1. * @return 0 if registration succeeds, otherwise -1.
*/ */
#if 0 #if 1
int pbx_register(PBX *pbx, TU *tu, int ext) { int pbx_register(PBX *pbx, TU *tu, int ext) {
// TO BE IMPLEMENTED if (!pbx || !tu)
abort(); {
debug("ERROR: pbx or tu does not exist");
return -1;
}
if (ext > PBX_MAX_EXTENSIONS + 1 || ext < 1)
{
debug("ERROR: ext is out of range");
return -1;
}
pthread_mutex_lock(&pbx->mutex);
if (pbx->tus[ext])
{
debug("ERROR: ext already exist");
pthread_mutex_unlock(&pbx->mutex);
return -1;
}
tu_ref(tu, "Register tu to pbx");
tu_set_extension(tu, ext);
pbx->tus[ext] = tu;
pthread_mutex_unlock(&pbx->mutex);
return 0;
} }
#endif #endif
@ -68,13 +126,39 @@ int pbx_register(PBX *pbx, TU *tu, int ext) {
* @param tu The TU to be unregistered. * @param tu The TU to be unregistered.
* @return 0 if unregistration succeeds, otherwise -1. * @return 0 if unregistration succeeds, otherwise -1.
*/ */
#if 0 #if 1
int pbx_unregister(PBX *pbx, TU *tu) { int pbx_unregister(PBX *pbx, TU *tu) {
// TO BE IMPLEMENTED if (!pbx || !tu)
abort(); {
debug("ERROR: pbx or tu does not exist");
return -1;
}
int ext = tu_extension(tu);
if (ext > PBX_MAX_EXTENSIONS + 1 || ext < 1)
{
debug("ERROR: ext is out of range");
return -1;
}
pthread_mutex_lock(&pbx->mutex);
if (!pbx->tus[ext])
{
debug("ERROR: ext does not exist");
pthread_mutex_unlock(&pbx->mutex);
return -1;
}
pbx_unregister_helper(tu);
pbx->tus[ext] = NULL;
pthread_mutex_unlock(&pbx->mutex);
return 0;
} }
#endif #endif
void pbx_unregister_helper(TU *tu) {
tu_hangup(tu);
shutdown(tu_fileno(tu), SHUT_RD);
tu_unref(tu, "Unregister tu from pbx");
}
/* /*
* Use the PBX to initiate a call from a specified TU to a specified extension. * Use the PBX to initiate a call from a specified TU to a specified extension.
* *
@ -83,9 +167,21 @@ int pbx_unregister(PBX *pbx, TU *tu) {
* @param ext The extension number to be called. * @param ext The extension number to be called.
* @return 0 if dialing succeeds, otherwise -1. * @return 0 if dialing succeeds, otherwise -1.
*/ */
#if 0 #if 1
int pbx_dial(PBX *pbx, TU *tu, int ext) { int pbx_dial(PBX *pbx, TU *tu, int ext) {
// TO BE IMPLEMENTED if (!pbx || !tu)
abort(); {
debug("ERROR: pbx or tu does not exist");
return -1;
}
if (ext > PBX_MAX_EXTENSIONS || ext < 0)
{
debug("ERROR: ext is out of range");
return -1;
}
pthread_mutex_lock(&pbx->mutex);
int status = tu_dial(tu, pbx->tus[ext]);
pthread_mutex_unlock(&pbx->mutex);
return status;
} }
#endif #endif

View File

@ -3,19 +3,134 @@
* Manages interaction with a client telephone unit (TU). * Manages interaction with a client telephone unit (TU).
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include "debug.h" #include "debug.h"
#include "pbx.h" #include "pbx.h"
#include "server.h" #include "server.h"
#include "tu.h"
/* /*
* Thread function for the thread that handles interaction with a client TU. * Thread function for the thread that handles interaction with a client TU.
* This is called after a network connection has been made via the main server * This is called after a network connection has been made via the main server
* thread and a new thread has been created to handle the connection. * thread and a new thread has been created to handle the connection.
*/ */
#if 0 #if 1
int pbx_get_next_command(FILE* f, char** str);
int pbx_run_command(TU *tu, char* cmd);
void *pbx_client_service(void *arg) { void *pbx_client_service(void *arg) {
// TO BE IMPLEMENTED // detach the thread
abort(); pthread_detach(pthread_self());
// free file descriptor
if (!arg)
return NULL;
int fd = *(int*)arg;
free(arg);
// init TU
TU* tu = tu_init(fd);
if (!tu)
return NULL;
// open socket
FILE *f = fdopen(fd, "r");
// register tu
if (pbx_register(pbx, tu, fd))
{
// not success
fclose(f);
tu_unref(tu, "fail to register a TU");
return NULL;
}
else {
// success
char* command = NULL;
while (1)
{
command = NULL;
if (pbx_get_next_command(f, &command))
break;
debug("Got command: %s", command);
pbx_run_command(tu, command);
free(command);
}
// no more command
fclose(f);
tu_unref(tu, "unregister tu");
pbx_unregister(pbx, tu);
return NULL;
}
}
/**
* try to get the next command from f and assign to str
* return 0 if next command exist
* return -1 if next command does not exist or have length 0
* return -2 if fail to alloc memory
* return -3 if fail to realloc memory
*/
int pbx_get_next_command(FILE* f, char** str) {
int size = 8, index = 0;
char *cmd = malloc(sizeof(char) * size);
if (!cmd)
// fail to alloc memory
return -2;
char c;
while ((c = fgetc(f)) != EOF)
{
if (index == size - 1)
{
size *= 2;
cmd = realloc(cmd, sizeof(char)*size);
if (!cmd)
// fail to realloc memory
return -3;
}
cmd[index++] = c;
if (c == '\r' || c == '\n')
cmd[index-1] = '\0';
if (c == '\n')
break;
}
// command is empty
if (index == 0) {
free(cmd);
return -1;
}
cmd[index--] = '\0';
*str = cmd;
return 0;
}
int pbx_run_command(TU *tu, char* cmd) {
if (!strcmp(tu_command_names[TU_PICKUP_CMD], cmd))
return tu_pickup(tu);
if (!strcmp(tu_command_names[TU_HANGUP_CMD], cmd))
return tu_hangup(tu);
if (cmd == strstr(cmd, tu_command_names[TU_DIAL_CMD]))
{
char* ptr = &cmd[strlen(tu_command_names[TU_DIAL_CMD])];
while (*ptr == ' ')
ptr ++;
if (!*ptr)
return -1;
return pbx_dial(pbx, tu, atoi(ptr));
}
if (cmd == strstr(cmd, tu_command_names[TU_CHAT_CMD]))
{
char* ptr = &cmd[strlen(tu_command_names[TU_CHAT_CMD])];
while (*ptr == ' ')
ptr ++;
return tu_chat(tu, ptr);
}
return -1;
} }
#endif #endif

View File

@ -2,6 +2,8 @@
* TU: simulates a "telephone unit", which interfaces a client with the PBX. * TU: simulates a "telephone unit", which interfaces a client with the PBX.
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include "pbx.h" #include "pbx.h"
#include "debug.h" #include "debug.h"
@ -13,10 +15,42 @@
* @return The TU, newly initialized and in the TU_ON_HOOK state, if initialization * @return The TU, newly initialized and in the TU_ON_HOOK state, if initialization
* was successful, otherwise NULL. * was successful, otherwise NULL.
*/ */
#if 0 #if 1
typedef struct tu
{
int fd;
FILE *f;
int ext;
int ref;
TU *chat_TU;
int chat_locked;
TU_STATE state;
pthread_mutex_t mutex;
} TU;
int tu_notify(TU *tu);
int tu_lock(TU *tu);
int tu_unlock(TU *tu);
void tu_destroy(TU* tu);
TU *tu_init(int fd) { TU *tu_init(int fd) {
// TO BE IMPLEMENTED TU *tu;
abort();
tu = malloc(sizeof(TU));
if ( !tu ) {
debug("ERROR: fail to alloc memory for tu");
return NULL;
}
tu->fd = fd;
tu->f = fdopen(fd, "w");
tu->ext = 0;
tu->ref = 1;
tu->chat_TU = NULL;
tu->chat_locked = 0;
tu->state = TU_ON_HOOK;
pthread_mutex_init(&tu->mutex, NULL);
return tu;
} }
#endif #endif
@ -27,10 +61,17 @@ TU *tu_init(int fd) {
* @param reason A string describing the reason why the count is being incremented * @param reason A string describing the reason why the count is being incremented
* (for debugging purposes). * (for debugging purposes).
*/ */
#if 0 #if 1
void tu_ref(TU *tu, char *reason) { void tu_ref(TU *tu, char *reason) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu does not exist");
return;
}
pthread_mutex_lock(&tu->mutex);
// debug(reason);
tu->ref ++;
pthread_mutex_unlock(&tu->mutex);
} }
#endif #endif
@ -41,10 +82,20 @@ void tu_ref(TU *tu, char *reason) {
* @param reason A string describing the reason why the count is being decremented * @param reason A string describing the reason why the count is being decremented
* (for debugging purposes). * (for debugging purposes).
*/ */
#if 0 #if 1
void tu_unref(TU *tu, char *reason) { void tu_unref(TU *tu, char *reason) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu does not exist");
return;
}
pthread_mutex_lock(&tu->mutex);
// debug(reason);
tu->ref --;
if (tu->ref == 0)
tu_destroy(tu);
else
pthread_mutex_unlock(&tu->mutex);
} }
#endif #endif
@ -57,10 +108,17 @@ void tu_unref(TU *tu, char *reason) {
* @param tu * @param tu
* @return the underlying file descriptor, if any, otherwise -1. * @return the underlying file descriptor, if any, otherwise -1.
*/ */
#if 0 #if 1
int tu_fileno(TU *tu) { int tu_fileno(TU *tu) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu does not exist");
return -1;
}
pthread_mutex_lock(&tu->mutex);
int fd = tu->fd;
pthread_mutex_unlock(&tu->mutex);
return fd;
} }
#endif #endif
@ -74,10 +132,17 @@ int tu_fileno(TU *tu) {
* @param tu * @param tu
* @return the extension number, if any, otherwise -1. * @return the extension number, if any, otherwise -1.
*/ */
#if 0 #if 1
int tu_extension(TU *tu) { int tu_extension(TU *tu) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu does not exist");
return -1;
}
pthread_mutex_lock(&tu->mutex);
int ext = tu->ext;
pthread_mutex_unlock(&tu->mutex);
return ext;
} }
#endif #endif
@ -88,10 +153,24 @@ int tu_extension(TU *tu) {
* *
* @param tu The TU whose extension is being set. * @param tu The TU whose extension is being set.
*/ */
#if 0 #if 1
int tu_set_extension(TU *tu, int ext) { int tu_set_extension(TU *tu, int ext) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu does not exist");
return -1;
}
pthread_mutex_lock(&tu->mutex);
if (tu->ext)
{
debug("ERROR: extention has already been set");
pthread_mutex_unlock(&tu->mutex);
return -1;
}
tu->ext = ext;
int status = tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
return status;
} }
#endif #endif
@ -125,10 +204,90 @@ int tu_set_extension(TU *tu, int ext) {
* @return 0 if successful, -1 if any error occurs that results in the originating * @return 0 if successful, -1 if any error occurs that results in the originating
* TU transitioning to the TU_ERROR state. * TU transitioning to the TU_ERROR state.
*/ */
#if 0 #if 1
int tu_dial(TU *tu, TU *target) { int tu_dial(TU *tu, TU *target) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu or target does not exist");
return -1;
}
pthread_mutex_lock(&tu->mutex);
if (tu->chat_TU)
{
debug("ERROR: Already in chat");
tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
return -1;
}
if (tu->state != TU_DIAL_TONE)
{
debug("ERROR: not in dial state");
tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
return -1;
}
if (target)
{
if (tu == target)
{
debug("ERROR: calling it self");
tu->state = TU_BUSY_SIGNAL;
int status = tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
return status;
}
if (tu->ext < target->ext)
{
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_lock(&tu->mutex);
pthread_mutex_lock(&target->mutex);
}
else {
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_lock(&target->mutex);
pthread_mutex_lock(&tu->mutex);
}
if (target->chat_TU)
{
debug("ERROR: target is already in chat");
tu->state = TU_BUSY_SIGNAL;
int status = tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_unlock(&target->mutex);
return status;
}
if (target->state != TU_ON_HOOK)
{
debug("ERROR: target not in on hook state");
tu->state = TU_BUSY_SIGNAL;
int status = tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_unlock(&target->mutex);
return status;
}
debug("dialing");
tu->ref++;
target->ref++;
tu->chat_TU = target;
target->chat_TU = tu;
tu->state = TU_RING_BACK;
target->state = TU_RINGING;
int status = 0;
if (tu_notify(tu))
status = -1;
if (tu_notify(target))
status = -1;
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_unlock(&target->mutex);
return status;
}
else {
debug("ERROR: cannot find target");
tu->state = TU_ERROR;
int status = tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
return status;
}
} }
#endif #endif
@ -149,10 +308,54 @@ int tu_dial(TU *tu, TU *target) {
* @return 0 if successful, -1 if any error occurs that results in the originating * @return 0 if successful, -1 if any error occurs that results in the originating
* TU transitioning to the TU_ERROR state. * TU transitioning to the TU_ERROR state.
*/ */
#if 0 #if 1
int tu_pickup(TU *tu) { int tu_pickup(TU *tu) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu or target does not exist");
return -1;
}
tu_lock(tu);
if (tu->state == TU_ON_HOOK)
{
debug("TU is on hook");
tu->state = TU_DIAL_TONE;
int status = tu_notify(tu);
tu_unlock(tu);
return status;
}
if (tu->state == TU_RINGING)
{
if (tu->chat_TU)
{
if (tu->chat_TU->state == TU_RING_BACK && tu == tu->chat_TU->chat_TU)
{
debug("connecting");
tu->state = TU_CONNECTED;
tu->chat_TU->state = TU_CONNECTED;
int status = 0;
if (tu_notify(tu))
status = -1;
if (tu_notify(tu->chat_TU))
status = -1;
tu_unlock(tu);
return status;
}
debug("ERROR: ringing and has chat_TU but chat_TU is not in ring back or tu's chat_TU's chat_TU is not tu");
tu_notify(tu);
tu_unlock(tu);
return -1;
}
else {
debug("ERROR: ringing but no chat_TU");
tu_unlock(tu);
return -1;
}
}
debug("already picked up");
tu_notify(tu);
tu_unlock(tu);
return -1;
} }
#endif #endif
@ -177,10 +380,107 @@ int tu_pickup(TU *tu) {
* @return 0 if successful, -1 if any error occurs that results in the originating * @return 0 if successful, -1 if any error occurs that results in the originating
* TU transitioning to the TU_ERROR state. * TU transitioning to the TU_ERROR state.
*/ */
#if 0 #if 1
int tu_hangup(TU *tu) { int tu_hangup(TU *tu) {
// TO BE IMPLEMENTED if (!tu)
abort(); {
debug("ERROR: tu or target does not exist");
return -1;
}
tu_lock(tu);
if (tu->state == TU_ON_HOOK)
{
debug("already on hook");
int status = tu_notify(tu);
tu_unlock(tu);
return status;
}
if (tu->state == TU_CONNECTED || tu->state == TU_RINGING)
{
if (tu->chat_TU)
{
debug("disconnecting peer");
tu->state = TU_ON_HOOK;
tu->chat_TU->state = TU_DIAL_TONE;
int status = 0;
if (tu_notify(tu))
status = -1;
if (tu_notify(tu->chat_TU))
status = -1;
tu->chat_TU->chat_TU = NULL;
tu->chat_TU->ref --;
if (tu->chat_TU->ref <= 0)
tu_destroy(tu->chat_TU);
else
pthread_mutex_unlock(&tu->chat_TU->mutex);
tu->chat_TU = NULL;
tu->ref --;
tu->chat_locked = 0;
if (tu->ref == 0)
tu_destroy(tu);
else
pthread_mutex_unlock(&tu->mutex);
return status;
}
else {
debug("ERROR: connecting or ringing but no peer");
tu->state = TU_ON_HOOK;
tu_notify(tu);
tu_unlock(tu);
return -1;
}
}
if (tu->state == TU_RING_BACK)
{
if (tu->chat_TU)
{
debug("disconnecting peer");
tu->state = TU_ON_HOOK;
tu->chat_TU->state = TU_ON_HOOK;
int status = 0;
if (tu_notify(tu))
status = -1;
if (tu_notify(tu->chat_TU))
status = -1;
tu->chat_TU->chat_TU = NULL;
tu->chat_TU->ref --;
if (tu->chat_TU->ref <= 0)
tu_destroy(tu->chat_TU);
else
pthread_mutex_unlock(&tu->chat_TU->mutex);
tu->chat_TU = NULL;
tu->ref --;
tu->chat_locked = 0;
if (tu->ref == 0)
tu_destroy(tu);
else
pthread_mutex_unlock(&tu->mutex);
return status;
}
else {
debug("ERROR: ringing back but no peer");
tu->state = TU_ON_HOOK;
tu_notify(tu);
tu_unlock(tu);
return -1;
}
}
if (tu->chat_TU) {
debug("ERROR: no chat_TU in these states");
tu->state = TU_ON_HOOK;
tu_notify(tu);
tu_unlock(tu);
return -1;
}
tu->state = TU_ON_HOOK;
int status = tu_notify(tu);
tu_unlock(tu);
return status;
} }
#endif #endif
@ -197,9 +497,120 @@ int tu_hangup(TU *tu) {
* @return 0 If the chat was successfully sent, -1 if there is no call in progress * @return 0 If the chat was successfully sent, -1 if there is no call in progress
* or some other error occurs. * or some other error occurs.
*/ */
#if 0 #if 1
int tu_chat(TU *tu, char *msg) { int tu_chat(TU *tu, char *msg) {
// TO BE IMPLEMENTED pthread_mutex_lock(&tu->mutex);
abort(); if (tu->state == TU_CONNECTED)
{
TU *target = tu->chat_TU;
if (target)
{
if (tu->ext < target->ext)
{
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_lock(&tu->mutex);
pthread_mutex_lock(&target->mutex);
}
else {
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_lock(&target->mutex);
pthread_mutex_lock(&tu->mutex);
}
if (tu->chat_TU == target && target->chat_TU == tu
&& tu->state == TU_CONNECTED && target->state == TU_CONNECTED)
{
debug("sending msg");
int status = 0;
if (fprintf(target->f, "CHAT %s\r\n", msg) < 0 || fflush(target->f) < 0)
status = -1;
if (tu_notify(tu) < 0)
status = -1;
pthread_mutex_unlock(&target->mutex);
pthread_mutex_unlock(&tu->mutex);
return status;
}
else
{
debug("ERROR: target changed or chat_TU do not match");
tu_notify(tu);
pthread_mutex_unlock(&target->mutex);
pthread_mutex_unlock(&tu->mutex);
return -1;
}
}
else {
debug("ERROR: connected but no chat_TU");
tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
return -1;
}
}
else {
debug("ERROR: not in connected state");
tu_notify(tu);
pthread_mutex_unlock(&tu->mutex);
return -1;
}
} }
#endif #endif
int tu_notify(TU *tu) {
int status = 0;
if (fputs(tu_state_names[tu->state], tu->f) < 0)
status = -1;
if (tu->state == TU_ON_HOOK)
if (fprintf(tu->f, " %d", tu->fd) < 0)
status = -1;
if (tu->state == TU_CONNECTED)
if (fprintf(tu->f, " %d", tu->chat_TU->fd) < 0)
status = -1;
if (fprintf(tu->f, "\r\n") < 0 || fflush(tu->f) < 0)
status = -1;
if (status == -1)
debug("ERROR: fail to print message to client");
return status;
}
int tu_lock(TU *tu) {
while (1) {
pthread_mutex_lock(&tu->mutex);
TU *tmp = tu->chat_TU;
if (!tu->chat_TU)
return 0;
if (tu->ext < tmp->ext)
{
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_lock(&tu->mutex);
pthread_mutex_lock(&tmp->mutex);
tu->chat_locked = 1;
}
else {
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_lock(&tmp->mutex);
pthread_mutex_lock(&tu->mutex);
tu->chat_locked = 1;
}
if (tu->chat_TU == tmp)
return 0;
pthread_mutex_unlock(&tmp->mutex);
pthread_mutex_unlock(&tu->mutex);
}
}
int tu_unlock(TU *tu) {
if (tu->chat_locked)
{
pthread_mutex_unlock(&tu->chat_TU->mutex);
tu->chat_locked = 0;
}
pthread_mutex_unlock(&tu->mutex);
return 0;
}
void tu_destroy(TU* tu) {
pthread_mutex_unlock(&tu->mutex);
pthread_mutex_destroy(&tu->mutex);
free(tu);
}